首页 > 代码库 > 关于查询服务器文件是否过期的分析

关于查询服务器文件是否过期的分析

最近在写一个客户端功能函数:向服务器查询本地文件是否过期。以减少在文件内容没变化的情况下,客户端每次访问文件都要重新获取文件内容的几率。

函数的思路为:获取本地文件的修改时间,向服务器查询这个文件是否过期。
由于客户端也许会不想保存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.1Host: 192.168.8.18Accept: */*If-Modified-Since: Tue, 06 Jan 2015 03:31:26 GMT回复包:HTTP/1.1 200 OKServer: nginx/1.5.10Date: Tue, 06 Jan 2015 03:31:27 GMTContent-Type: text/htmlContent-Length: 135Last-Modified: Mon, 05 Jan 2015 08:40:56 GMTConnection: keep-aliveETag: "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.10\src\http\modules\ngx_http_not_modified_filter_module.cstatic ngx_int_tngx_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)为truereturn ngx_http_next_header_filter(r);}

 

那么继续看ngx_http_test_if_modified函数

static ngx_uint_tngx_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 ModifiedServer: nginx/1.5.10Date: Tue, 06 Jan 2015 02:57:40 GMTLast-Modified: Mon, 05 Jan 2015 08:40:56 GMTConnection: keep-aliveETag: "54aa4e18-87"

本文只是分析了这个情况出现的原因,但是如果服务器是别人的,或者由于某些策略不便于这样配置,
可以通过另外的方法实现这个函数功能:
1.在服务器返回200的情况下,将服务器答复包中的Last-Modified时间,与本地时间对比,判断文件是否已经修改。

 

关于查询服务器文件是否过期的分析