最近在写一个客户端功能函数:向服务器查询本地文件是否过期。以减少在文件内容没变化的情况下,客户端每次访问文件都要重新获取文件内容的几率。
函数的思路为:获取本地文件的修改时间,向服务器查询这个文件是否过期。
由于客户端也许会不想保存ETag,而ETag的生成算法在不同的服务器也许不一样,
那么似乎只有通过If-Modified-Since来实现这个功能。
函数的流程大致如下:
1.获取文件最后修改时间
2.拼装If-Modified-Since http头
3.向服务器发送http请求
4.根据服务器的http code判断本地文件是否过期
本次测试服务器系统为centos6.4,httpserver为nginx 1.5.1,默认配置。
写一段测试代码进行测试:
int ShIsRemoteFileChange(std::string szUrl, int nTimeOut) { CURL *curl_handle; curl_global_init(CURL_GLOBAL_ALL); curl_handle = curl_easy_init(); std::string szBuffer; curl_easy_setopt(curl_handle, CURLOPT_URL, szUrl.c_str()); curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, nTimeOut); curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteBufferCB); curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&szBuffer); //添加头部 struct curl_slist *headerlist = NULL; //获取时间 time_t t = time(0) - 8 * 60 * 60; char tmp[64]; strftime( tmp, sizeof(tmp), "%a, %d %b %Y %H:%M:%S GMT", localtime(&t) ); std::string szHeader1 = "If-Modified-Since: "; szHeader1.append(tmp); headerlist = curl_slist_append(headerlist, szHeader1.c_str()); curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headerlist); curl_easy_perform(curl_handle); curl_easy_cleanup(curl_handle); curl_slist_free_all(headerlist); return 0; }
代码中获取的时间并非真正的文件时间,而是我取的当前时间做测试。使用GMT时间。
下面看一下抓包情况:
请求包: GET /web/test.html HTTP/1.1 Host: 192.168.8.18 Accept: */* If-Modified-Since: Tue, 06 Jan 2015 03:31:26 GMT 回复包: HTTP/1.1 200 OK Server: nginx/1.5.10 Date: Tue, 06 Jan 2015 03:31:27 GMT Content-Type: text/html Content-Length: 135 Last-Modified: Mon, 05 Jan 2015 08:40:56 GMT Connection: keep-alive ETag: "54aa4e18-87" Accept-Ranges: bytes
回复包中的http code为200,还原一下对话如下:
客户端:/web/test.html这个文件在Tue, 06 Jan 2015 03:31:26 GMT之后有修改吗?
服务器:上次修改时间为Mon, 05 Jan 2015 08:40:56 GMT,并且返回了文件内容。
(注明:服务器在http code为200时,会返回文件内容,304则不会)
那么问题就来了:为毛在时间 Tue, 06 Jan 2015 03:31:26 GMT 之后没有修改,服务器还要返回文件内容呢?
这跟缓存机制中说好的不一样啊!
百思不得骑姐,那么看看nginx的代码吧。
关于这功能的代码位于:nginx-1.5.10srchttpmodules gx_http_not_modified_filter_module.c static ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r) { if (r->headers_out.status != NGX_HTTP_OK || r != r->main || r->headers_out.last_modified_time == -1) { return ngx_http_next_header_filter(r); } //我们没用到这个头,跳过 if (r->headers_in.if_unmodified_since && !ngx_http_test_if_unmodified(r)) { return ngx_http_filter_finalize_request(r, NULL, NGX_HTTP_PRECONDITION_FAILED); } //我们没用到这个头,跳过 if (r->headers_in.if_match && !ngx_http_test_if_match(r, r->headers_in.if_match)) { return ngx_http_filter_finalize_request(r, NULL, NGX_HTTP_PRECONDITION_FAILED); } //由于使用了r->headers_in.if_modified_since,进入这个函数 if (r->headers_in.if_modified_since || r->headers_in.if_none_match) { //执行进入了这个if if (r->headers_in.if_modified_since && ngx_http_test_if_modified(r)) { //在这里出去了,导致返回值不为304 //r->headers_in.if_modified_since肯定为true //ngx_http_test_if_modified(r)为true return ngx_http_next_header_filter(r); } if (r->headers_in.if_none_match && !ngx_http_test_if_match(r, r->headers_in.if_none_match)) { return ngx_http_next_header_filter(r); } //没返回304,说明没执行到这 /* not modified */ r->headers_out.status = NGX_HTTP_NOT_MODIFIED; r->headers_out.status_line.len = 0; r->headers_out.content_type.len = 0; ngx_http_clear_content_length(r); ngx_http_clear_accept_ranges(r); if (r->headers_out.content_encoding) { r->headers_out.content_encoding->hash = 0; r->headers_out.content_encoding = NULL; } return ngx_http_next_header_filter(r); } return ngx_http_next_header_filter(r); }
通过看代码以及调试发现,没返回304是因为代码在这里跳出去了:
if (r->headers_in.if_modified_since && ngx_http_test_if_modified(r)) { //在这里出去了,导致返回值不为304 //r->headers_in.if_modified_since肯定为true //ngx_http_test_if_modified(r)为true return ngx_http_next_header_filter(r); }
那么继续看ngx_http_test_if_modified函数
static ngx_uint_t ngx_http_test_if_modified(ngx_http_request_t *r) { time_t ims; ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); //不进入 if (clcf->if_modified_since == NGX_HTTP_IMS_OFF) { return 1; } //获取客户端发送的if_modified_since时间 ims = ngx_http_parse_time(r->headers_in.if_modified_since->value.data, r->headers_in.if_modified_since->value.len); //打印日志 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http ims:%d lm:%d", ims, r->headers_out.last_modified_time); //因为返回为1,所以这里没进入 if (ims == r->headers_out.last_modified_time) { return 0; } //因为返回为1,肯定进入这里了 if (clcf->if_modified_since == NGX_HTTP_IMS_EXACT || ims < r->headers_out.last_modified_time) { return 1; } return 0; }
这个函数中主要看这段代码: if (clcf->if_modified_since == NGX_HTTP_IMS_EXACT || ims < r->headers_out.last_modified_time) { return 1; }
ims为客户端发送的时间,由于客户端发送的时间大于服务器文件的最后修改时间,
所以ims < r->headers_out.last_modified_time为 false
那么可以认定问题在clcf->if_modified_since == NGX_HTTP_IMS_EXACT
clcf->if_modified_since是nginx配置文件中对于if_modified_since的配置
改配置描述如下:
if_modified_since 语法:if_modified_since [off|exact|before] 默认值:if_modified_since exact 使用字段:http, server, location 指令(0.7.24)定义如何将文件最后修改时间与请求头中的”If-Modified-Since”时间相比较。 • off :不检查请求头中的”If-Modified-Since”(0.7.34)。 • exact:精确匹配 • before:文件修改时间应小于请求头中的”If-Modified-Since”时间
查看配置文件,发现If-Modified-Since并未配置,nginx的默认配置为1.
修改配置文件,在server段中加入:
if_modified_since before;
Nginx重载配置。
再次测试已经成功返回304,抓包内容如下:
客户端请求同上不变 服务器端答复如下: HTTP/1.1 304 Not Modified Server: nginx/1.5.10 Date: Tue, 06 Jan 2015 02:57:40 GMT Last-Modified: Mon, 05 Jan 2015 08:40:56 GMT Connection: keep-alive ETag: "54aa4e18-87"
本文只是分析了这个情况出现的原因,但是如果服务器是别人的,或者由于某些策略不便于这样配置,
可以通过另外的方法实现这个函数功能:
1.在服务器返回200的情况下,将服务器答复包中的Last-Modified时间,与本地时间对比,判断文件是否已经修改。