在nginx的gzip模块上掉了个坑

今天在做服务迁移服务器,将后台整个迁移到云服务器上,前期很顺利,客户端都能正常跑起来,接口和后台管理,推送,异步队列等都正常运行。但是发现一个非常奇怪的问题,搞了一下午没解决。

ios客户端在拉取接口后,第一次成功,第二次再刷新,就没有任何网络请求,很奇怪。

其它接口也是,成功后再刷新数据就不会变,取缓存的数据。因为抓包发现没网络请求,所以在后台把数据改了后,刷新客户端看不到新的数据。

一旦切回到线上,这种现象就好了。andorid客户端没有这个问题。

刚开始以为是域名的问题,于是保留原域名,把原域名的ip通过修改host指向新服务器,用fiddler抓包,发现问题还是有,说明跟域名无关,是新服务有问题。

通过单步调试ios程序,也没发现什么异常,网络请求的调试代码也都执行了的,但就是没有网络请求,可能是ios底层库相关问题。

总而言之,搞了很久,到晚上九点多,最终发现,客户端请求没有带Etag值。

因为我们用tornado做服务器,每个http接口返回都会有一个Etag值在header里面,客户端请求的时候带上Etag,从而实现数据缓存,减少传输。但是奇怪,客户端请求新服务的时候,一律没有带Etag。

最终发现,其实是我们服务器返回就根本没有Etag。因为我们新服务用的nginx版本,是根据最新的nginx源代码修改image_filter插件的代码后,重新编译的,跟以前我们用的不一样。

能过抓包分析接口返回的header,发现缓存控制多了 Catch-Control: max-age 这个头,可以修改nginx配置去掉。但不是这个原因导致没有Etag值,最终原因是Gzip!

当去掉Gzip后,返回就有Etag值了,于是就怀疑是nginx在做gzip的时候,将tornado返回的Etag头给干掉了。

后来查资料发现,新版的nginx确实有这一行为,在nginx源码中,src/http/modules/ngx_http_gzip_filter_module.c 第306行左右,函数:

static ngx_int_t
ngx_http_gzip_header_filter(ngx_http_request_t *r)

....................

    r->main_filter_need_in_memory = 1;

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);
    ngx_http_clear_etag(r);                             //就是这一行,把返回的etag头信息干掉了

    return ngx_http_next_header_filter(r);
}

问题找到了,解决办法就很简单了,把那行代码注释掉,再重新编译个nginx使用就好了。

就算是个疑难杂症,搞了好久才查到原因,主要是刚开始方向不对,没人会想到是nginx底层出的问题,走了不少弯路。同时出吐槽一下ios底层网络库,缺少了etag就不给网络请求了,也不给任何警告提示之类的,真是的!android无问题。