Last-Modified — 是一种HTTP响应头,用于指示服务器上资源的最后一次修改日期和时间,允许客户端通过If-Modified-Since执行条件请求。如果资源自指定日期以来未发生变化,服务器将返回304 Not Modified,不传输响应主体,从而显著节省带宽。根据RFC 7232 (IETF, 2014),使用Last-Modified的条件请求可将重复访问时的页面加载时间减少30-60%。该头部被大多数HTTP服务器和代理服务器自动支持。
重点
Last-Modified — 属于HTTP条件请求头部组。服务器将其添加到GET或HEAD响应中,以HTTP-date格式指示所请求资源的最后一次修改日期和时间:Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT。客户端(浏览器、移动应用、代理服务器)将此日期与缓存的资源一同保存。再次请求时,客户端发送带有相同日期的If-Modified-Since头部,服务器将其与资源的当前修改时间进行比较。
使用Last-Modified的条件请求协议由RFC 7232定义,得到所有现代HTTP服务器的支持。日期格式有严格规定 — 仅使用GMT(格林尼治平时间),不带时区指示。服务器必须以三种可能格式之一返回日期:RFC 1123(标准)、RFC 850(过时)或ANSI C asctime。实际上,几乎所有服务器都使用RFC 1123格式,固定长度29个字符。
Last-Modified属于验证机制缓存类别:它不告诉客户端能否缓存响应,而是提供一种检查已缓存资源是否仍有效的工具。缓存策略通过Cache-Control头部单独设置。根据Akamai (2025)的研究,正确配置Last-Modified与Cache-Control可将静态内容的源服务器负荷降低达70%。
Last-Modified头部早在HTTP/1.0(RFC 1945, 1996)中就已定义,成为互联网上最早的缓存管理机制之一。在HTTP/1.1中Etag出现之前,它是执行条件请求的唯一方式。尽管历史悠久,该头部因其简单性仍然自成主流 — 服务器无需计算内容的哈希值,只需从文件系统读取文件的时间戳或从数据库读取updated_at字段。
完整循环包括三个阶段。第一次请求时,服务器返回带有Last-Modified头部和HTTP状态200 OK的资源。客户端将响应连同日期一起缓存。再次请求时,客户端发送带有保存日期的If-Modified-Since头部。服务器将此日期与资源的当前修改时间进行比较。如果资源未修改 — 返回304 Not Modified,响应主体为空。如果已修改 — 返回200 OK,含有新数据和新的Last-Modified。
// 第一次请求 — 服务器返回带有日期的资源
HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT
[{"id": 1, "name": "Alice"}]
// 再次请求 — 客户端发送保存的日期
GET /api/users HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 02 Jul 2025 14:30:00 GMT
// 响应 — 数据未发生变化
HTTP/1.1 304 Not Modified
对于移动应用,Last-Modified在数据同步时尤其有用。应用程序保存最后一次成功更新的日期,并将其发送给服务器作为If-Modified-Since。如果有更多数据或数据已变化 — 服务器返回完整集合。如果未变化 — 返回304,应用程序使用本地副本。OkHttp和URLSession通过内置缓存系统自动支持该机制。
对于静态文件,Nginx和Apache从文件系统属性获取日期 — mtime(修改时间)。对于动态内容,服务器代码必须根据业务逻辑显式设置Last-Modified:来自数据库的updated_at字段、Git中最后一次提交的日期、构建产物的时间戳。如果没有显式设置Last-Modified,服务器可能根本不返回该头部,客户端将无法根据日期执行条件请求。
Last-Modified和ETag执行类似的任务 — 允许客户端检查缓存的有效性 — 但具有根本性差异。Last-Modified使用时间戳,而ETag使用唯一版本标识符。每种方法在不同场景下更高效,HTTP规范建议同时使用两种头部。
| 维度 | Last-Modified | ETag |
|---|---|---|
| 实质 | 最后一次修改日期 | 唯一版本标识符 |
| 精确度 | 精确至秒 | 精确至比特(哈希) |
| 实现复杂度 | 低 — 从文件系统自动获取 | 中 — 需要计算哈希 |
| 集群服务器 | 问题:各节点的mtime可能不同 | 节点数据相同时稳定 |
| 范围请求支持 | 不影响Range请求 | 需要强ETag支持范围请求 |
| 建议 | 适合静态文件和简单API | 适合需要精确验证的API |
Last-Modified的主要优势是简单。服务器无需计算内容的哈希值,从而节省每次请求的CPU资源。对于高负载项目,服务静态文件或具有清晰时间戳的数据时,Last-Modified仍是最佳选择。而ETag提供了绝对的精确度 — JSON响应中一个字母的变化将改变ETag,但可能不会改变日期(如果文件被相同版本覆盖)。
规范建议同时返回两个头部。服务器在200 OK响应中同时包含Last-Modified和ETag。客户端发送两个条件头部 — If-Modified-Since和If-None-Match。服务器先检查ETag(优先级更高),然后检查Last-Modified。如果至少有一个指示内容已变化 — 返回完整响应。这提供了最大灵活性:ETag确保精确度,Last-Modified作为不支持ETag的客户端的备用检查。
Last-Modified的配置取决于服务器类型。对于Nginx和Apache,静态文件的Last-Modified根据mtime自动设置。对于动态应用,需要在服务器代码中设置头部。下面看看常见平台上的配置。
// Express.js — 设置Last-Modified
app.get("/api/users", async (req, res) => {
const updatedAt = await getLastUpdate()
const ifModifiedSince = req.get("If-Modified-Since")
// 检查If-Modified-Since
if (ifModifiedSince && new Date(ifModifiedSince)
>= updatedAt) {
return res.status(304).end()
}
const users = await getUsers()
res.set("Last-Modified", updatedAt.toUTCString())
res.json(users)
})
在Express.js示例中,服务器从数据库获取最后一次更新日期,检查客户端的If-Modified-Since,如果缓存有效则返回304。如果数据已变化 — 设置新的Last-Modified并返回完整响应。toUTCString()将日期转换为所需的HTTP格式。在生产环境中,建议将updatedAt缓存到Redis,以免每次请求都查询数据库。
Nginx基于文件最后一次修改时间自动为静态文件设置Last-Modified。可以通过etag指令(关闭ETag)或通过ngx_http_headers_module模块禁用或改变这种行为。对于代理到后端的请求,Last-Modified从upstream响应中原样传递。重要:如果后端未返回Last-Modified,Nginx不会为动态响应自动添加该头部。
Last-Modified存在几个已知局限。主要问题是精确度仅至秒级。如果资源在一秒内发生了两次变化,客户端可能会错过新版本。实际上这种情况并不常见,但对于高频更新(报价流、聊天)建议使用ETag。第二个局限是集群问题:由于复制或部署,不同服务器上的文件可能具有不同的mtime,导致Last-Modified不一致。
第三个局限是秒级精度的If-Modified-Since处理在频繁轮询服务器时可能导致多余请求。如果客户端每500ms发送一次If-Modified-Since,服务器每次都返回200 OK,因为日期未变,但资源实际上已更新。解决方案是与ETag联合使用:ETag能捕捉秒级内的变化,而Last-Modified作为备用。
第四个问题是Last-Modified无法区分相同日期的不同版本。如果文件从备份中恢复且其mtime与原始文件相同,客户端将无法察觉内容已变。ETag解决了这个问题:无论时间戳如何,内容的哈希值在数据发生任何变化时都会改变。对于关键数据,始终同时使用两种头部。
常见问题
仅使用RFC 1123格式的GMT(格林尼治平时间):星期几,日期,月份,年份,时间。示例:Wed, 02 Jul 2025 14:30:00 GMT。时区始终为GMT,不允许其他格式。
技术上可以,但这违反RFC 7232。如果服务器返回未来时间,客户端将不会更新资源直到该时刻。这种配置被视为错误 — 日期必须是过去或现在。
不能,If-Modified-Since条件请求仅支持GET和HEAD。POST请求不会被缓存,也不使用日期验证。对于POST数据的有效性检查,请使用ETag或自定义机制。
Cache-Control确定缓存策略(最大存储时间、谁可以缓存),而Last-Modified是验证过期缓存的机制。max-age过期后,客户端发送If-Modified-Since来检查内容是否仍有效。
检查服务器是否从正确的源(数据库、文件系统或API)设置了头部。对于动态响应,确保在处理程序中显式调用res.setHeader(“Last-Modified”, ...)。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。