解决 sub_filter 不能替换 Gzip 过的内容

在微信小程序 web-view 跳转外部链接?甲方的需求千奇百怪,好在办法总比困难多。不是只允许业务域名吗,不大可能在别人网站放一个 txt 校验吧?此时反代不失为解决业务域名限制的一个办法,总之一切为目标服务。

⚠️ 就算绕开了限制,由于内容经你方服务器转发,你方也须承担责任。另请注意,撰文时个人类型的小程序暂不支持内嵌网页。本文假设我方域名 yourdomain.com,对方域名 example.com。

1) proxy_pass 转发下?感觉事情没那么简单。

1
2
3
4
5
6
7
location / {
...
proxy_pass https://example.com;
proxy_set_header Host example.com;
proxy_set_header Referer https://example.com;
...
}
  1. 尝试修改绝对 URL,将对方域名变成我方域名,避免打开非业务域名。
1
2
3
4
5
6
7
8
9
location / {
...
proxy_set_header Accept-Encoding '';

sub_filter_types *;
sub_filter_once off;
sub_filter 'example.com' 'yourdomain.com';
...
}
  1. 有些网站无视 Accept-Encoding: '',不管三七二十一,统统返回压过的。sub_filter 又只能处理未经压缩的内容,所以替换失败。

先来看人家怎么办到的。第一个 location / 是对方 frontend 端的配置,向 upstream 请求压缩过的资料;第二个 location / 则是对方 backend 端的配置,开启 Gzip,并允许在 HTTP/1.0 压缩。

1
2
3
4
5
location / {
...
proxy_set_header Accept-Encoding 'gzip';
...
}

前文提到,Nginx 默认以 HTTP/1.0 连接上游,偏偏 gzip_http_version 缺省值是 1.1,表现为对后端不启用 Gzip,所以…

1
2
3
4
5
6
location / {
...
gzip on;
gzip_http_version 1.0;
...
}
  1. 思路清晰了有没有?先解压、再替换就好了呗。前人留下许多牛掰的方法。Maxim Dounin 出于减省回源带宽的考量,写了个 gunzip filter 模块。姚伟斌又撸了个 patch,使 upstream 返回内容总是被解压。还有人直接修改 Nginx 源码以达到目的。

另一种 trick (感谢 ryd994 大大的原帖) 比较直观。做两次 proxy_pass,先反代源站 gzip 过的内容,再反代一次即可获得未压缩的内容。这里假设第一次的 location 在 forward 目录,第二次的在根目录。

⚠️ 上面的 gunzip on 和下面的 proxy_set_header Accept-Encoding '' 互为替代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
location /forward {
...
proxy_pass https://example.com;
# proxy_set_header Accept-Encoding 'gzip';
# gunzip on;
...
}

location / {
...
proxy_pass https://yourdomain.com/forward;
proxy_set_header Accept-Encoding '';

sub_filter_types *;
sub_filter_once off;
sub_filter 'example.com' 'yourdomain.com';
...
}

⚠️ 即使您在 Step 2 透过禁用压缩的方式顺利替换,也可以试看看 Step 4。这种需求很能吃出站流量,带宽嘛,总是省一点算一点。