一些使用HTTP头提高性能的方法
2010 1 29 09:57 PM 2771次查看
实际上说是提高性能,主要就围绕缓存而来,因此不得不了解HTTP协议的缓存机制。
由于说用户代理太麻烦,下面我就直接用浏览器来代替了,因为用户代理可以不按HTTP协议来(或者只实现一部分),而浏览器一般是遵循的(不然没人会去用这个浏览器)。
对于一次成功的请求(状态码是200、203、206、300、301或410的请求),如果没有要求浏览器不缓存,那么浏览器可以缓存这次响应的,并用于后续的访问。
而其他请求(特别是302和307这2个跳转状态),浏览器在没有被要求缓存时,是不会去缓存的。
其实想想也知道,如果一个页面访问时需要登录,那么服务器会用302或307状态让用户跳转到登录页面。如果这个响应也被缓存了,那用户登录完后返回到原页面时,又会从缓存里取出这个响应;而由于发现是跳转状态,就又跳转到登录页面了,于是倒霉的用户就永远陷入登录的死循环了。
上述说的是没有设置缓存策略的情况,如果服务器强行要求了,那就变得不一样了。
在HTTP 1.1协议里,Cache-Control这个头就是用来控制缓存策略的。如果它的值为no-cache,浏览器就不会去缓存;而如果是max-age=xxx,那么浏览器就会缓存xxx秒。
但是HTTP 1.0协议里没有这个玩意,所以可以用Pragma: no-cache来要求不缓存,效果基本是相同的,但是功能没Cache-Control多。
不过IE处理这2个头仍然有bug,你可以参考《解决IE 6的缓存bug》这篇文章来处理。
当一个响应被浏览器缓存后,浏览器就可以直接使用缓存里的响应,而不去访问服务器端了。
如果此后服务器端的页面更新了,浏览器也不会获取新的页面,这就会造成问题了。
最简单的解决办法是让用户手动刷新(一般是按F5键),这样浏览器就会跳过缓存,直接向服务器端获取。但是很显然,你的用户并不知道你什么时候更新页面,所以不会没事就刷新的。
另一种办法是设置缓存的过期时间,在响应头里增加一个Expires字段即可。一旦超过这个时间,浏览器也会跳过缓存,直接访问服务器。
但这又存在一个性能问题:假如访问服务器后服务器并没更新,缓存里的内容仍然是最新的,那重新传一遍响应数据就浪费带宽了。
实际上HTTP协议也考虑到了这个情况,提供了一个304状态码,用来表明响应数据没有更新。一个状态码为304的响应是不带body的,也就是Content-Length为0,这就节约了传输带宽。
当浏览器收到304状态的响应后,就会重新从缓存里取出旧的响应,将新的响应头和旧的响应体合并,组成一个新的响应。
因此如果你的响应本来就没有响应体,例如204、302等,那么就不需要用304状态码。它的用处主要是来取代200,但如果错误页面也比较大的话,我觉得取代400、404、500、503等也是可行的。虽然RFC里只明确提到200可以用304取代,但实际上除了搜索引擎,基本没人管你是不是用正确的错误状态码的。而搜索引擎是不会去缓存错误页面,并问你是否更新了,所以也不用担心。
说到这个304,还得说下服务器是怎么判断浏览器的缓存和服务器端是一致的。
最简单的办法是输出一个Last-Modified头,浏览器收到后就会知道这个响应实体最后被更改的时间。如果浏览器认为它的缓存可能过期了,需要和服务器端验证一下的话,它就会在请求头里添加一个If-Modified-Since头,值就是它接收到的Last-Modified。服务器收到后,比较一下时间,如果发现时间没更新,就知道响应没被更改,就可以发出304状态了。
另一种方法是输出ETag头,这个头用于唯一标识一个实体。一般可以用一些hash函数来生成,例如md5和sha。相应的,浏览器会发出If-None-Match头,里面的值就是收到的ETag。服务器接收到后,重新用hash函数计算一下,比较这个ETag是否改变了,如果没改变,那就可以发出304状态。
一般来说是不需要同时使用这2种方法的,因为同时使用时必须都符合才能发出304状态,这会导致检查的时间增加,而实际上又没有非要这样做的必要。
此外,Content-Location和Content-MD5也可以用于标识实体,但一般没人去用。
还有些时候,用户不希望浏览器发出这些标识实体的请求头,那就可以使用强制刷新(IE和Firefox是按shift+F5,但Chrome不支持)。
一般只有在调试和测试时才会用到这个方法。
以上是服务器直接和浏览器打交道的情况,实际上浏览器还可以通过代理服务器来访问浏览器,因此你可能还得设置代理服务器的策略(特别是用反向代理时)。
前面所说的4种标识实体的字段,一个透明的代理服务器是不影响更改和增加的,这就保证了服务器可以正确地依赖这些字段。此外,Content-Encoding、Content-Range和Content-Type也是不允许修改和新增的。Expires不允许被修改,但如果没提供的话,代理服务器允许自己设置,但必须和Date相同(也就表示接收到立即过期)。
看上去好像不需要担心代理服务器会带来什么危害,但实际上代理也可以使用缓存,所以还得告诉它什么页面是可以缓存的。
一般情况下,代理服务器可以缓存任意页面。而如果服务器发出了Vary头,那么代理服务器就必须检查这个头里指定的字段。如果这些字段都没有发生更改,那就可以用代理服务器端的缓存构造一个响应,发送给浏览器;否则就必须重新从服务器获取新的响应。
Google的服务器会要求检查Accept-Language、Cookie和Referer,这基本上可以唯一标识一个用户了,值得借鉴。
而如果Vary被设成*了,那么代理服务器的检查必定失败,因此必须每次都访问原服务器。
此外,代理服务器也可以使用304状态码,效果和浏览器直接访问服务器差不多。
最后,虽然程序中处理响应头一般是用多值字典,但实际上只有Set-Cookie和Warning字段会出现多次(至少我没发现其他字段可以出现多次),所以用单值字典来处理大部分头,而用单独的列表来处理Set-Cookie,Warning由于基本用不到,所以基本上不用实现。
0条评论 你不来一发么↓