304 Not Modified
用法
当请求是安全方法(如 GET 或 HEAD 请求),且服务器判定该资源相较于客户端缓存中存储的版本未发生变化时,会返回 304 Not Modified 状态码。这样可以节省带宽,因为服务器不必重新传输客户端已经持有的数据。
注意
尽管 304 与重定向同属 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。到第三次请求时,有了新版本,于是返回完整的 200 响应,并带有更新后的 ETag。
注意
每次服务器更新资源时,都会生成新的 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