弹霄博科

小谈谈一亩三分地



NGINX 反向代理导致客户端缓存标记丢失的两种情况


发布日期: 2022/08/12
更新日期: 2022/08/12
作者: 小谈谈
分类: NGINX
标签: nginx, sub_filter, gzip, etag, last-modified
阅读时间: 2 分钟
字数: 446 字


问题引入

遇到一个很奇怪的问题,后端站点使用 etag 和 last-modified 为静态资源增加了客户端缓存,经过 NGINX 代理之后,etag 和 last-modified 标记到客户端就消失了,导致客户端无法使用浏览器缓存。

排查和结论

1、是否因为 gzip 导致 etag 丢失,在某些版本(1.3.3~1.7.3)的 NGINX 中,当开启 gzip 时,会导致 etag 头丢失。

官方给出的解释是压缩以后,文件大小可能被修改,会导致文件发生变化,故考虑将 etag 头丢掉。

源码分析:./release-1.3.3/src/http/modules/ngxhttpgzipfiltermodule.c

```
ngx_str_set(&h->key, "Content-Encoding");
ngx_str_set(&h->value, "gzip");
r->headers_out.content_encoding = h;
r->main_filter_need_in_memory = 1;
ngx_http_clear_content_length(r);
ngx_http_clear_accept_ranges(r);
# remove etag
ngx_http_clear_etag(r);
```

自 NGINX 1.7.3 版本以后,当在 gzip 中遇到 etag 头,会将强 etag 自动转换为弱 etag(weak etag),如果遇到弱 etag,则不作处理原样返回。

源码分析:./release-1.7.3/src/http/modules/ngxhttpgzipfiltermodule.c

```
ngx_str_set(&h->key, "Content-Encoding");
ngx_str_set(&h->value, "gzip");
r->headers_out.content_encoding = h;
r->main_filter_need_in_memory = 1;
ngx_http_clear_content_length(r);
ngx_http_clear_accept_ranges(r);
# 弱化 etag 标记
ngx_http_weak_etag(r);
```

2、是否因为引入了 subfilter 模块导致的 etag 头丢失,自 1.5.1 版本起 NGINX 引入了一个配置项允许在替换期间保留原始响应中的 last-modified 头字段,以便于响应缓存。但默认情况下,在 subfilter 模块处理过程中修改响应内容时,将删除 last-modified 头字段。

配置项目 subfilterlastmodified on | off; 默认值 subfilterlastmodified off; 可出现的位置 http,server,location

源码分析:./release-1.5.11/src/http/modules/ngxhttpsubfiltermodule.c

```
if (r == r->main) {
ngx_http_clear_content_length(r);
ngx_http_clear_etag(r);
if (!slcf->last_modified) {
ngx_http_clear_last_modified(r);
}
}
```

自 NGINX 1.7.3 优化了 subfilter 模块对 etag 和 last-modified 的处理逻辑。

源码分析:./release-1.7.0/src/http/modules/ngxhttpsubfiltermodule.c

```
if (r == r->main) {
ngx_http_clear_content_length(r);
# sub_filter_last_modified off
if (!slcf->last_modified) {
ngx_http_clear_last_modified(r);
ngx_http_clear_etag(r);
# sub_filter_last_modified on
} else {
ngx_http_weak_etag(r);
}
}
```

clear 处理函数分析

以 nginx-release-1.7.0 为例,会将 hash 置 0,标记置空。

源码位置:./release-1.7.3/src/http/ngxhttpcoremodule.h

```
#define ngx_http_clear_last_modified(r) \
\
r->headers_out.last_modified_time = -1; \
if (r->headers_out.last_modified) { \
r->headers_out.last_modified->hash = 0; \
r->headers_out.last_modified = NULL; \
}

#define ngx_http_clear_etag(r) \
\
if (r->headers_out.etag) { \
r->headers_out.etag->hash = 0; \
r->headers_out.etag = NULL; \
}
```

建议

1、后端使用客户端强缓存(例如 cache-control)方式能避免该问题。 2、调整 NGINX 版本,避免 NGINX 版本特性。