Apache Web 服务器对 ETag 和 Deflate 的处理

ETag

  1. HTTP请求头中如果包含 If-None-Match: "44d0ac3fd1f00"
  2. 服务器如果发现内容没有发生变化,可以返回HTTP/1.1 304 Not Modified

这样做的主要好处是可以节省网络带宽。

gzip 和 deflate

  1. HTTP请求头如果包含 Accept-Encoding: gzip,deflate
  2. 服务器可以对响应内容进行压缩以减少网络带宽。

ETag + Deflate

两者一起用的时候,有一个容易忽略的问题。

  1. ETag 通常由内容生成程序负责
  2. Deflate 通常由服务器负责进行
  3. 两种情况下理论上应该产生不同的ETag值

Apache 服务器的做法

Apache最早在启用Deflate的情况下不改变已经生成好的ETag值。 这后来被认为是一个bug:39727 ``` 对此bug的修正方法是,当Deflate启用的时候,改变ETag的值。

If-None-Match: "44d0ac3fd1f00"-gzip

可以看到,是通过简单追加 -gzip 来实现的。但这引入了一个新的bug:45023

因为理论上-gzip应该加在引号里面。否则,根据规范,不能触发正确的内容一致性检查。

修正办法很简单,变成下面这样就可以了。

If-None-Match: "44d0ac3fd1f00-gzip"

但如果只是简单地如此修改,却还是有问题:浏览器再次发送请求时,用于检查ETag一致性的代码会认为内容不一样。因为ETag的内容来自用户的程序,此时并没有添加"-gzip"后缀,简单地与带有后缀的请求头中的值进行比较不能得出一致的结论。

修正办法,自然也很直接,就是去掉-gzip后缀后再进行比较。但 Apache 服务器器由于某种原因一直没有修正这个问题。

The Workaround

解决问题的思路是很直接的,就是需要编辑相应的头部字段

RequestHeader  edit "If-None-Match" "^(.*)-gzip$" "$1"
Header edit "ETag" "^(.*[^g][^z][^i][^p])$" "$1-gzip"
  • RequestHeader修改请求头里面的If-None-Match
  • Header edit修改相应里面的ETag

更进一步

又有人发现,根据rfc2616If-None-Match字段是可以包含多个值的,那么把有后缀的和没后缀的都放进去好了。

RequestHeader edit "If-None-Match" '^"((.*)-gzip)"$' '"$1", "$2"'

不必要的静态文件 ETag

对于如此麻烦的ETag问题,如果只是考虑静态文件的话,其实只要简单的关掉ETag功能就好了。

FileETag: None

对于性能的损失也不用担心,因为我们还有Last-Modified这个选项起作用。

实际上FileETag也是根据INode MTime Size这三个的组合来生成ETag值,而通常情况下文件的变动都会引起MTime的变化,进而引起Last-Modified的变化。

自力更生

凡事自己能动手的,解决起来最方便。

如果服务器端的生成内容的程序可以自己判断ETag并直接生成HTTP/1.1 304 Not Modified响应的话,这个问题也可以获得解决。

参考资源