Lighttpd1.4.20源码分析之etag.c(h) -------HTTP/1.1中的Etag域

etag的全称是entity tag(标记实体值),在RFC2616中关于etag的定义如下:
The ETag response-header field provides the current value of the entity tag for the requested variant. The headers used with entity tags are described in sections 14.24, 14.26 and 14.44. The entity tag MAY be used for comparison with other entities from the same resource(see section 13.3.3).
  ETag = "ETag" ":" entity-tag
Examples:
     ETag: "xyzzy"
     ETag: W/"xyzzy" (前面的W/表示这个是个弱Etag)
     ETag: ""
巨长的RFC2616对Etag的描述就上面这么多。意思就是说Etag域提供了请求变体的一个实体标记值。这个值可以和If-Match和If-No-Match一起使用。RFC2616中对Etag的唯一要求就是它是一个双引号包围的字符串,至于怎么生成这个字符串以及怎么使用,由应用程序决定。
下面说一说在服务器程序中,一般是怎么使用Etag的(这个东西用好了还是很不错的。。。):
把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由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。常见的是使用If-None-Match.
请求一个文件的流程可能如下:
====第一次请求===
1.客户端发起 HTTP GET 请求一个文件;
2.服务器处理请求,返回文件内容和一堆Header,当然包括Etag(例如"2e681a-6-5d044840")(假设服务器支持Etag生成和已经开启了Etag).状态码200。
====第二次请求===
1.客户端发起 HTTP GET 请求一个文件,注意这个时候客户端同时发送一个If-None-Match头,这个头的内容就是第一次请求时服务器返回的Etag:2e681a-6-5d044840
2.服务器判断发送过来的Etag和计算出来的Etag匹配,因此If-None-Match为False,不返回200,返回304,客户端继续使用本地缓存;

流程很简单,问题是,如果服务器又设置了Cache-Control:max-age和Expires呢,怎么办?答案是同时使用,也就是说在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,服务器才能返回304.
另外,使用Etag比使用Last-Modified接合If-Modified-Sience更有优势。如果一些文件经常被修改的不是文件的内容,而是文件的属性,如:文件的修改时间等。那么就没有必要重新发送文件,此时,Last-Modified不能判断其内容是否修改,所以只会重新发送。而使用Etag,可以通过检验如文件的i节点号,大小等来判断是否重传。
那么,在Lighttpd中,Etag到底是个什么东东呢?且听我慢慢道来。。。
在头文件Etag.h中定义了一个枚举类型
1 typedef enum 
2 
3     ETAG_USE_INODE = 1,      //包含文件的i节点号。
4     ETAG_USE_MTIME = 2,      //包含文件最后一次修改的时间。
5     ETAG_USE_SIZE = 4        //包含文件的byte数。
6 } etag_flags_t;
这个枚举类型决定了Etag中所包含的东西。注意,三个枚举量被定义为1,2,4.这样可以通过或的方式来包含多个内容。如::ETAG_USE_INODE | ETAG_USE_SIZE,表示Etag中既包含文件的i节点号,也包含文件的大小。
在头文件etag.h中,只声明了三个函数:
1、int etag_is_equal(buffer * etag, const char *matches);
这个是判断etag的内容是否和matches相同。相同返回1,不同返回0.
2、int etag_create(buffer * etag, struct stat *st, etag_flags_t flags);
这个是根据flags和st生成一个etag,存放在etag中传出。st是struct stat,在文件sys/stat.h中定义,用来表示文件的状态(目录也是文件哦)。可以使用stat函数获取一个文件的文件状态。具体操作请读者自己查阅。对于flags的设置,Lighttpd通过读取配置文件信息,设置flags。
3、int etag_mutate(buffer * mut, buffer * etag);
这个函数个etag生成一个哈希值,存放在mut中,并用双引号包围。里面使用的哈希方法是上一个结果左移五位异或上一个结果右移27位异或下一个字符,得到下一个结果。在实际应用中,这个哈希方法是很可靠的,基本上不会得到两个不同的字符串得到相同的结果,也就是基本不会出现碰撞。

三个函数的具体实现如下:

 1 int etag_is_equal(buffer * etag, const char *matches)
 2 {
 3     if (etag && !buffer_is_empty(etag) && 0 == strcmp(etag->ptr, matches))
 4     {
 5         return 1;
 6     }
 7     return 0;
 8 }
 9 
10 int etag_create(buffer * etag, struct stat *st, etag_flags_t flags)
11 {
12     if (0 == flags)
13         return 0;
14     buffer_reset(etag);
15     if (flags & ETAG_USE_INODE) //i节点号(serial number)
16     {
17         buffer_append_off_t(etag, st->st_ino);
18         buffer_append_string_len(etag, CONST_STR_LEN("-"));
19     }
20     if (flags & ETAG_USE_SIZE) //普通文件的byte数
21     {
22         buffer_append_off_t(etag, st->st_size);
23         buffer_append_string_len(etag, CONST_STR_LEN("-"));
24     }
25     if (flags & ETAG_USE_MTIME) //文件最后一次修改的时间。
26     {
27         buffer_append_long(etag, st->st_mtime);
28     }
29     return 0;
30 }
31 
32 int etag_mutate(buffer * mut, buffer * etag)
33 {
34     size_t i;
35     uint32_t h;
36     //计算哈希值。
37     for (h = 0, i = 0; i < etag->used; ++i)
38     {
39         h = (h << 5^ (h >> 27^ (etag->ptr[i]);
40     }
41     buffer_reset(mut);
42     buffer_copy_string_len(mut, CONST_STR_LEN("\""));
43     buffer_append_long(mut, h);
44     buffer_append_string_len(mut, CONST_STR_LEN("\""));
45     return 0;
46 }
47 

参考:
   http://www.hudong.com/wiki/Etag
   http://www.rfc-editor.org/rfc/rfc2616.txt

 

posted @ 2009-11-16 21:53  kernel@hcy  阅读(1761)  评论(0编辑  收藏