Post

HTTP - 8 HTTP 헤더 2

해당 자료는 인프런 김영한 선생님의 모든 개발자를 위한 HTTP 웹 기본 지식 강의노트입니다.


1. 캐시 기본 동작

클라이언트에서 ‘GET/star.JPG’ 라는 요청을 서버에 보내면 서버는 star.jpg를 찾아서 응답해줄 것입니다. HTTP 헤더(0.1M 가정) + HTTP 바디(1.0M) = 1.1M 정도의 크기를 포함해서 말이죠.

하지만 캐시가 없는 상태에서 똑같은 요청을 또 한다면 또 1.1M크기의 데이터를 응답하는 것은 효율적인 상황은 아닙니다.

그렇기에 캐시가 없는 상태에서의 이러한 요청은 사용자에게 느린 경험을 주고 브라우저 로딩 속도 저하로 이어집니다.

만약 캐시가 있다면 첫 번째 요청에 서버는 cache 유효시간을 포함한 헤더를 함께 클라이언트에 응답하여 클라이언트는 유효시간동안 ‘브라우저 캐시’ 라는 곳에 응답 결과를 저장해 놓고 유효시간 동안 똑같은 요청이 오면 캐시에서 꺼내서 쓸 것입니다.

정말 운이 나쁘게도 유효시간이 끝날 때 마다 요청을 보내면 결국 매 번 똑같은 응답을 다시 받아야하는데 어떻게 해결할까요.?


2. 검증 헤더와 조건부 요청1

캐시가 시간을 초과해서 서버에 다시 요청을 보내면 다음과 같은 두 가지 상황이 있습니다.

    1. 서버에서 기존 데이터를 변경하여 새로 받아야함.
    1. 서버에서 기존 데이터가 변경되지 않아서 새로 받을 필요가 없음.

2 번의 경우 클라이언트의 데이터와 서버의 데이터가 같다는 사실을 확인할 수 있는 방법이 필요합니다.

그래서 헤더에 새로운 정보를 추가하게 됩니다.

헤더

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Content-Type: image/jpeg
cache-control: max-age=60
Last-Modified: UTC 날짜 어쩌구 저쩌구 <- 이부분 이 중요합니다.
Content-Length:77777

sdkakldjlajdlkjfklajslkdj
dkasjdlkajdskljdaslk

위의 헤더를 처음 클라이언트가 서버에 요청을 보낼 때 받게 됩니다. 이를 통해 클라이언트는 다음 요청에 캐시에 있는 Last-Modified를 꺼내 서버에 비교 요청을 합니다.

만약 수정일이 같다면 서버는 304 Not Modified라는 응답을 보내며 캐시를 다시 업데이트 하고,HTTP 헤더만 보냅니다. 따라서 HTTP body는 보내지 않기에 클라이언트는 다운로드에 대한 부담이 적어지고 캐시에 있는 데이터를 재활용할 수 있게 됩니다.

참고로 캐시의 만료 와 삭제는 다른 개념으로 캐시가 만료되었다고 해서 삭제되지는 않습니다. 브라우저마다 캐시 삭제 주기가 다르기도 하고 직접 삭제도 가능합니다.


3. 검증 헤더와 조건부 요청2

  • 검증 헤더
    위에서 설명한 Last-Modified 말고 ETag라는 것이 있습니다.

  • 조건부 요청 헤더

    • 검증 헤더로 조건에 따른 분기
    • If-Modified-Since : Last-Modified 사용
    • If-None-Match : ETag 사용.
    • 조건이 만족하면 200 OK
    • 조건이 만족하지 않으면 304 Not Modified

조건부 요청 헤더에서 주의할 점은 부정 구문이라 조건을 만족하면 304(실패)가 떠야한다는 것입니다. If-Modified-Since는 이후에 데이터가 수정되었으면? 이라는 구문인데 데이터가 수정이 되지 않았으면 200 OK가 아닌 304 Not Modified라는 페이지를 반환해야한다는 것이지요.

Last-Modified와 이를 이용한 If-Modified-Since는 단점이 있습니다.

  • 날짜 기반의 로직 사용이라는 점.
  • 데이터를 수정해서 날짜가 다르지만 같은 데이터를 수정해서 데이터 결과가 같은 경우
    • 위의 경우는 a 를 b로 수정했다가 다시 a로 수정하였는데도 데이터를 처음부터 받는 경우를 말합니다.
  • 서버에서 별도의 캐시 로직을 관리하고 싶은 경우
    • 스페이스바, 주석처럼 크게 영향이 없는 변경에서도 캐시를 유지하고 싶은 경우



위의 단점들을 보완하기 위해 ETag, If-None-Match가 탄생하였습니다.

  • ETag(Entity Tag)
  • 캐시용 데이터에 임의의 고유한 버전 이름을 달아둡니다.
    • 예) ETag:”v1.1”, ETag:”kmsblog1”
  • 데이터가 변경되면 이 이름을 바꾸어서 변경합니다.
    • 예) ETag:”v1.1” -> ETag:”v1.2” 등
  • 단순하게 클라이언트가 캐시에 저장된 ETag만 보내서 같으면 유지, 다르면 다시 받게 합니다.

즉 클라이언트 첫 번째 요청에 서버가 ETag를 포함한 헤더를 응답하여 클라이언트 캐시에 저장시킨다음 클라이언트가 다음 요청 시 로컬에 저장된 캐시에서 ETag만 끄집어 내어 ‘If-None-Match’를 통해 같으면 304 Not Modified와 캐시 유효시간을 증가시키고 다르면 데이터를 다시 받습니다.

이를 통해서 캐시 제어 로직을 서버에서 관리할 수 있습니다. 보통 배포 주기에 맞추어 ETag를 모두 갱신합니다.


4. 캐시와 조건부 요청 헤더

캐시 제어 헤더

  • Cache-Control : 캐시 제어
  • Pragma : 캐시 제어 (하위 호환)
  • Expires : 캐시 유효 기간(하위 호환)

Cache-Control

  • Cache-Control : max-age
    • 캐시 유효 시간, 초 단위
  • Cache-Control : no-cache
    • 데이터는 캐시해도 되지만, 항상 origin 서버에서 검증하고 사용
  • Cache-Control : no-store
    • 데이터에 민감한 정보가 있으므로 저장하면 안됨.(메모리에서 사용하고 최대한 빨리 삭제)

Pragma

  • no-cache
  • HTTP 1.0 하위 호환

Expires

  • 캐시 만료일 지정
  • expires : Mon, 01 Jan 2020 00:00:00 GMT
  • HTTP 1.0 부터 사용 했지만, Cache-Control: max-age를 권장.(더 유연) 따라서 같이 사용되면 Expires는 무시 됩니다.

5. 프록시 캐시

만약 한국에서 미국에 있는 원(origin)서버에 바로 접근한다고 하면 시간이 오래 걸릴 것입니다.

그래서 한국 어딘가에 프록시 캐시 서버라는 서버를 두면 이 서버에 접근하여 상대적으로 시간을 줄일 수 있을 것입니다.

위의 그림에서 private, public 캐시가 있습니다.

  • Cache-Control : public
    • 응답이 public 캐시에 저장되어도 됨 (방문 경로 등)
  • Cache-Control : private
    • 응답이 해당 사용자만을 위한 것임.(로그인 정보 등) prvate 캐시에 저장되어야함(기본 값)
  • Cache-Control : s-maxage
    • 프록시 캐시에만 적용되는 max-age
  • Age : 60 (HTTP 헤더)
    • 오리진 서버에서 응답 후 프록시 캐시 내에 머문 시간(초)

6. 캐시 무효화

계좌 금액이나 민감한 정보에 대해 캐시가 들어오지 않게 지정해야 합니다.

확실하게 캐시를 무효화 시키는 방법은 밑의 값들을 전부 넣어주는 것입니다.

  • Cache-Control : no-cache, no-store, must-revalidate
  • Pragma:no-cache (HTTP 1.0 하위 호환)

  • Cache-Control : no-cache
    • 데이터는 캐시해도 되지만, 항상 원 서버에 검증하고 사용.
  • Cache-Control : no-store
    • 데이터에 민감한 정보가 있으므로 저장하면 안됨.
  • Cache-Control : must-revalidate
    • 캐시 만료후 최초 조회시 원 서버에 검증해야함.
    • 원 서버 접근 실패시 반드시 오류가 발생해야함. 504(Gateway Timeout)

no-cahce와 must-revalidate는 비슷해보이지만 다음과 같은 상황에서 다르게 작용합니다.

위의 상황인 프록시 캐시 서버와 원서버의 연결이 단절된 경우

no-cache는 프록시 캐시에 있는 데이터를 보여주자고 200 OK를 응답으로 주지만
must-revalidate는 504 Gateway Timeout을 응답으로 내어 오류를 뿜습니다.


This post is licensed under CC BY 4.0 by the author.