모든 개발자를 위한 HTTP 웹 기본 지식 - 인프런 | 강의
실무에 꼭 필요한 HTTP 핵심 기능과 올바른 HTTP API 설계 방법을 학습합니다., 웹 기술을 사용하는 개발자라면 누구나 OK!꼭 필요한 HTTP의 핵심을 알려드립니다. 📣 확인해주세요!본 강의는 자바 스
www.inflearn.com
강의를 들으며 생각 정리
캐시 기본 동작
캐시가 없을 때
1. 첫 번째 요청
GET /star.jpg
2. 응답
HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Length: 34012
lkj123kljoiasudlkjaweioluywlnfdo912u34ljko98udjkla slkjdfl;qkawj9;o4ruawsldkal;skdjfa;ow9ejkl3123123
이 때 star.jpg가 약 1.1M라고 가정하자. 만약 브라우저에서 두 번째 요청을 똑같이 star.jpg로 한다면 다시 1.1M를 전송해야 한다.
캐시가 없을 때의 단점은 다음과 같다.
- 데이터가 변경되지 않아도 계속 네트워크를 통해서 데이터를 다운로드 받아야 한다.
- 브라우저 로딩 속도가 느리다.
캐시 적용
캐시를 적용했을 때, 같은 예시를 보자.
1. 첫 번째 요청
GET /star.jpg
2. 응답
HTTP/1.1 200 OK
Content-Type: image/jpeg
cache-control: max-age=60 -> 캐시가 유효한 시간(초)
Content-Length: 34012
lkj123kljoiasudlkjaweioluywlnfdo912u34ljko98udjkla slkjdfl;qkawj9;o4ruawsldkal;skdjfa;ow9ejkl3123123
캐시가 있을 때, 이처럼 응답 시 캐시 헤더가 추가되고 응답 결과를 캐시와 브라우저 모두에게 전달한다.
이후, 두 번째 같은 start.jpg를 요청할 때 캐시 유효 시간을 검증하고(ex. 60초) 유효하다면 캐시에서 조회한다.
캐시를 적용함으로써 장점은 다음과 같다.
- 캐시 유효 시간동안 네트워크를 사용하지 않아도 된다.
- 브라우저 로딩 속도가 매우 빠르다.
만약 캐시 유효 시간이 지나면 다시 서버에 요청을 하게 되고 응답 결과를 다시 캐시에 저장한다.
그런데 결과가 바뀌지 않았다면 굳이 시간이 지났다고 해서 다시 서버에 요청할 필요가 있을까? 이에 대해 알아보자.
검증 헤더와 조건부 요청
캐시 유효 시간이 초과해서 서버에 다시 요청하면 다음 두 가지 상황이 나타난다.
- 서버에서 기존 데이터를 변경함
- 서버에서 기존 데이터를 변경하지 않음
1의 경우에는 당연히 데이터를 다시 서버로부터 불러와야 한다.
그러나 2의 경우 캐시 만료 후에도 서버에서 데이터를 변경하지 않았다. 생각해보면 데이터를 전송하는 대신에 저장해 두었던 캐시를 재사용할 수 있다. 이 때, 클라이언트의 데이터와 서버의 데이터가 같다는 사실을 확인할 수 있는 방법이 필요하다.
데이터 일치를 확인하는 방법은 검증 헤더와 조건부 요청을 사용하는 것이다. 다음 예시를 보자.
1. 첫 번째 요청
GET /star.jpg
2. 응답
HTTP/1.1 200 OK
Content-Type: image/jpeg
cache-control: max-age=60
Last-Modified: 2020년 11월 10일 10:00:00 -> 데이터가 마지막에 수정된 시간
Content-Length: 34012
lkj123kljoiasudlkjaweioluywlnfdo912u34ljko98udjkla slkjdfl;qkawj9;o4ruawsldkal;skdjfa;ow9ejkl3123123
서버가 응답할 때 검증 헤더(Last-Modified)를 추가해서 데이터 최종 수정일을 보낸다.
데이터(star.jpg), 캐시 유효 시간(60초) 뿐만 아니라 데이터 최종 수정일(Last-Modified) 역시 브라우저 캐시에 저장한다.
이후, 캐시 유효 시간이 지나면 다시 서버에 재요청을 한다. 이 때, 조건부 요청을 한다.
1. 조건부 요청
GET /star.jpg
if-modified-since: 2020년 11월 10일 10:00:00 -> 캐시가 가지고 있는 데이터 최종 수정일을 함께 보낸다.
2. 응답
조건부 요청에서 데이터 최종 수정일과, 서버의 해당 데이터의 최종 수정일이 같다면 다음과 같은 응답을 보낸다.
HTTP/1.1 304 Not Modified
Content-Type: image/jpeg
cache-control: max-age=60
Last-Modified: 2020년 11월 10일 10:00:00
Content-Length: 34012
(HTTP Body가 없음)
메시지를 보면 304 상태코드와 함께 HTTP Body를 제외한 HTTP 헤더들만 전송하는 것을 볼 수 있다.
만약 start.jpg 메시지의 경우 HTTP 헤더가 0.1M, HTTP 바디가 1.0M라고 했을 때, 0.1M의 헤더만 전송하면 되기 때문에 더 빠르고 효율적으로 전송할 수 있다.
브라우저 입장에서 서버로부터 304 상태코드를 받았다면 캐시의 기존 결과를 재사용, 갱신해서 다시 사용한다.
검증 헤더와 조건부 요청을 사용함으로써 얻는 장점은 다음과 같다.
- 캐시 유효 시간이 초과해도, 서버의 데이터가 갱신되지 않으면 304코드 + 헤더 정보만 응답(바디x)
- 클라이언트는 캐시에 저장되어 있는 데이터 재활용
- 결과적으로 네트워크 다운로드가 발생하지만 용량이 적은 헤더 정보만 다운로드하기 때문에 매우 실용적인 해결책
그러나 이 방식은 단점 역시 존재한다.
- 1초 미만(0.x초) 단위로 캐시 조정이 불가능하다.(cache-control: max-age 헤더가 초 단위)
- Last-Modified 헤더는 날짜 기반의 로직을 사용한다. 데이터를 수정해서 날짜가 다르지만, 수정한 데이터 결과가 똑같은 경우(ex. A->B->A) 같은 데이터를 다시 네트워크로 불러와야 한다.
- 서버에서 별도의 캐시 로직을 관리하고 싶은 경우 : 날짜 기반의 로직은 수정하면 어쩔 수 없이 헤더가 변경되어야 하지만 서버가 직접 데이터의 변경 여부 등의 로직을 관리하고 싶을 때가 있다.(ex. 스페이스나 주석처럼 크게 영향이 없는 변경에서 캐시를 유지하고 싶은 경우)
그래서 Last-Modified, If-Modified-Since 로직 대신 사용할 수 있는 것이 ETag, If-None-Match 로직이다.
ETag : 캐시용 데이터에 임의의 고유한 버전 이름을 달아둔다.
ex) ETag: "v1.0", ETag: "a2jiodwjekjl3"
데이터가 변경되면 이 이름을 바꾸어서 변경한다.
ex) ETag: "aaaaa" -> ETag: "bbbbb"
그래서 단순하게 ETag만 보내서 같으면 유지, 다르면 다시 받는 방식을 사용한다.
예시를 보자.
1. 첫 번째 요청
GET /star.jpg
2. 응답
HTTP/1.1 200 OK
Content-Type: image/jpeg
cache-control: max-age=60
ETag: "aaaaaaaaaa"
Content-Length: 34012
lkj123kljoiasudlkjaweioluywlnfdo912u34ljko98udjklasl kjdfl;qkawj9;o4ruawsldkal;skdjfa;ow9ejkl3123123
-> 캐시에 응답결과가 ETag와 함께 추가된다.
이제 캐시 시간이 초과되고 요청하는 경우를 보자.
1. 요청
GET /star.jpg
If-None-Match: "aaaaaaaaaa" -> 캐시가 가지고 있는 ETag
2. 응답
만약 데이터가 아직 수정되지 않았다면 -> 클라이언트와 서버의 ETag가 같다면
HTTP/1.1 304 Not Modified
Content-Type: image/jpeg
cache-control: max-age=60
ETag: "aaaaaaaaaa"
Content-Length: 34012
(HTTP Body가 없음)
-> HTTP 헤더만 보내고 응답 결과를 재사용한다.
ETag, If-None-Match 정리
- 진짜 단순하게 ETag만 서버에 보내서 같으면 유지, 다르면 다시 받기
- 캐시 제어 로직을 서버에서 완전히 관리
- 클라이언트는 단순히 이 값을 서버에 제공(클라이언트는 캐시 메커니즘을 모름)
캐시와 조건부 요청 헤더
앞서 배운 헤더들을 정리하면서 비슷한 기능의 헤더들을 추가적으로 알아보자.
캐시 제어 헤더
Cache-Control
Cache-Control: max-age : 캐시 유효 시간, 초 단위
Cache-Control: no-cache : 데이터는 캐시해도 되지만, 항상 ORIGIN 서버에 검증하고 사용
-> 즉, 캐시는 사용하되 데이터를 사용할 때마다 if-modified-since 등의 검증, 조건부 헤더를 통해서 확인하고 사용.
Cache-Control: no-store : 데이터에 민감한 정보가 있으므로 저장하면 안됨.
Expires
expires: Mon, 01 Jan 1990 00:00:00 GMT -> 캐시 만료일을 정확한 날짜로 지정
지금은 더 유연한 Cache-Control: max-age 권장
검증 헤더와 조건부 요청 헤더
검증 헤더
ETag: "v1.0",
Last-Modified: Thu, 04 Jun 2020 07:19:24 GMT
조건부 요청 헤더
If-Match, If-None-Match: ETag 값 사용
If-Modified-Since, If-Unmodified-Since: Last-Modified 값 사용
프록시 캐시
만약 웹 브라우저가 미국에 있는 어떤 ORIGIN 서버에 접근한다고 하자. 이 때, 500ms의 시간이 걸린다고 가정하자.
그러나 이렇게 원(ORIGIN) 서버에 직접 접근하지 않고 브라우저와 서버 가운데 프록시 캐시 서버를 둔다면 데이터에 접근하는 시간을 줄일 수 있다.
그림처럼 미국 서버에 직접 접근할 때는 500ms가 걸릴 것을 프록시 캐시 서버를 통해 접근한다면 100ms만에 접근할 수 있다. 이 때, 프록시 캐시 서버를 public 캐시라고 하고 각각의 웹 브라우저만의 캐시를 private 캐시라고 한다.
Cache-Control - 캐시 지시어
Cache-Control: public -> 응답이 public 캐시에 저장되어도 됨
Cache-Control: private-> 응답이 해당 사용자만을 위한 것임, private 캐시에 저장해야 함(기본값)
캐시 무효화
Cache-Control: no-cache(or no-store) 처럼 캐시 무효화에 관련된 지시어가 있지만 이는 완벽히 무효화를 보장하지 않는다. 금융 관련 정보 같은 경우처럼 확실한 캐시 무효화가 보장되어야 하는 경우들이 있다.
만약 확실한 캐시 무효화 응답을 하고자 한다면 다음과 같이 헤더를 추가해야 한다.
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache -> HTTP 1.0 하위 호환의 경우 필요한 헤더
no-cache와 no-store의 경우 앞서 공부했었지만 한번 더 정리해 보자.
Cache-Control: no-cache : 데이터는 캐시해도 되지만, 항상 원 서버에 검증하고 사용
Cache-Control: no-store : 데이터에 민감한 정보가 있으므로 저장하면 안됨
Cache-Control: must-revalidate : 캐시 만료 후 최조 조회 시 원 서버에 검증하고 사용
웹 브라우저, 서버마다 스펙이 살짝 다르고 지원하는 헤더들도 다르기 때문에 위 지시어들을 전부 사용하는 것이 좋다.
그런데 no-cache가 어차피 항상 원 서버에 검증할텐데 must-revalidate를 굳이 추가해주는 이유는 뭘까?
no-cache
no-cache는 데이터를 원 서버에서 검증해서 사용해야 하기 때문에 프록시 캐시를 거쳐서 원 서버까지 ETag를 포함한 요청 메시지를 보내야 한다.
그런데 만약 순간 네트워크가 단절되어 프록시 캐시에서 원 서버로 접근하지 못한다면 no-cache는 캐시 서버 설정에 따라서 오래된 데이터라도 보여주기 위해 200 OK와 함께 데이터를 반환할 수 있다.
must-revalidate
그러나 must-revalidate의 경우 프록시 캐시에서 원 서버에 접근할 수 없는 경우 항상 504 Gateway Timeout 오류를 발생시킨다.
위와 같은 차이점으로 인해 Cache-Control: no-cache, no-store, must-revalidate 지시어를 전부 사용함으로써 확실한 캐시 무효화를 보장해준다.
'http' 카테고리의 다른 글
HTTP 헤더 1 - 일반 헤더 (0) | 2021.04.14 |
---|---|
HTTP 상태코드 (0) | 2021.04.13 |
HTTP 메서드 활용 (0) | 2021.04.12 |
HTTP 메서드 (0) | 2021.04.12 |
HTTP 기본 (0) | 2021.04.07 |