一.분류
캐시의 강력함에 따라 분류:
-
강력 캐시: 유효 기간 내에는 리소스를 직접 로컬 캐시 (disk cache 또는 memory cache) 에서 가져옴; 유효 기간 외 또는 강제 새로고침 시 server 에서 다시 요청
-
협상 캐시: 유효 기간 내에는 위와 동일; 유효 기간 외 또는 강제 새로고침 시 로컬 버전 번호를 가지고 server 에 리소스 업데이트 여부 문의,
304(캐시 만료 시간 등 캐시 상태 업데이트, 로컬 버전 계속 사용) 또는200(새 버전 캐시, 로컬 버전 폐기) 응답 받음
협상 캐시는 세분화 가능:
-
시간 기반: 리소스 수정 시간 (Last-Modified) 을 버전 번호로 함
-
콘텐츠 기반: 리소스 내용 hash(ETag) 를 버전 번호로 함
협상은 캐시가 무효화 (만료 또는 폐기) 된 후 에야 발생하는 일
二.관련 Header 필드
HTTP Header 필드는 4 가지 유형으로 분류:
-
general-header(일반 헤더): 요청 및 응답 메시지 모두에 적용
-
request-header(요청 헤더): client 가 server 에 추가 정보 전송 가능, 요청 수식자, 매개변수와 유사한 역할
-
response-header(응답 헤더): server 가 client 에게 이 응답에 대한 추가 정보 전송 가능, server 관련 정보 및 향후 이 리소스 액세스에 필요한 일부 정보 포함
-
entity-header(엔티티 헤더): 메시지 엔티티 관련 meta 정보 제공, 메시지 엔티티가 없는 경우 요청에 해당하는 리소스 정보
P.S.HTTP Header 에 대한 자세한 정보는 4.2 Message Headers 참조
Pragma
HTTP 1.0 일반 헤더 필드, 캐시 정책 지정
Pragma = "Pragma" ":" 1#pragma-directive
pragma-directive = "no-cache" | extension-pragma
extension-pragma = token [ "=" ( token | quoted-string ) ]
Pragma 는 의미가 모호한 필드로, RFC 는 Pragma: no-cache 가 요청에 나타날 때 캐시가 유효하더라도 origin 에서 새로운 것을 가져와야 한다고만 지정. 응답에 나타날 때는 명확한 의미 없음
P.S.Pragma 에 대한 자세한 정보는 14.32 Pragma 참조
Expires
HTTP 1.0 엔티티 헤더 필드, 리소스 만료 시간을 나타내며 만료 정책 지정
Expires = "Expires" ":" HTTP-date
정확한 시점으로, 그 이전에는 캐시 유효. 이 시점은 server 가 제공하며, client 와 server 의 시간이 동기화되지 않으면 캐시 만료 정책이 신뢰할 수 없게 됨
Expires 가 나타내는 시점이 client 와 server 에서 동일한 시점에 대응함을 보장할 수 없으므로, HTTP 1.1 에서는 Cache-Control: max-age=<seconds> 를 통해 유효 기간 정의 가능. 시간 간격을 제공하여 client 가 리소스를 받은 후 seconds 초 후에 캐시 만료. 이렇게 하면 client 시간에만 의존하며 일관성을 요구하지 않음
Cache-Control
일반 헤더 필드, 캐시 정책과 만료 정책 지정
Cache-Control = "Cache-Control" ":" 1#cache-directive
cache-directive = cache-request-directive
| cache-response-directive
cache-extension = token [ "=" ( token | quoted-string ) ]
응답 헤더에는 9 개의 값이 나타날 수 있음:
cache-response-directive =
; 리소스는 클라이언트와 프록시 서버에 의해 캐시됨
"public"
; 리소스는 클라이언트만 캐시하며, 프록시 서버 캐시 허용 안 함
| "private" [ "=" <"> 1#field-name <"> ]
; origin 재확인 없이는 리소스 재사용 허용 안 함
| "no-cache" [ "=" <"> 1#field-name <"> ]
; 리소스 캐시 쓰기 허용 안 함
| "no-store"
; 프록시 서버의 Content-Encoding, Content-Range, Content-Type 필드 수정 금지
| "no-transform"
; 만료된 리소스 사용 허용 안 함, 일단 만료되면 origin 검증 필수 (클라이언트가 만료된 리소스를 받아들이더라도)
| "must-revalidate"
; public 에 의존, must-revalidate 와 유사, 프록시 서버에만 적용
| "proxy-revalidate"
; 리소스 캐시하되 지정 시간 (초 단위) 후 캐시 만료
| "max-age" "=" delta-seconds
; public 에 의존, 프록시 서버에서만 유효, max-age 오버라이드
| "s-maxage" "=" delta-seconds
; 사용자 정의 확장 값
| cache-extension
요청 헤더에는 7 개의 값이 나타날 수 있음:
cache-request-directive =
; 강제 origin, 캐시의 콘텐츠 불필요
"no-cache"
; 클라이언트 요청 관련 정보를 캐시에 쓰는 것 허용 안 함
| "no-store"
; 클라이언트는 age(프록시 서버 캐시 시간) 가 delta 초를 초과하지 않는 리소스를 받아들임
| "max-age" "=" delta-seconds
; 클라이언트는 delta 초 이내의 만료된 구버전 콘텐츠를 받아들임
| "max-stale" [ "=" delta-seconds ]
; 클라이언트는 응답 콘텐츠가 delta 초 내에 유효하기를 희망
| "min-fresh" "=" delta-seconds
; 클라이언트는 변환된 콘텐츠 (예: Content-Type) 를 받아들이지 않음
| "no-transform"
; 클라이언트는 캐시된 리소스만 원함, 리소스를 재요청하지 않음
| "only-if-cached"
; 사용자 정의 확장 값
| cache-extension
no-store, no-cache, must-revalidate 설명 간의 미묘한 차이에 주의, 동일 필드가 요청 헤더와 응답 헤더에 나타날 때의 의미도 다름
Last-Modified
엔티티 헤더 필드, 리소스의 최종 수정 시간을 나타내며 협상 정책 지정
Last-Modified = "Last-Modified" ":" HTTP-date
클라이언트는 받은 후 저장하고, 다음에 server 에 리소스를 요청할 때 이 시점을 버전 번호로 첨부하여 로컬 캐시 리소스가 계속 사용 가능한지 검증
리소스가 수정되었지만 내용이 변하지 않은 경우 내용이 동일한 응답을 보내는 것은 불필요하므로, 콘텐츠 기반의 협상 캐시도 제공되어 이러한 상황을 방지
P.S.Cache-Control: max-age 보다 우선순위가 낮으며, 동시에 나타날 때는 max-age 가 우선
If-Modified-Since
요청 헤더 필드, 시간 기반 협상 정책 구현에 필요, 리소스 최종 수정 시간 (Last-Modified, 리소스 최종 수정 시간) 이 일치하는지 비교
If-Modified-Since = "If-Modified-Since" ":" HTTP-date
Last-Modified 버전 번호를 필드 값으로 server 에 전송, 리소스가 업데이트되지 않았으면 304 반환하고 응답 본문 없음, 업데이트되었으면 200 반환하고 새 버전 콘텐츠를 응답 본문으로 전송
If-Unmodified-Since
위와 동일하지만 동작은 반대 (리소스 최종 수정 시간이 일치하지 않는지 비교), 일치하지 않고 method 가 POST/PUT 등의 업데이트 작업일 때 412 반환(Precondition Failed, 조건 불만족) 업데이트 실행 실패를 나타냄
ETag
응답 헤더 필드, 리소스의 내용 hash 를 나타내며 협상 정책 지정
ETag = "ETag" ":" entity-tag
클라이언트는 이 값을 기록하고, 다음에 이 리소스를 요청할 때 버전 번호로 server 에 전송
P.S.ETag 의 우선순위가 Last-Modified 보다 높음
If-Match
요청 헤더 필드, 콘텐츠 기반 협상 정책 구현에 필요, 이 필드의 값 (ETag, 리소스 내용 hash) 이 일치하는지 비교
If-Match = "If-Match" ":" ( "*" | 1#entity-tag )
일치하지 않고 method 가 POST/PUT 등의 업데이트 작업일 때 412 반환하여 업데이트 실패를 나타냄
If-None-Match
위와 동일하지만 동작은 반대 (이 필드의 값이 일치하지 않는지 비교), 일치하면 304 반환하여 클라이언트에게 캐시 버전 계속 사용 가능함을 알림, 아니면 새 리소스 반환
Age
응답 헤더 필드, 리소스가 프록시 서버에서 캐시된 시간을 나타냄
Age = "Age" ":" age-value
age-value = delta-seconds
계산 방식:
/*
* age_value
* is the value of Age: header received by the cache with
* this response.
* date_value
* is the value of the origin server's Date: header
* request_time
* is the (local) time when the cache made the request
* that resulted in this cached response
* response_time
* is the (local) time when the cache received the
* response
* now
* is the current (local) time
*/
apparent_age = max(0, response_time - date_value);
corrected_received_age = max(apparent_age, age_value);
response_delay = response_time - request_time;
corrected_initial_age = corrected_received_age + response_delay;
resident_time = now - response_time;
current_age = corrected_initial_age + resident_time;
Age:0 은 방금 origin server 에서 가져왔음을 나타내고, 양수 값은上次 origin 에서 가져온 후 현재까지 경과한 초수를 나타냄
三.강력 캐시와 협상 캐시
각각 캐시의 다른 단계에서 발생, 캐시 유효 시 강력 캐시, 요청 보내지 않음, 캐시 무효 후 협상 캐시, 요청 보내 리소스 업데이트 여부 문의
강력 캐시
응답 콘텐츠가 강력 캐시에 적중 후 캐시 유효 기간 내 브라우저는 server 에 요청을 시작하지 않고 직접 로컬 캐시 (disk cache 또는 memory cache) 에서 읽음
로컬에 이 리소스의 캐시 버전이 있고 Cache-Control: max-age 또는 Expires 가 만료되지 않았으면 강력 캐시에 적중
협상 캐시
캐시 만료 후 이 리소스에 다시 액세스할 때 브라우저는 로컬 캐시 버전 번호를 첨부하여 server 에 문의, server 는 클라이언트가 보낸 ETag 또는 Last-Modified 값을 검사하여 클라이언트에게 캐시를 업데이트할지 말지 알림
응답 헤더의 ETag 와 Last-Modified 는 협상 캐시의 스위치, 협상 캐시의 장점은 내용이 변하지 않았으면 직접 304 반환, 응답 본문 전송 불필요
四.휴리스틱 캐시
비교적 특수한 상황은 응답 헤더가 캐시 관련 정보를 전혀 제공하지 않는 경우, 이때 브라우저는 휴리스틱 알고리즘을 사용하여 리소스 캐시 기간 결정:
max-age = Date - Last-Modified / 10
기본 캐시 정책, 휴리스틱 캐시라고 함, 휴리스틱이란 경험에 기반하여 구축된 것으로 엄격한 근거는 없음
五.새로고침 동작
브라우저에는 3 가지 다른 새로고침 동작이 있으며, HTTP 캐시를 검증할 때 혼동하기 쉬움:
-
새 페이지 열기: 새 탭 또는 창을 열고 페이지 액세스
-
일반 새로고침: 새로고침 버튼 클릭, 주소창 Enter,
CMD + R -
강제 새로고침:
CMD + Shift + R, Chrome 에서 새로고침 버튼 길게 누르기, 하드 다시 로드 선택 -
캐시 비활성화 후 새로고침:
Disable cache설정 체크 후 새 페이지/새로고침
새 페이지 열기
요청 헤더에 캐시 관련 필드를 첨부하지 않음, 로컬 캐시 버전이 유효하면 캐시에서 읽고 요청을 보내지 않으며 가짜 요청 헤더 표시:
Request Headers
Provisional headers are shown
Upgrade-Insecure-Requests:1
User-Agent:...
응답 헤더는 캐시의那份 계속 사용
일반 새로고침
캐시에서 가져오지 않고 반드시 서비스에 요청을 시작함, 요청 헤더에 If-Modified-Since 와 If-None-Match 등의 캐시 헤더 (있는 경우) 를 첨부하며, 추가로 임의로 추가:
Cache-Control:max-age=0
프록시 서버에 캐시가 만료되었는지 확인 요청
P.S.일반 새로고침 동작이 발생할 때 브라우저는 반드시 요청을 시작함, 리소스 캐시가 여전히 유효하여 강력 캐시 상태여야 함에도. 사용자가 콘텐츠 새로고침을 요구하고 새로운 것을 보기를 원하며, 관련 리소스 (예: 이 페이지에 포함된 CSS, JS 등의 리소스) 는 강제 요청을 시작하지 않음
강제 새로고침
마찬가지로 강제 요청을 시작하고 캐시 관련 정보를 첨부하며, 추가로 임의로 추가:
Cache-Control:max-age=0
Pragma:no-cache
origin 에서 새로운 것을 가져오도록 요청, 캐시가 만료되지 않았더라도
캐시 비활성화 후 새로고침
캐시 비활성화 후 후속 모든 요청에 추가:
Cache-Control:max-age=0
Pragma:no-cache
모두 강제 새로고침을 실행하는 것과 동일, 관련 리소스 포함
P.S.Cache-Control:max-age=0, Pragma:no-cache 의 구체적 동작은 server 구현에 의존, 실제로 프록시 서버는 반드시 origin 으로 돌아가거나 만료를 확인하지는 않음
참고 자료
-
Hypertext Transfer Protocol -- HTTP/1.1: RFC 2616
-
브라우저 캐시 메커니즘 분석: 캐시 메커니즘 흐름도는 좋음, Header 필드 의미 설명은 부정확
-
HTTP 캐시 제어 소결: 내용은 매우 정확하고 비교적 포괄적
-
What heuristics do browsers use to cache resources not explicitly set to be cachable?
아직 댓글이 없습니다