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

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

函数的思路为:获取本地文件的修改时间,向服务器查询这个文件是否过期。
由于客户端也许会不想保存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时间,与本地时间对比,判断文件是否已经修改。

原文地址:https://www.cnblogs.com/solohac/p/4205730.html