HTTPQUERY
한국어

304 Not Modified

사용법

304 Not Modified 상태 코드는 GET이나 HEAD 요청처럼 안전한 요청에 대해, 클라이언트 캐시에 저장된 버전 이후로 리소스가 변경되지 않았다고 서버가 판단했을 때 전송됩니다. 서버가 클라이언트가 이미 가진 데이터를 다시 전송하지 않으므로 대역폭이 절약됩니다.

참고

304 Not Modified는 3xx 상태 분류를 공유하지만 리다이렉트가 아닙니다. 리다이렉트(301, 302, 307, 308)는 Location 헤더를 통해 클라이언트를 다른 URL로 보냅니다. 304는 클라이언트에게 동일한 리소스의 캐시된 사본을 재사용하라고 알리며, Location 헤더는 전송되지 않습니다. 브라우저 개발자 도구가 304를 리다이렉트와 묶어 보여주기도 해서 혼란을 더합니다.

참고

개발자 도구의 “200 (from disk cache)“는 Cache-Control max-age가 아직 만료되지 않았기 때문에 브라우저가 서버와 통신하지 않고 캐시에서 리소스를 제공했다는 뜻입니다. 304는 브라우저가 재검증을 위해 서버와 통신했고 서버가 캐시된 사본이 최신임을 확인해 주었다는 뜻입니다. 캐시된 200은 네트워크 요청이 전혀 없고, 304는 한 번의 왕복이 있습니다.

서버는 응답에 다음 헤더 중 하나 이상을 생성합니다.

304 Not Modified 응답의 목적은 대역폭을 최소화하는 것이므로, 서버는 캐시 갱신 과정에 도움이 되지 않는 한 원래 요청에 포함되지 않았던 헤더를 생략합니다.

조건부 GET 요청에서 관련된 지시문은 If-None-Match와 If-Modified-Since입니다. 이들은 리소스를 다시 전송해야 하는지 판단하기 위해 ETag 같은 검증자에 의존합니다.

예시

클라이언트가 리소스를 요청해 버전을 식별하는 ETag를 받고, 이후에 재검증을 수행합니다. 서버는 리소스가 변경되지 않았음을 인지하고 304 Not Modified를 반환합니다. 세 번째 요청에서는 새 버전이 존재하므로, 갱신된 ETag와 함께 완전한 200 응답이 전송됩니다.

참고

서버가 리소스를 갱신할 때마다 새 ETag가 생성됩니다. 또는 서버가 Last-Modified 날짜를 반환하고 클라이언트가 대신 If-Modified-Since를 사용하면, 서버가 ETag 이력을 유지할 필요가 없어집니다.

SEO 영향

Googlebot이 304를 받으면 크롤러는 마지막 크롤링 이후 콘텐츠가 변경되지 않았음을 인식합니다. 색인 파이프라인은 해당 URL의 순위 신호를 다시 계산하지만, 콘텐츠를 다시 가져오거나 다시 색인하지는 않습니다.

오래된 304의 함정

304는 캐시된 버전이 여전히 최신임을 확인해 줍니다. 서버가 과거에 깨진 페이지(빈 본문, 오류 콘텐츠)를 200으로 제공했고 크롤러가 그 결과를 캐시했다면, 이후의 304는 크롤러에게 그 깨진 버전이 그대로 유효하다고 알리는 셈입니다. “콘텐츠”가 변경되지 않았으므로 크롤러는 재확인을 멈춥니다. 페이지를 고치는 것만으로는 충분하지 않습니다. 서버는 캐시된 오류를 대체하기 위해 올바른 본문(그리고 새 ETag)과 함께 새로운 200을 반환해야 합니다. 근본 원인(과거의 일시적 오류)이 더 이상 보이지 않기 때문에 이 상황은 디버깅하기 어렵습니다.

최초 요청

GET /news.html HTTP/1.1
Host: example.com

최초 응답

HTTP/1.1 200 OK
ETag: "1234000"
Content-Type: text/html
Content-Length: 1250

<message body contains requested resource>

두 번째 요청

GET /news.html HTTP/1.1
Host: example.com
If-None-Match: "1234000"

두 번째 응답

HTTP/1.1 304 Not Modified
ETag: "1234000"

세 번째 요청

GET /news.html HTTP/1.1
Host: example.com
If-None-Match: "1234000"

세 번째 응답

HTTP/1.1 200 OK
ETag: "1234001"
Content-Type: text/html
Content-Length: 1600

<message body contains requested resource>

코드 참고

.NET

HttpStatusCode.NotModified

Rust

http::StatusCode::NOT_MODIFIED

Rails

:not_modified

Go

http.StatusNotModified

Symfony

Response::HTTP_NOT_MODIFIED

Python3.5+

http.HTTPStatus.NOT_MODIFIED

Java

java.net.HttpURLConnection.HTTP_NOT_MODIFIED

Apache HttpComponents Core

org.apache.hc.core5.http.HttpStatus.SC_NOT_MODIFIED

Angular

@angular/common/http/HttpStatusCode.NotModified