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


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

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

  • 相关阅读:
    md笔记——HTTP知识
    百万表格难题
    微信接口改良
    md笔记——正则学习
    md笔记——编程术语
    md笔记——微信JS接口
    md笔记——使用 @font-face 引入你喜欢的字体
    博客一年记
    “挨踢”的伙食怎样?
    比尔·盖茨早年
  • 原文地址:https://www.cnblogs.com/solohac/p/4205730.html
Copyright © 2020-2023  润新知