명확한 단어와 예를 통해 클라이언트 캐싱의 기본 사항을 설명합니다. 최종 수정, Etag, Expires, Cache-control: max-age 및 기타 헤더. 캐싱 모범 사례 단순 ETag 캐싱

외부 CSS와 Javascript를 포함하여 불필요한 HTTP 요청을 최소한으로 줄이고 싶습니다.

이를 위해 .js 및 .css 파일은 안정적인 캐싱을 보장하는 헤더와 함께 제공됩니다.

하지만 개발 중에 이러한 파일 중 하나가 변경되면 어떻게 합니까? 모든 사용자가 캐시에 보관함 구 버전- 캐시가 오래될 때까지 서버와 클라이언트 부분의 통합이 깨졌다는 불만이 많이 있을 것입니다.

적절한 캐싱 및 버전 관리는 이 문제를 완전히 제거하고 스타일/스크립트 버전의 안정적이고 투명한 동기화를 제공합니다.

간단한 ETag 캐싱

정적 리소스를 캐시하는 가장 간단한 방법은 ETag를 사용하는 것입니다.

적절한 서버 설정을 활성화하면 충분합니다(Apache의 경우 기본적으로 활성화되어 있음). 헤더의 각 파일에 대해 ETag가 제공됩니다. 업데이트 시간, 파일 크기 및 (inode 기반에 따라 달라지는 해시) 파일 시스템) 아이노드.

브라우저는 이러한 파일을 캐시하고 후속 요청 시 캐시된 문서의 ETag를 사용하여 If-None-Match 헤더를 지정합니다. 이러한 헤더를 수신하면 서버는 코드 304로 응답할 수 있으며 그러면 문서가 캐시에서 가져옵니다.

다음과 같습니다.

서버에 대한 첫 번째 요청(캐시 정리) GET /misc/pack.js HTTP/1.1 호스트: 웹사이트

일반적으로 브라우저는 일반적으로 User-Agent, Accept 등과 같은 여러 헤더를 추가합니다. 간결하게 하기 위해 잘렸습니다.

서버 응답 서버는 코드 200 및 ETag: HTTP/1.x 200 OK Content-Encoding: gzip Content-Type: text/javascript; charset=utf-8 Etag: "3272221997" Accept-Ranges: bytes Content-Length: 23321 Date: Fri, 02 May 2008 17:22:46 GMT Server: lighttpd 다음 브라우저 요청 다음 요청에서 브라우저는 If-None을 추가합니다. -일치: (캐시된 ETag): GET /misc/pack.js HTTP/1.1 호스트: 사이트 If-None-Match: "453700005" 서버 응답 서버가 보입니다. 예, 문서는 변경되지 않았습니다. 이는 304 코드를 발행하고 문서를 다시 보낼 수 없음을 의미합니다. HTTP/1.x 304 수정되지 않음 콘텐츠 인코딩: gzip Etag: "453700005" 콘텐츠 유형: text/javascript; charset=utf-8 Accept-Ranges: 바이트 날짜: 2008년 4월 15일 화요일 10:17:11 GMT

대체 옵션- 문서가 변경된 경우 서버는 단순히 새 ETag와 함께 200을 보냅니다.

Last-Modified + If-Modified-Since 조합은 비슷한 방식으로 작동합니다.

  • 서버는 (ETag 대신) Last-Modified 헤더에 마지막 수정 날짜를 보냅니다.
  • 브라우저는 문서를 캐시하고 다음에 동일한 문서에 대한 요청이 이루어질 때 캐시된 버전의 날짜를 If-Modified-Since 헤더(If-None-Match 대신)로 보냅니다.
  • 서버는 날짜를 확인하고, 문서가 변경되지 않은 경우 내용 없이 304 코드만 보냅니다.
  • 이러한 방법은 안정적이고 잘 작동하지만 브라우저는 여전히 각 스크립트나 스타일에 대해 요청을 해야 합니다.

    스마트 캐싱. 버전 관리

    버전 관리에 대한 일반적인 접근 방식 - 간단히 말해서:

  • 버전(또는 수정 날짜)이 모든 스크립트에 추가됩니다. 예를 들어 http://site/my.js는 http://site/my.v1.2.js가 됩니다.
  • 모든 스크립트는 브라우저에 의해 하드 캐시됩니다.
  • 스크립트를 업데이트하면 버전이 새로운 버전으로 변경됩니다: http://site/my.v2.0.js
  • 주소가 변경되었으므로 브라우저가 파일을 다시 요청하고 캐시합니다.
  • 이전 버전 1.2는 점차 캐시에서 제외됩니다.
  • 하드 캐싱

    하드 캐싱- 캐시된 문서에 대한 서버 요청을 완전히 고정하는 일종의 큰 망치입니다.

    이렇게 하려면 Expires 및 Cache-Control: max-age 헤더를 추가하기만 하면 됩니다.

    예를 들어, PHP에서 365일 동안 캐시하려면 다음을 수행하세요.

    Header("만료: ".gmdate("D, d M Y H:i:s", time()+86400*365)." GMT"); header("캐시 제어: max-age="+86400*365);

    또는 Apache에서 mod_header를 사용하여 콘텐츠를 영구적으로 캐시할 수 있습니다.

    이러한 헤더를 수신하면 브라우저는 문서를 오랫동안 하드 캐시합니다. 문서에 대한 모든 추가 액세스는 서버에 접속하지 않고 브라우저 캐시에서 직접 제공됩니다.

    대부분의 브라우저(Opera, 인터넷 익스플로러 6+, Safari) 주소에 물음표가 있는 문서는 동적 문서로 간주되므로 캐시하지 마세요.

    이것이 바로 파일 이름에 버전을 추가하는 이유입니다. 물론 이러한 주소에는 mod_rewrite와 같은 솔루션을 사용해야 합니다. 이에 대해서는 기사 뒷부분에서 살펴보겠습니다.

    추신: 하지만 Firefox는 물음표가 있는 주소를 캐시합니다...

    자동 이름 확인

    파일 자체의 이름을 바꾸지 않고 자동으로 투명하게 버전을 변경하는 방법을 살펴보겠습니다.

    버전과 이름 -> 파일

    가장 간단한 방법은 버전 이름을 원본 파일 이름으로 바꾸는 것입니다.

    Apache 수준에서는 mod_rewrite를 사용하여 이 작업을 수행할 수 있습니다.

    RewriteRule의 RewriteEngine ^/(.*\.)v+\.(css|js|gif|png|jpg)$ /$1$2 [L]

    이 규칙은 모든 css/js/gif/png/jpg 파일을 처리하며 이름에서 버전을 제거합니다.

    예를 들어:

    /images/logo.v2.gif -> /images/logo.gif
    /css/style.v1.27.css -> /css/style.css
    /javascript/script.v6.js -> /javascript/script.js

    그러나 버전을 잘라내는 것 외에도 파일에 하드 캐싱 헤더를 추가해야 합니다. mod_header 지시문은 다음과 같이 사용됩니다.

    헤더 추가 "만료" "2014년 7월 28일 월요일 23:30:00 GMT" 헤더 추가 "Cache-Control" "max-age=315360000"

    그리고 모두 함께 다음 Apache 구성을 구현합니다.

    # RewriteEngine은 버전을 제거하고 동시에 파일의 버전이 지정되는 변수를 설정합니다. RewriteRule ^/(.*\.)v+\.(css|js|gif|png|jpg)$ /$1$2 # 하드 캐시 버전이 지정된 파일 헤더 추가 "만료" "월, 2014년 7월 28일 23:30:00 GMT" env=VERSIONED_FILE 헤더 추가 "Cache-Control" "max-age=315360000" env=VERSIONED_FILE

    mod_rewrite 모듈의 작동 방식으로 인해 RewriteRule을 기본 모듈에 배치해야 합니다. 구성 파일 httpd.conf 또는 포함된 파일이지만 .htaccess 에는 없습니다. 그렇지 않으면 VERSIONED_FILE 변수가 설정되기 전에 헤더 명령이 먼저 실행됩니다.

    헤더 지시문은 .htaccess를 포함해 어디에나 있을 수 있습니다. 상관없습니다.

    HTML 페이지의 파일 이름에 자동으로 버전 추가

    스크립트 이름에 버전을 넣는 방법은 템플릿 시스템과 일반적으로 스크립트(스타일 등)를 추가하는 방법에 따라 다릅니다.

    예를 들어 수정 날짜를 버전으로 사용하고 Smarty 템플릿 엔진을 사용하는 경우 링크는 다음과 같이 설정할 수 있습니다.

    version 함수는 버전을 추가합니다.

    함수 smarty_version($args)( $stat = stat($GLOBALS["config"]["site_root"].$args["src"]); $version = $stat["mtime"]; echo preg_replace("! \.(+?)$!", ".v$version.\$1", $args["src"]); )

    페이지 결과:

    최적화

    불필요한 통계 호출을 피하기 위해 현재 버전 목록이 포함된 배열을 별도의 변수에 저장할 수 있습니다.

    $versions["css"] = array("group.css" => "1.1", "other.css" => "3.0", )

    이 경우 배열의 현재 버전이 HTML로 대체됩니다.

    두 가지 접근 방식을 모두 교차할 수 있으며, 개발 중에는 관련성을 위해 수정 날짜별로 버전을 생성하고, 프로덕션에서는 성능을 위해 어레이 버전을 생성합니다.

    적용 가능성

    이 캐싱 방법은 Javascript, CSS, 이미지, Flash 동영상 등을 포함한 모든 곳에서 작동합니다.

    문서가 변경될 때마다 유용하지만 브라우저에는 항상 최신 버전이 있어야 합니다.

    캐시는 데이터베이스, 웹 서버 및 클라이언트 작업 수준에서 거의 모든 웹 애플리케이션의 작동에 중요한 역할을 합니다.

    이 기사에서는 클라이언트 캐싱을 이해하려고 노력할 것입니다. 특히, 브라우저와 웹 서버에서 사용되는 HTTP 헤더가 무엇인지, 그리고 그것이 무엇을 의미하는지 살펴보겠습니다.

    하지만 먼저 클라이언트 측 캐싱이 왜 필요한지 알아 보겠습니다. .

    웹페이지는 여러 가지로 구성되어 있습니다. 다양한 요소: 사진, CSS, js 파일 등 이러한 요소 중 일부는 사이트의 여러 페이지에서 사용됩니다. 클라이언트 측 캐싱은 파일을 다시 다운로드하지 않도록 파일 복사본(서버 응답)을 저장하는 브라우저의 기능을 의미합니다. 이를 통해 페이지 다시 로드 속도를 크게 높이고 트래픽을 절약하며 서버 부하도 줄일 수 있습니다.

    클라이언트 측 캐싱 프로세스를 제어하기 위한 여러 가지 HTTP 헤더가 있습니다. 그들 각각에 대해 이야기합시다.

    클라이언트 캐싱을 제어하는 ​​Http 헤더

    먼저 캐싱이 없을 때 서버와 브라우저가 어떻게 상호 작용하는지 살펴보겠습니다. 명확한 이해를 위해 그들 사이의 소통 과정을 문자 채팅의 형태로 상상하고 시각화해 보았습니다. 서버와 브라우저가 서로 통신하는 사람들이라고 잠시 상상해 보세요 :)

    캐시 없음(http 헤더 캐싱이 없는 경우)

    보시다시피 cat.png 이미지가 표시될 때마다 브라우저는 이를 서버에서 다시 다운로드합니다. 이것이 느리고 비효율적이라고 설명할 필요는 없다고 생각합니다.

    마지막으로 수정된 응답 헤더 및 if-Modified-Since 요청 헤더.

    아이디어는 서버가 브라우저에 제공하는 파일(응답)에 Last-modified 헤더를 추가한다는 것입니다.

    이제 브라우저는 해당 파일이 2014년 12월 1일에 생성(또는 수정)되었음을 알고 있습니다. 다음에 브라우저에 동일한 파일이 필요할 때 if-Modified-Since 헤더가 포함된 요청을 보냅니다.

    파일이 수정되지 않은 경우 서버는 304(수정되지 않음) 상태로 빈 응답을 브라우저에 보냅니다. 이 경우 브라우저는 파일이 업데이트되지 않았음을 알고 마지막으로 저장한 복사본을 표시할 수 있습니다.

    따라서 Last-modified를 사용하면 로딩 시간이 절약됩니다. 대용량 파일, 서버에서 빈 빠른 응답으로 하차합니다.

    Etag 응답 헤더 및 If-None-Match 요청 헤더.

    Etag의 작동 원리는 Last-modified와 매우 유사하지만, Etag와 달리 시간에 얽매이지 않습니다. 시간은 상대적인 것입니다.

    아이디어는 파일이 생성되고 변경될 때마다 서버가 ETag라는 특수 태그로 파일에 태그를 지정하고 브라우저에 제공하는 파일(응답)에 헤더를 추가한다는 것입니다.

    E태그: "686897696a7c876b7e"

    이제 브라우저는 현재 버전 파일의 ETag가 "686897696a7c876b7e"와 동일하다는 것을 알고 있습니다. 다음에 브라우저에 동일한 파일이 필요할 때 If-None-Match 헤더 "686897696a7c876b7e" 와 함께 요청을 보냅니다.

    일치하지 않는 경우: "686897696a7c876b7e"

    서버는 레이블을 비교할 수 있으며, 파일이 수정되지 않은 경우 304(수정되지 않음) 상태로 브라우저에 빈 응답을 보냅니다. Last-modified와 마찬가지로 브라우저는 파일이 업데이트되지 않았음을 파악하고 캐시의 복사본을 표시할 수 있습니다.

    타이틀이 만료되었습니다

    이 헤더의 작동 원리는 위에서 설명한 Etag 및 Last-modified와 다릅니다. Expired를 사용하면 파일의 "만료 날짜"("관련 기간")가 결정됩니다. 저것들. 처음 로드할 때 서버는 만료됨에 지정된 날짜까지 파일을 변경할 계획이 없다는 것을 브라우저에 알립니다.

    다음번에는 "만료 날짜"가 아직 도착하지 않았다는 것을 알고 있는 브라우저는 서버에 요청을 시도조차 하지 않고 캐시에 있는 파일을 표시합니다.

    이러한 유형의 캐시는 특히 기사, 아이콘, 파비콘, 일부 CSS 및 js 파일 등에 대한 일러스트레이션과 관련이 있습니다.

    max-age 지시어가 포함된 캐시 제어 헤더입니다.

    캐시 제어의 작동 원리: max-age는 Expired와 매우 유사합니다. 여기에서 파일의 "만료 날짜"도 결정되지만 초 단위로 설정되며 특정 시간에 얽매이지 않으므로 대부분의 경우 훨씬 편리합니다.

    참고로:

    • 1일 = 86400초
    • 1주 = 604800초
    • 1개월 = 2629000초
    • 1년 = 31536000초

    예:

    캐시 제어: 최대 연령=2629000;

    Cache-control 헤더에는 max-age 외에 다른 지시어가 있습니다. 가장 인기있는 것을 간단히 살펴 보겠습니다.

    공공의
    사실 요청은 사용자의 최종 클라이언트(브라우저)뿐만 아니라 다양한 중간 프록시, CDN 네트워크 등에 의해서도 캐시될 수 있습니다. 따라서 public 지시문을 사용하면 모든 프록시 서버가 브라우저처럼 캐싱을 수행할 수 있습니다.

    사적인
    지시문에는 다음과 같이 명시되어 있습니다. 이 파일(서버 응답)은 최종 사용자에 따라 다르며 다양한 중간 프록시에 의해 캐시되어서는 안 됩니다. 동시에 최종 클라이언트(사용자의 브라우저)에 대한 캐싱을 허용합니다. 예를 들어 이는 내부 사용자 프로필 페이지, 세션 내 요청 등과 관련이 있습니다.

    클라이언트가 매번 서버에 요청하도록 지정할 수 있습니다. 위에서 설명한 Etag 헤더와 함께 사용되는 경우도 있습니다.

    무점포
    어떤 상황에서도 요청의 사본이나 요청의 일부를 보관해서는 안 된다는 점을 클라이언트에게 지시합니다. 이는 모든 캐시를 재정의하는 가장 엄격한 헤더입니다. 이는 기밀 정보 작업을 위해 특별히 고안되었습니다.

    재검증 필수
    이 지시어는 콘텐츠를 다시 검증하기 위해 서버에 필수 요청을 하도록 브라우저에 지시합니다(예: eTag를 사용하는 경우). 사실 특정 구성의 http를 사용하면 캐시가 이미 오래된 콘텐츠를 저장할 수 있습니다. must-revalidate는 브라우저가 서버에 요청하여 어떤 상황에서도 콘텐츠의 최신성을 확인하도록 의무화합니다.

    프록시 재검증
    이는 필수 재검증과 동일하지만 캐싱 프록시에만 적용됩니다.

    s-최대
    이 지시문은 다른 프록시의 캐시에만 고려되고 사용자 브라우저 자체에서는 고려되지 않는다는 점을 제외하면 max-age 와 실질적으로 다르지 않습니다. 문자 “s -”는 “shared”라는 단어에서 유래되었습니다(예: CDN). 이 지시어는 CDN 및 기타 중간 캐시를 위해 특별히 고안되었습니다. 이를 지정하면 max-age 지시어와 Expired 헤더의 값이 무시됩니다. 그러나 CDN 네트워크를 구축하지 않는 경우에는 s-maxage가 필요하지 않을 것입니다.

    사이트에서 어떤 헤더가 사용되는지 어떻게 확인할 수 있나요?

    즐겨 사용하는 브라우저의 디버거에서 http 요청 헤더와 응답 헤더를 볼 수 있습니다. Chrome에서 표시되는 예는 다음과 같습니다.

    자존심이 강한 브라우저나 http 스니퍼에서도 똑같은 것을 볼 수 있습니다.

    Apache 및 Nginx에서 캐싱 설정

    널리 사용되는 서버 설정에 대한 문서는 다시 설명하지 않습니다. 당신은 항상 그것을 볼 수 있습니다. 아래에서는 구성 파일의 모양을 보여주는 몇 가지 실제 예를 제공합니다.

    아파치 구성만료를 제어하기 위해

    우리는 다른 "만료일"을 설정합니다. 다양한 방식파일. 이미지의 경우 1년, 스크립트, 스타일, PDF 및 아이콘의 경우 1개월입니다. 그 외의 경우 – 2일.

    ExpiresActive On ExpiresByType image/jpg "액세스 + 1년" ExpiresByType image/jpeg "액세스 + 1년" ExpiresByType image/gif "액세스 + 1년" ExpiresByType image/png "액세스 + 1년" ExpiresByType text/css "액세스 + 1 월" ExpiresByType application/pdf "액세스 + 1개월" ExpiresByType text/x-javascript "액세스 + 1개월" ExpiresByType image/x-icon "액세스 + 1년" ExpiresDefault "액세스 + 2일"

    만료를 제어하는 ​​Nginx 구성 예

    우리는 다양한 유형의 파일에 대해 서로 다른 "만료일"을 설정합니다. 이미지는 일주일, 스타일과 스크립트는 하루.

    서버( #... 위치 ~* \.(gif|ico|jpe?g|png)(\?+)?$( 1w 만료; ) 위치 ~* \.(css|js)$( 1d 만료; ) #... )

    Cache-control에 대한 Apache 구성 예(max-age 및 public/private/no-cache) 헤더 세트 Cache-Control "max-age=2592000, public" 헤더 세트 Cache-Control "max-age=88000, private, must- revalidate" 헤더 세트 Cache-Control "private, no-store, no-cache, must-revalidate, no-transform, max-age=0" 헤더 세트 Pragma "no-cache" 캐시 제어 정적 파일 서버에 대한 Nginx 구성 예 ( #... 위치 ~* \.(?:ico|css|js|gif|jpe?g|png)$ ( add_header Cache-Control "max-age=88000, public"; ) #... ) In 결론

    “캐시할 수 있는 모든 것을 캐시하라”는 웹 개발자의 좋은 모토입니다. 때로는 구성에 몇 시간만 투자하는 동시에 사이트의 사용자 경험을 크게 향상시키고 서버 부하를 크게 줄이고 트래픽을 절약할 수 있습니다. 가장 중요한 것은 리소스의 특성을 고려하여 과용하지 않고 모든 것을 올바르게 설정하는 것입니다.

    적절하게 구성된 캐싱은 엄청난 성능 이점을 제공하고 대역폭을 절약하며 서버 비용을 절감하지만, 많은 사이트에서 캐싱을 제대로 구현하지 않아 상호 연결된 리소스가 동기화되지 않는 경쟁 조건이 발생합니다.

    압도적 인 대다수 모범 사례캐싱은 다음 두 가지 패턴 중 하나를 나타냅니다.

    패턴 1: 변경할 수 없는 콘텐츠 및 긴 max-age 캐시 Cache-Control: max-age=31536000
    • URL의 내용은 변경되지 않으므로...
    • 브라우저나 CDN은 1년 동안 리소스를 쉽게 캐시할 수 있습니다.
    • 지정된 max-age보다 어린 캐시 콘텐츠는 서버에 문의하지 않고도 사용할 수 있습니다.

    페이지: 안녕하세요, "/script-v1.js" , "/styles-v1.css" 및 "/cats-v1.jpg"가 필요합니다 10:24

    캐시: 저는 비어있습니다. 서버님, 당신은 어떻습니까? 10:24

    서버: 네, 여기 있습니다. 그건 그렇고, 현금은 1년 동안 사용해야 합니다. 10:25

    현금: 감사합니다! 10:25

    페이지: 만세! 10:25

    다음날

    페이지: 안녕하세요, "/script-v2 .js" , "/styles-v2 .css" 및 "/cats-v1.jpg"가 필요합니다 08:14

    캐시: 고양이 사진이 있는데 나머지는 없어요. 섬기는 사람? 08:14

    서버: 쉬움 - 여기에 새로운 CSS & JS가 있습니다. 다시 한 번 말씀드리지만, 캐시의 유통기한은 1년을 넘지 않습니다. 08:15

    현금: 좋아요! 08:15

    페이지: 감사합니다! 08:15

    Cash: 흠, 저는 꽤 오랫동안 "/script-v1.js"와 "/styles-v1.css"를 사용하지 않았습니다. 이제 제거할 시간입니다. 12:32

    이 패턴을 사용하면 특정 URL의 내용을 변경하지 않고 URL 자체를 변경합니다.

    모든 URL에는 콘텐츠에 따라 변경되는 내용이 있습니다. 이는 버전 번호, 수정된 날짜 또는 콘텐츠 해시(내 블로그에서 선택한 것)일 수 있습니다.

    대부분의 서버 측 프레임워크에는 이와 같은 작업을 쉽게 수행할 수 있는 도구가 있습니다(Django에서는 Manifest​Static​Files​Storage를 사용합니다). Node.js에는 동일한 문제를 해결하는 매우 작은 라이브러리(예: gulp-rev)도 있습니다.

    하지만 이 패턴은 기사나 블로그 게시물 같은 것에는 적합하지 않습니다. 해당 URL은 버전을 관리할 수 없으며 내용이 변경될 수 있습니다. 진지하게, 나는 종종 문법 및 구두점 오류가 있어서 콘텐츠를 빠르게 업데이트할 수 있어야 합니다.

    패턴 #2: 서버에서 항상 검증되는 변경 가능한 콘텐츠 캐시 제어: 캐시 없음
    • URL의 내용이 변경됩니다. 즉...
    • 서버를 지정하지 않으면 로컬로 캐시된 버전을 사용할 수 없습니다.

    페이지: 안녕하세요, "/about/" 및 "/sw.js"의 내용이 필요합니다 11:32

    현금: 나는 당신을 도울 수 없습니다. 섬기는 사람? 11:32

    서버: 몇 가지가 있습니다. 현금은 가지고 다니되 사용하기 전에 꼭 물어보세요. 11:33

    현금: 정확해요! 11:33

    페이지: 감사합니다! 11:33

    다음날

    페이지: 안녕하세요, "/about/" 및 "/sw.js"의 내용이 다시 필요합니다 09:46

    현금: 잠시만요. 서버님, 제 사본은 괜찮나요? "/about/"의 복사본은 월요일의 복사본이고 "/sw.js"의 복사본은 어제의 복사본입니다. 09:46

    서버: "/sw.js"가 변경되지 않았습니다... 09:47

    현금: 좋습니다. 페이지에서는 "/sw.js"를 유지하세요. 09:47

    서버: ...하지만 "/about/"이 있습니다. 새로운 버전. 현금은 갖고 계시되 지난번처럼 나에게 먼저 물어보는 것 잊지 마세요. 09:47

    현금: 알았어요! 09:47

    페이지: 좋아요! 09:47

    참고: no-cache는 "캐시하지 않음"을 의미하는 것이 아니라 서버에 캐시된 리소스를 "확인"(또는 재검증)하는 것을 의미합니다. 그리고 no-store는 브라우저에 전혀 캐시하지 말라고 지시합니다. 또한 must-revalidate는 필수 재검증을 의미하는 것이 아니라 캐시된 리소스가 지정된 max-age보다 어린 경우에만 사용되며 그렇지 않은 경우에만 재검증됩니다. 모든 일은 이렇게 시작됐어 키워드캐싱용.

    이 패턴에서는 ETag(원하는 버전 ID) 또는 Last-Modified 헤더를 응답에 추가할 수 있습니다. 다음에 클라이언트가 콘텐츠를 요청할 때 If-None-Match 또는 If-Modified-Since가 각각 출력되어 서버가 "현재 가지고 있는 것을 사용하세요. 캐시가 최신 상태입니다"라고 말할 수 있습니다. 즉, HTTP 304를 반환합니다.

    ETag/Last-Modified 전송이 불가능한 경우 서버는 항상 전체 내용을 전송합니다.

    이 패턴에는 항상 네트워크 호출이 필요하므로 네트워크 요청 없이 수행할 수 있는 첫 번째 패턴만큼 좋지 않습니다.

    첫 번째 패턴에 대한 인프라가 없는 것은 드문 일이 아니지만 패턴 2의 네트워크 요청에 문제가 발생할 수도 있으므로 중간 옵션인 짧은 max-age 및 변경 가능한 콘텐츠가 사용됩니다. 이것은 나쁜 타협입니다.

    변경 가능한 콘텐츠에 max-age를 사용하는 것은 일반적으로 잘못된 선택입니다.

    불행하게도 이는 일반적이며 Github 페이지가 그 예입니다.

    상상하다:

    • /기사/
    • /styles.css
    • /script.js

    서버 헤더 포함:

    캐시 제어: 재검증 필수, 최대 연령=600

    • URL 콘텐츠 변경
    • 브라우저에 캐시된 버전이 10분 이상인 경우 서버에 문의하지 않고 사용됩니다.
    • 그러한 캐시가 없으면 가능한 경우 If-Modified-Since 또는 If-None-Match를 사용하여 네트워크 요청이 사용됩니다.

    페이지: 안녕하세요, "/article/", "/script.js" 및 "/styles.css"가 필요합니다 10:21

    캐쉬: 나한테는 너 같은 게 없어, 서버? 10:21

    서버: 문제 없습니다. 여기 있습니다. 하지만 기억하세요. 현금은 10분 이내에 사용할 수 있습니다. 10:22

    현금: 네! 10:22

    페이지: 감사합니다! 10:22

    페이지: 안녕하세요, "/article/" , "/script.js" 및 "/styles.css" 가 다시 필요합니다 10:28

    Cash: 이런, 죄송합니다. "/styles.css"를 잃어버렸지만 나머지는 모두 갖고 있습니다. 여기 있습니다. 서버님, "/styles.css"를 맞춤설정해 주실 수 있나요? 10:28

    서버: 진정하세요. 지난번에 데려갔을 때보다 그 사람은 이미 변했어요. 앞으로 10분 동안은 안전하게 사용하실 수 있습니다. 10:29

    현금: 문제없습니다. 10:29

    페이지: 감사합니다! 그런데 뭔가 잘못된 것 같아요! 모든 것이 깨졌습니다! 무슨 일이야? 10:29

    이 패턴은 테스트 중에 생명권을 가지지만 실제 프로젝트에서는 모든 것을 깨뜨리고 추적하기가 매우 어렵습니다. 위의 예에서 서버는 HTML, CSS 및 JS를 업데이트했지만 페이지는 이전에 캐시된 HTML 및 JS와 함께 서버에서 업데이트된 CSS로 렌더링됩니다. 버전 불일치로 인해 모든 것이 망가집니다.

    HTML을 크게 변경할 때 새로운 구조를 적절하게 반영하기 위해 CSS를 변경하고 콘텐츠와 스타일을 유지하기 위해 JavaScript를 모두 변경하는 경우가 많습니다. 이러한 리소스는 모두 독립적이지만 캐싱 헤더는 이를 표현할 수 없습니다. 결과적으로 사용자는 자신을 찾을 수 있습니다. 최신 버전하나/두 개의 리소스와 나머지는 이전 버전입니다.

    max-age는 응답 시간을 기준으로 설정되므로 모든 리소스가 단일 주소의 일부로 전송되면 동시에 만료되지만 여전히 비동기화 가능성이 적습니다. JavaScript가 포함되지 않거나 다른 스타일이 포함된 페이지가 있는 경우 캐시 만료 날짜가 동기화되지 않습니다. 더 나쁜 것은 브라우저가 HTML, CSS, JS가 상호 의존적이라는 사실을 모르고 캐시에서 지속적으로 콘텐츠를 가져오기 때문에 목록에서 한 가지만 가져오고 나머지는 모두 잊어버릴 수 있다는 것입니다. 이러한 모든 요소를 ​​함께 고려하면 버전이 일치하지 않을 가능성이 상당히 높다는 것을 이해해야 합니다.

    사용자의 경우 페이지 레이아웃이 깨지거나 기타 문제가 발생할 수 있습니다. 작은 결함부터 완전히 사용할 수 없는 콘텐츠까지.

    다행스럽게도 사용자에게는 비상구가 있습니다 ...

    페이지를 새로 고치는 것이 도움이 되는 경우가 있습니다.

    새로 고침으로 페이지가 로드되면 브라우저는 항상 max-age 를 무시하고 서버 측 재검증을 수행합니다. 따라서 사용자에게 max-age로 인해 문제가 발생한 경우 간단한 페이지 새로 고침으로 모든 문제를 해결할 수 있습니다. 그러나 물론 숟가락을 찾은 후에도 침전물은 여전히 ​​남아 있으며 사이트에 대한 태도는 다소 달라질 것입니다.

    서비스 작업자는 이러한 버그의 수명을 연장할 수 있습니다.

    예를 들어 다음과 같은 서비스 워커가 있습니다.

    Const 버전 = "2"; self.addEventListener("install", event => ( event.waitUntil(caches.open(`static-$(version)`) .then(cache => 캐시.addAll([ "/styles.css", "/script .js" ]))); )); self.addEventListener("activate", event => ( // ...오래된 캐시 삭제... )); self.addEventListener("fetch", event => ( event.respondWith(caches.match(event.request) .then(response => response || fetch(event.request))); ));

    이 서비스 워커는:

    • 스크립트와 스타일을 캐시합니다.
    • 일치하는 항목이 있으면 캐시를 사용하고, 그렇지 않으면 네트워크에 액세스합니다.

    CSS/JS를 변경하면 버전 번호도 늘어나 업데이트가 실행됩니다. 그러나 addAll이 캐시에 먼저 액세스하기 때문에 max-age 및 일치하지 않는 CSS 및 JS 버전으로 인해 경쟁 조건이 발생할 수 있습니다.

    일단 캐시되면 다음 서비스 워커가 업데이트될 때까지 CSS와 JS가 호환되지 않습니다. 업데이트 중에 다시 경쟁 조건이 발생하지 않는 한 그렇습니다.

    서비스 워커에서 캐싱을 건너뛸 수 있습니다.

    Self.addEventListener("설치", 이벤트 => ( event.waitUntil(caches.open(`static-$(version)`) .then(cache => 캐시.addAll([ new Request("/styles.css", ( 캐시: "캐시 없음" )), new Request("/script.js", ( 캐시: "캐시 없음" )) ]))); ));

    불행하게도 캐싱 옵션은 Chrome/Opera에서 지원되지 않으며 방금 Firefox의 nightly 빌드에 추가되었지만 직접 수행할 수 있습니다.

    Self.addEventListener("install", event => ( event.waitUntil(caches.open(`static-$(version)`) .then(cache => Promise.all([ "/styles.css", "/script .js" ].map(url => ( // 무작위 쿼리 문자열을 사용하여 캐시 버스트 return fetch(`$(url)?$(Math.random())`).then(response => ( // 실패 404, 500 등 if (!response.ok) throw Error("Not ok"); return 캐시.put(url, response); )) ))))); ));

    이 예에서는 임의의 숫자를 사용하여 캐시를 재설정하지만 더 나아가 빌드할 때 콘텐츠의 해시를 추가할 수 있습니다(이는 sw-precache가 수행하는 작업과 유사합니다). 이는 JavaScript를 사용한 일종의 첫 번째 패턴 구현이지만 서비스 워커에서만 작동하고 브라우저 및 CDN에서는 작동하지 않습니다.

    서비스 워커와 HTTP 캐시는 함께 잘 작동합니다. 서로 싸우게 만들지 마세요!

    보시다시피 서비스 워커의 캐싱 버그를 해결할 수 있지만 문제의 근본 원인을 해결하는 것이 더 좋습니다. 올바른 설정캐싱을 사용하면 서비스 작업자의 작업이 더 쉬워질 뿐만 아니라 서비스 작업자를 지원하지 않는 브라우저(Safari, IE/Edge)에도 도움이 되며 CDN을 최대한 활용할 수 있습니다.

    적절한 캐싱 헤더를 사용하면 서비스 워커 업데이트가 훨씬 쉬워질 수도 있습니다.

    Const 버전 = "23"; self.addEventListener("install", event => ( event.waitUntil(caches.open(`static-$(version)`) .then(cache => 캐시.addAll([ "/", "/script-f93bca2c. js", "/styles-a837cb1e.css", "/cats-0e9a2ef4.jpg" ]))); ));

    여기서는 패턴 #2(서버 측 재검증)를 사용하여 루트 페이지를 캐시하고 패턴 #1(변경할 수 없는 콘텐츠)을 사용하여 다른 모든 리소스를 캐시했습니다. 서비스 워커를 업데이트할 때마다 루트 페이지에 대한 요청이 발생하며, 다른 모든 리소스는 해당 URL이 변경된 경우에만 로드됩니다. 이는 이전 버전에서 업그레이드하든 매우 많이 업그레이드하든 상관없이 트래픽을 절약하고 성능을 향상시키기 때문에 좋습니다. 구 버전.

    작은 변경에도 불구하고 전체 바이너리가 다운로드되거나 복잡한 비교가 발생하는 경우 기본 구현에 비해 상당한 이점이 있습니다. 바이너리 파일. 이런 방식으로 상대적으로 작은 로드로 대규모 웹 애플리케이션을 업데이트할 수 있습니다.

    서비스 작업자는 일시적인 버팀목이 아닌 개선 기능으로 더 잘 작동하므로 캐시와 싸우는 대신 캐시를 사용하여 작업하십시오.

    주의 깊게 사용하면 최대 연령 및 가변 콘텐츠가 매우 좋을 수 있습니다.

    max-age는 변경 가능한 콘텐츠에 대해 잘못된 선택인 경우가 많지만 항상 그런 것은 아닙니다. 예를 들어 원본 기사의 최대 수명은 3분입니다. 동일한 캐싱 패턴(CSS, JS 및 이미지는 패턴 #1 - 불변 콘텐츠 사용)을 사용하는 페이지에 종속성이 없기 때문에 경쟁 조건은 문제가 되지 않으며, 다른 모든 항목에서는 이 패턴을 사용하지 않습니다.

    이 패턴은 업데이트된 기사가 제공될 때까지 3분만 기다리면 인기 있는 기사를 편안하게 작성할 수 있고 CDN(Cloudflare)이 서버의 부하를 줄일 수 있음을 의미합니다. 사용자가 접근 가능.

    이 패턴은 광신주의 없이 사용되어야 합니다. 기사에 새 섹션을 추가하고 다른 기사에서 해당 섹션에 연결하면 해결해야 하는 종속성이 생성됩니다. 사용자는 링크를 클릭하면 원하는 섹션이 없는 기사의 사본을 얻을 수 있습니다. 이를 방지하려면 기사를 새로 고치고, Cloudflare에서 기사의 캐시된 버전을 삭제하고, 3분 정도 기다린 후 다른 기사에 대한 링크를 추가해야 합니다. 예, 이 패턴에는 주의가 필요합니다.

    캐싱을 올바르게 사용하면 성능이 크게 향상되고 대역폭이 절약됩니다. URL을 쉽게 변경할 수 있거나 서버 측 재검증을 사용할 수 있는 경우 변경 불가능한 콘텐츠를 제공하세요. 콘텐츠에 동기화되지 않을 수 있는 종속성이 없다고 확신할 만큼 용감하고 확신이 있다면 최대 수명과 변경 가능한 콘텐츠를 혼합하세요.

    웹사이트를 변경할 때 페이지, CSS 파일 및 스크립트(.js)의 내용이 브라우저에 의해 캐시되고 꽤 오랫동안 변경되지 않은 상태로 유지된다는 사실을 종종 접하게 됩니다. 이는 변경 사항이 모든 브라우저에 반영되기 위해서는 클라이언트가 F5 또는 Ctrl + F5의 복잡한 조합에 익숙해져야 한다는 사실로 이어집니다. 그리고 수시로 눌려져 있는지 확인하십시오.

    그 과정이 꽤 지루하고 불편합니다. 물론 매번 파일 이름을 바꾸면 상황에서 벗어날 수 있지만 다시 불편합니다.

    그러나 동일한 이름을 유지하고 필요한 순간에 .css 또는 .js 파일의 캐싱을 재설정할 수 있는 방법이 있습니다. 그리고 Ctrl + F5는 영원히 잊어버리세요.

    결론은 마지막에 의사 매개변수를 .css 또는 .js 파일에 첨부하고 수시로 변경하여 브라우저에서 캐시를 재설정한다는 것입니다.

    따라서, 소스 코드이제 다음과 같이 보일 것입니다:

    여기서 186485는 동일한 파일을 출력하는 임의의 조합이지만 의사 매개변수 덕분에 브라우저는 이를 새로운 것으로 해석합니다. ?186485

    이제 매번 매개변수의 모든 항목을 변경하지 않기 위해 이를 PHP 파일에 설정하고 필요한 모든 위치에 연결합니다.