如何知道何时发送304 Not Modified响应
我正在编写一种资源处理方法,该方法可以控制对各种文件的访问,并且我希望能够利用浏览器的缓存。我的问题有两个:
- 为了确定是否应该发送304响应,我需要检查哪些确定的HTTP标头,并且在检查它们时我在寻找什么?
- 另外,当我最初以200响应发送文件(例如" Last-Modified")时,是否需要发送任何标头?
一些伪代码可能是最有用的答案。
缓存控制标头如何?它的各种可能值会影响我们发送给客户端的内容(即最大年龄),还是仅在遵守修改后的条件下才应该这样做?
解决方案
回答
如果客户端明确声明其缓存中可能已包含该页面,则应发送304. 这称为条件GET,它应在请求中包含if-modified-since标头。
基本上,此请求标头包含一个日期,客户端声称从该日期起拥有缓存的副本。我们应该检查此日期之后内容是否已更改,如果未更改,则发送304.
有关RFC中的相关部分,请参见http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25.
回答
这是我的实现方法。该代码已经在多个浏览器上工作了一年多,所以我认为它是可靠的。这基于RFC 2616并观察各种浏览器发送的内容和时间。
这是伪代码:
server_etag = gen_etag_for_this_file(myfile) etag_from_browser = get_header("Etag") if etag_from_browser does not exist: etag_from_browser = get_header("If-None-Match") if the browser has quoted the etag: strip the quotes (e.g. "foo" --> foo) set server_etag into http header if etag_from_browser matches server_etag send 304 return code to browser
这是我处理该问题的服务器逻辑的一个片段。
/* the client should set either Etag or If-None-Match */ /* some clients quote the parm, strip quotes if so */ mketag(etag, &sb); etagin = apr_table_get(r->headers_in, "Etag"); if (etagin == NULL) etagin = apr_table_get(r->headers_in, "If-None-Match"); if (etag != NULL && etag[0] == '"') { int sl; sl = strlen(etag); memmove(etag, etag+1, sl+1); etag[sl-2] = 0; logit(2,"etag=:%s:",etag); } ... apr_table_add(r->headers_out, "ETag", etag); ... if (etagin != NULL && strcmp(etagin, etag) == 0) { /* if the etag matches, we return a 304 */ rc = HTTP_NOT_MODIFIED; }
如果我们需要有关etag生成的帮助,请发布另一个问题,我将挖掘出一些实现此目的的代码。 HTH!
回答
关于缓存控制:
除了提供合理的值外,我们不必担心分发时的缓存控制。基本上,这是告诉浏览器和其他下游实体(例如代理)在超时缓存之前应该经过的最长时间。
回答
带有Not-Modified-Since(" IMS")或者If-Not-Match(" INM")标头的GET或者HEAD请求可能会产生304 Not Modified响应。
为了确定收到这些标头时的处理方式,请想象我们正在处理没有这些条件标头的GET请求。确定该响应中ETag和Last-Modified标头的值是什么,并使用它们来做出决定。希望我们已经建立了自己的系统,从而确定该系统比构造完整的响应所需的成本更低。
如果有一个INM且该标头的值与我们将在ETag中放置的值相同,则响应304.
如果有IMS,并且该标头中的日期值晚于我们要在" Last-Modified"中放置的日期,则响应304.
否则,就好像请求不包含那些标头一样继续。
为了省力地解决问题的第2部分,请弄清楚我们可以在Web应用程序中轻松正确地生成(Expires,ETag和Last-Modified)标头。
有关建议的阅读材料:
http://www.w3.org/Protocols/rfc2616/rfc2616.html
http://www.mnot.net/cache_docs/
回答
我们还处理缓存但安全的资源。如果发送/生成ETAg标头(RFC 2616第13.3节建议我们这样做),则客户端必须在条件请求中使用它(通常在If-None-Match HTTP_IF_NONE_MATCH标头中)。如果发送了Last-Modified标头(再次应该),则应检查If-Modified-Since HTTP_IF_MODIFIED_SINCE标头。如果同时发送,则客户端应该发送两个,但必须发送ETag。还要注意,验证只是定义为检查条件标头与要发送的条件标头是否严格相等。 ?此外,对于范围内的请求(仅请求资源的一部分),将仅使用强大的验证器(例如ETag)。
实际上,由于我们所保护的资源是相当静态的,并且一秒的延迟时间是可以接受的,因此我们将执行以下操作:
- 检查用户是否有权访问所请求的资源?如果不是,请重定向它们或者发送适当的4xx响应。我们将为看起来像黑客尝试或者公然尝试以执行安全性结束运行的请求生成404响应。
- 将If-Modified-Since标头与我们为严格相等而发送的Last-Modified标头进行比较(见下文)? ?如果它们匹配,则发送304 Not Modified响应并退出页面处理
- 使用请求资源的修改时间创建Last-Modified标头查找RFC 2616中的HTTP日期格式
- 发送标题和资源内容以及适当的Content-Type
我们决定避开ETag标头,因为对于我们的目的而言,它太过分了。我想我们也可以只使用日期时间戳作为ETag。如果我们转向真正的ETag系统,则可能会存储资源的计算得出的哈希值并将其用作ETag。
如果资源是根据数据库内容动态生成的,则ETag可能更适合需求,因为它们只是我们认为合适的文本。