65.9K
CodeProject 正在变化。 阅读更多。
Home

HTTP 304 Not Modified - 简介

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.57/5 (8投票s)

2015年1月28日

CPOL

6分钟阅读

viewsIcon

53223

HTTP 304 缓存机制简介

引言

HTTP 304 Not Modified 及相关头部信息提供了一种网站中广泛使用的缓存机制。它可以减少网络流量并避免不必要的响应开销。大多数 Web 服务器(如 IIS、Apache 和 Nginx)默认支持此机制来处理静态资源(JavaScript、CSS、图像文件等),因此我们通常不会注意到它的存在。但它实际上在 Web 开发中起着重要作用。了解其幕后原理对于我们构建 Web 应用程序非常有益。

机制

HTTP 304 Not Modified 的工作流程可以总结为以下几个步骤:

  1. 客户端发送一个正常的请求以获取服务器上的内容。
  2. 服务器响应内容,并附带一个头部值,指示当前状态。客户端将保留此头部值以备将来使用。
  3. 在将来的请求中,客户端发送一个带有已保留值的头部,询问服务器状态是否已更改。服务器的响应可能是:
    • 如果状态已更改,服务器将重复步骤 1,响应最新的内容以及当前状态。客户端将渲染内容并将其保存在缓存中。
    • 如果状态未更改,服务器将响应 HTTP 304 Not Modified,但不包含内容。客户端将从缓存中渲染内容。

图 1 是服务器端的流程图。

图 1

在步骤 2 中,服务器有两种方式来指示当前内容的最新状态。

  • 一种是响应 Last-Modified 日期时间值,表示上次修改发生的时间。客户端在下一个请求中将发送 If-Modified-Since 头部值来询问服务器是否已更改。
  • 另一种是响应一个 ETag 唯一标识符,该标识符在每次发生更改时生成。在这种情况下,客户端将在下一个请求中发送 If-None-Match 头部值来询问服务器是否已更改。

演练

让我们从一个简单的请求开始,通过几个例子来了解。

GET /dota2/sheever.png HTTP/1.1
Host: localhost:8000 

在此示例中,客户端尝试渲染一个 PNG 文件。由于没有 If-Modified-SinceIf-None-Match 头部,服务器知道这是该图像的第一次请求。在接下来的部分中,我们将看到服务器的两种选项。

选项 1

对于第一种选项,服务器指示文件系统中的图像的创建/修改时间。

HTTP/1.1 200 OK
Date: Mon, 18 Sep 2015 22:19:01 GMT
Content-Type: image/png
Content-Length: 1393216 
Last-Modified: Thu, 15 Sep 2015 22:10:34 GMT

(PNG Content)

从服务器接收到响应后,客户端将此图片和 Last-Modified 头部值存储在缓存中。有了这些信息,客户端就能够在将来的请求中,使用 If-Modified-Since 头部值询问服务器。假设用户只是按 F5 重新加载页面,对同一图像的第二次请求将是:

GET /dota2/sheever.png HTTP/1.1
Host: localhost:8000
If-Modified-Since: Thu, 15 Sep 2015 22:10:34 GMT

现在,服务器将检查该图像自 Thu, 15 Sep 2015 22:10:34 GMT 以来是否被编辑过。如果答案是肯定的,服务器可以直接将此请求视为正常请求,并将 Last-Modified 头部值更新为最新更改的日期时间。如果答案是否定的,响应将非常简单:

HTTP/1.1 304 Not Modified
Date: Mon, 18 Sep 2015 22:20:01 GMT 

请注意,此状态码不包含任何内容。响应立即结束,客户端将从缓存中渲染图像。这就是该机制如何减少网络流量。

如果我们应用图 1 到这个选项,它将变为下面的图 2:

图 2

选项 2

在第二种选项中,服务器给出一个代表此图像最新更改的唯一标识符。由于 HTTP 1.1 中 ETag 值格式未严格定义,在本例中我们使用 GUID。(此外,生成 ETag 值的方法并不重要。重要的是服务器能否识别其背后的更改。)

HTTP/1.1 200 OK
Date: Mon, 18 Sep 2015 22:19:01 GMT
Content-Type: image/png
Content-Length: 1393216 
ETag: "82b600b9-c321-4f34-8065-cec076cfbacc"

​(PNG Content)

现在,客户端就能够在将来的请求中,使用 If-None-Match 头部值询问服务器。此头部用于测试客户端持有的值是否是最新的。

GET /dota2/sheever.png HTTP/1.1
Host: localhost:8000
If-None-Match: "82b600b9-c321-4f34-8065-cec076cfbacc"

由于服务器在图像更改时必须重新生成和维护 ETag 值,因此服务器能够识别该值是否已过期。如果答案是肯定的,服务器将响应最新的内容以及更新的 ETag 头部值。如果答案是否定的,响应将与我们在选项 1 中看到的类似:

HTTP/1.1 304 Not Modified
Date: Mon, 18 Sep 2015 22:20:01 GMT 

同样,此状态码不包含任何内容。响应立即结束,客户端将从其缓存中渲染图像。

现在我们可以将图 1 更改为图 3。

图 3

在本文的下一节中,我们将讨论根据内容特性选择哪种选项。

优缺点

您可能已经注意到选项 2 在实践中存在一个问题。让我们回顾一下示例并查看这行:

引用

服务器在图像每次更改时都必须重新生成和维护 ETag

问题是:服务器如何实现这一点?服务器如何实时得知更改?在 .NET Framework 中,我们可以在 Web 应用程序中使用 FileSystemWatcher 来监视文件。在 Java 中,我们可以使用 WatchService,在其他平台也是如此。但无论使用什么 API,监视文件系统对我们的 Web 应用程序来说总是成本高昂的,特别是监视多个目录中的多个文件。

选项 1 在此示例中实现起来相对容易,因为服务器可以随时从文件系统中检索文件的创建/修改时间。服务器知道自 If-Modified-Since 头部中给出的时间以来文件是否已更改。因此,它不必监视文件。

但选项 1 也有问题。文件的创建/修改时间很容易被其他应用程序修改,而不会改变其内容。因此,它不是一个非常可靠的解决方案。此外,HTTP 1.1 使用的日期时间格式,RFC1123 标准不包含毫秒部分,这意味着如果内容在一秒钟内更改超过 2 次,Last-Modified 头部值将无法反映这些更改。

那么哪种选项更好?这将高度依赖于内容的特性。如果内容是图像文件、CSS 文件或 Web 应用程序中相对不重要的东西,选项 1 可能就足够了。这正是大多数 CDN(内容分发网络)服务提供这类内容的方式。另一方面,如果内容是动态生成的 JavaScript 语句、JSONP 或我们希望暂时保留在内存中的对象,那么选项 2 是更好的选择。我们可以应用简单的设计模式,例如 .NET Framework 中的 <a href="https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged%28v=vs.110%29.aspx" target="_blank">INotifyPropertyChanged</a> 接口,使我们的 Web 应用程序能够轻松地接收内容更改。因此,维护内容的 ETag 值就更容易实现了。

结论

Web 开发框架不断发展,新框架层出不穷。但无论您使用什么框架,多关注 HTTP 本身并理解其内在机制,都能帮助构建更好的 Web 应用程序。HTTP 304 Not Modified 就是其中之一。

延伸阅读

HTTP 304 Not Modified - An Introduction - CodeProject - 代码之家
© . All rights reserved.