[Network] HTTP 조건부 요청
본 내용은 MDN HTTP 조건부 요청을 보고 공부, 정리한 내용입니다.
HTTP 조건부 요청이란?
HTTP는 조건에 따라 다른 응답을 받을 수 있도록 하는 기능을 지원한다. 여기서 말하는 조건이란, 해당 자원의 신선한 정도(?)를 의미한다.
예를 들어, 클라이언트가 찾고자 하는 자료가 2020년 7월에 만들어졌고, 2020년 8월에 일부 수정되었다고 가정하자. 만약 이 클라이언트가 2020년 7월 이후 수정 내역은 상관없이 자료를 원한다면, 7월 자료를 클라이언트에게 주어도 아무 문제가 없다. 만약 클라이언트가 가장 최근의 자료를 원한다면, 서버는 반드시 2020년 8월에 업데이트된 자료를 클라이언트에 제공해야 할 것이다.
이와 같이 클라이언트가 서버에게 요청하는 자원에 조건을 걸 수 있는 기능을 HTTP의 조건부 요청이라 한다. HTTP 요청 메세지에서 validator header를 통해 조건을 걸 수 있다. validator header에는 Last-Modified 와 ETag가 있다. 또한 조건부 요청은 캐시와 밀접한 연관이 있다.
HTTP는 강한 검사를 기본으로 하며, 약한 검사를 원할 때 헤더에 명시한다.
Strong validation, Weak validation
요청으로 주어진 조건을 검사하는 방법에는 강한 검사와 약한 검사, 2가지가 있다.
1. Strong validation
말 그대로 조건이 정확하게 일치하는지를 검사하는 방법이다. 정확하게 검사한다는 말은, 해당 자원을 바이트 단위로 검사한다. 강하게 검사하는 만큼 시간과 구현의 복잡성이 증가하지만, 다운로드와 같이 정확한 조건 검사가 필요할 때는 이를 감수하고 사용된다.
Last-Modified 헤더로 강한 검사는 한계가 있어서, 주로 각 자원의 각 버전마다 고유의 ETag를 생성하여 검사한다.
(참고로 ETag는 MD5 해시값을 이용하여 생성된다.)
2. Weak validation
두 문서가 동일하다고 판단하는 기준이 강한 검사보다 낮다. 예를 들어 서버가 어떤 html 페이지를 제공할 때, 강한 검사는 해당 페이지의 모든 요소가 동일해야 클라이언트가 요청한 문서와 서버의 문서가 동일하다고 판단할 것이다. 하지만 클라이언트가 해당 페이지 내의 광고 내용이나 페이지의 footer 날짜 정도의 차이는 상관없다고 생각한다면, 이 때 약한 검사가 사용된다.
조건부 헤더
아래의 헤더들이 추가된 HTTP 요청 메세지는 조건부 요청이다.
-
If-Match
서버 자원의 ETag가 이 헤더에 나열된 값들 중 하나와 일치한다면 성공이다.‘W/’ 가 ETag의 접두사로 붙지 않았다면, 강한 검사로 체크한다.
-
If-None-Match
서버 자원의 ETag가 이 헤더에 나열된 값들 중 어느 것도 일치하지 않으면 성공이다.‘W/’ 가 ETag의 접두사로 붙지 않았다면, 강한 검사로 체크한다.
-
If-Modified-Since
서버 자원의 Last-Modified 날짜가 이 헤더 내의 주어진 날짜보다 최근 일자이면 성공이다. -
If-Unmodified-Since
서버 자원의 Last-Modified 날짜가 이 헤더 내의 주어진 날짜보다 오래되거나 같다면 성공이다. -
If-Range
If-Match와 If-Unmodified-Since와 유사하지만 이 헤더 내에 ETag 혹은 날짜를 하나만 정의할 수 있다. 성공한다면 206 partial content 상태줄과 캐쉬에 저장되어 있던 자원을 리턴하고, 그렇지 않으면 200 OK 상태줄과 origin 서버의 자원이 리턴된다.
Validator 예시 (Caching)
클라이언트가 /doc 경로의 문서를 처음 요청했고, 서버가 이에 대한 응답을 해주는 이미지를 보자.
서버가 응답한 문서와 헤더를 클라이언트는 캐싱한다. 서버가 지정한 Cache-Control 의 내용에 따라 캐시는 어느 기간 동안 유효하다. 이 기간 동안은 클라이언트가 해당 문서를 요청해도 브라우저는 캐싱된 문서를 바로 리턴한다.
더 이상 캐싱된 내용이 유효하지 않을 때 클라이언트의 해당 문서 요청이 발생하면, 브라우저는 서버에게 cache validation request를 전송한다. 이 때 서버에서 제공한 validator에 따라 요청 헤더에 날짜를 사용하는 If-Modified-Since가 사용될지, ETag를 사용하는 If-Match가 사용될지 결정된다. 이 예시에서는 서버에서 /doc 문서에 대해 날짜와 ETag를 모두 제공했으므로 cache validation request에도 두 헤더가 모두 사용된다.
만약 /doc 문서가 변경되지 않았다면, 서버에서는 304 Not Modified 응답을 내놓는다. 이를 받은 브라우저는 캐시 만료일이 지난 자원이 아직 신선함을 인지하고, 캐시되어 있던 자원을 클라이언트에게 제공한다.
캐시 자원의 신선함을 판단하기 위해 HTTP 요청/응답이 한 번 이루어졌지만, 자원의 신선함을 판단하지 않고 서버에서 해당 자원을 다시 받아오는 것보다 대역폭을 더 적게 사용함을 알 수 있다.
만약 /doc 문서가 변경되었다면 서버에서는 200 OK 상태코드와 함께 변경된 자원을 응답으로 내놓는다. 해당 자원은 서버 정책에 맞게 다시 캐시될 것이다.
브라우저가 캐시 만료일을 체크하고, 서버에게 cache validation request를 보내는 등의 작업은 웹 개발자의 조작이 없어도 브라우저가 스스로 진행한다.
조건부 요청 예시 (부분 다운로드)
웹에서의 자원 다운로드 상황에서 조건부 요청이 응용될 수 있다.
클라이언트가 서버의 자원을 다운로드 할 때, 서버에서 Accept-Ranges 헤더를 통해 부분적인 다운로드를 지원함을 알릴 수 있다.
위 그림처럼 부분 다운로드를 지원하는 서버로부터 다운로드 중 끊겼다면, 클라이언트는 아래와 같이 동작할 수 있다.
HTTP 요청 메세지의 Ranges 헤더를 통해 다음에 받을 byte를 명시함으로써 다운로드를 이어갈 수 있다.
여기서 포인트는, 다운로드가 끊긴 사이에 해당 자원에 변경사항이 있었는지를 체크해야 한다. 여기서 조건부 요청이 사용되며, 조건 검사가 성공적으로 끝나면 서버는 206 Partial Content 응답코드와 함께 Content-Range 헤더에 앞으로 전송할 바이트범위를 명시하여 클라이언트에게 보낸 후 다운로드를 이어간다.
만약 조건 검사가 실패하면 서버는 412 Precondition Failed 응답코드를 보내고 HTTP 연결을 끊는다. 따라서 클라이언트는 다시 다운로드 요청을 보내야 하며, 이와 같은 HTTP 메세지 교환이 잦아질수록 성능에 악영향을 주게 된다.
불필요한 HTTP 메세지 교환을 줄이기 위해 클라이언트 측에서 If-Range 헤더를 사용한다. If-Range 헤더는 위에서 설명했듯이 하나의 ETag 혹은 날짜를 기입할 수 있으며, 인자로 준 조건이 일치하면 206 Partial Content 응답코드를 전달하여 브라우저가 캐시된 자원을 그대로 사용할 수 있게 하고, 그렇지 않으면 200 OK 응답코드와 함께 서버의 자원을 클라이언트에게 전달한다.
다운로드 상황에서 If-Range 헤더를 사용할 때는 조건 검사 실패 시 바로 새로운 다운로드가 시작된다. 성공 시 206 Partial Content 응답코드로 다운로드를 이어서 진행하게 하는건 동일하지만, 실패 시 200 OK 응답코드와 변경된 Last-Modified 혹은 ETag 내용과 함께 새로운 다운로드가 바로 시작된다.
조건부 예시 (업데이트 손실)
웹 서버에서 클라이언트가 문서를 받아, 수정 후 업데이트하는 상황을 가정해보자.
한 명의 클라이언트가 일정 기간 동안 수정 작업을 하는 건 문제가 없지만, 두 명 이상의 클라이언트가 동일 문서에 접근하여 수정하면 문제가 생긴다. 이 때 조건부 요청으로 서버의 자원이 변경된 상태인지를 확인할 수 있다.
조건부 요청을 사용하면 문서를 최초로 업데이트하고자 하는 클라이언트 (이하 클라이언트 1) 의 시점으로 봤을 때 서버의 자원은 아직 그대로이며, 클라이언트는 PUT 요청을 통해 자원을 업데이트하고 새로운 Last-Modified 혹은 ETag를 수신할 수 있다.
클라이언트1이 문서를 수정하기 전에 접근했으나, 클라이언트1이 문서를 업데이트 한 후 업데이트를 요청한 클라이언트2는 자신이 보낸 If-Match 혹은 If-Unmodified-Since 내용이 서버의 조건 검사 시 부합하지 않으므로 412 Precondition Failed 응답을 받게 되고, 이 응답을 어떻게 처리할 것인지는 클라이언트에 달려 있다.
- 이 예시의 경우에는 어떤 자원이 자신이 업데이트 요청한 시점에서 달라져 있는지를 확인하는 것이 포인트이므로 If-Match (ETag 변경 확인) 나 If-Unmodified-Since (해당 자원이 헤더 인자로 주어진 날짜 이후에 변경되었는지를 확인) 헤더를 사용하는 것이 바람직하다.
- 412 Precondition Failed 응답을 받은 뒤, 클라이언트는 해당 자원의 최신 버전에서부터 다시 시작할 수 있도록 해 주거나, 최신 업데이트 내용과 자신의 내용 간 차이를 보여주어 사용자가 재편집을 할 수 있도록 하는 방법 등이 있다.