HTTP缓存ETAG和Last-Modified

       1) 什么是”Last-Modified”? 
        在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端最后被修改的时间,格式类似这样: 
        Last-Modified: Fri, 12 May 2006 18:53:33 GMT 
        客户端第二次请求此URL时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过: 
        If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT 
        如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而 保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。 
        2) 什么是”Etag”? 
        HTTP 协议规格说明定义ETag为“被请求变量的实体值” (参见 —— 章节 14.19)。 另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单 独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式: 
        ETag: "50b1c1d4f775c61:df3" 
        客户端的查询更新格式是这样的: 
        If-None-Match: W/"50b1c1d4f775c61:df3" 
        如果ETag没改变,则返回状态304然后不返回,这也和Last-Modified一样。本人测试Etag主要在断点下载时比较有用。
      Last-Modified和Etags如何帮助提高性能?
        聪明的开发者会把Last-Modified 和ETags请求的http报头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生 Last-Modified/Etag标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客 户端)缓存。 
        过程如下:
                1. 客户端请求一个页面(A)。 
                2. 服务器返回页面A,并在给A加上一个Last-Modified/ETag。 
                3. 客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。 
                4. 客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。 
                5. 服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。

 

 

一、Etag的使用

客户端和浏览器之间的交互:

+---------+       1         +---------+
|         |---------------->|         |
|         |   2200,OK)   |         |
|         |<----------------|         |
|    客   |    3(Etag)     |    服   |
|         |---------------->|         |
|    户   |    4304)      |    务   |
|         |<----------------|         |
|    端   |    3(强制刷新)  |    端   |
|         |---------------->|         |
|         |   6200,OK)   |         |
|         |<----------------|         |
+---------+                 +---------+
                <Created By Barret Lee>

1. 客户端向服务器请求资源S

2. 服务器返回数据,并带上一个 Etag

3. 客户端再次请求资源S,由于上次服务器给他返回了一个 Etag,这次请求的时候他会带上这个 Etag

4. 服务器发现请求中包含 Etag,判断是否过期,没过期则返回 304 Not Modified

5. 客户端强制刷新(如chrome中ctrl+shift+R刷新页面),请求中剔除 Etag

6. 服务器未发现请求中包含 Etag,返回资源S,并带上一个 Etag

二、代码实现

第一次请求数据:

浏览器在接受到服务器发过来的 Etag 后,会保存下来,下次请求的时候会将它放在请求头中,其 key 值为 If-None-Match。

服务器拿到 If-None-Match 之后,对比之前的 Etag,如果没变,则返回 304 Not Modified.

1. php 中的 Etag

<?php
    $str = "Barret Lee";
    $Etag = md5($str);

    if(array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER) and $_SERVER['HTTP_IF_NONE_MATCH'] == $Etag){
        header("HTTP/1.1 304 Not Modified");
        exit();
    } else {
        header("Etag:" . $Etag);
        echo $str;
    }
?>

Etag 是一个字符串,我们一般使用该请求对应响应输出的 md5 值作为 Etag,可以简单地理解为文件的版本号。在 php 中存在两个获取 md5 的函数,一个是针对字符串的,就是 md5(),然后就是针对文件的, md5_file()

首先判断在请求中是否包含 'HTTP_IF_NONE_MATCH' 这个 key,如果包含并且其值为之前的 md5 值,则返回 304,否则输出 Etag 以及内容。

2. node 中的 Etag

与 php 有些不同,从 $_SERVER 中拿到的内容是经过 apache 包装过的,而 node 获取的数据是最原始的。

var hashStr = "A hash string.";
var hash = require("crypto").createHash('sha1').update(hashStr).digest('base64');

require("http").createServer(function(req, res){
    if(req.headers['if-none-match'] == hash){
        res.writeHead(304);
        res.end();
        return;
    }
    res.writeHead(200, {
        "Etag": hash
    })
    res.write(hashStr);
    res.end();
}).listen(9999);

上面对 hashStr(输出的内容) 进行了简单的处理,并将其作为 Etag 放在 head 中输出,上面的代码一目了然,我就不解释了。

三、小结

Etag 在缓存处理中用的比较广泛,使用它可以减少一些不必要请求的带宽的占用。服务器输出的内容不变,浏览器就应该使用缓存,没必要每次都向服务器端索要数据,造成不必要的浪费。

从上面我们可以看到,如果想拿到 Etag,就必须先拿到要输出的数据,所以 Etag 只能减少带宽的占用,并不能降低服务器的消耗。如果是静态页面,可以判断文件最近一次的修改时间(Last-Modified),获取文件上次修改时间的 消耗比拿到整个数据的消耗要小的多。所以很多时候 Etag 都是配合这 Last-Modified 一起使用的。

上面的 php 和 node 代码演示,很明显的差异就是,node 更加细致,或者说他更加底层,我们可以获取的几乎都是未加修饰的原始数据,从数据量的交互和可操控性来看,我更偏向于 node 的使用。

posted @ 2014-05-14 10:27  蓝翔之云  阅读(2241)  评论(0编辑  收藏  举报