谜题
朋友们,又到了解题时间,假设有这么一个场景,现在我们有一个Nginx网关作为公网暴露面,用于代理内网的web服务,本身配置如下:
ini
http {
server {
listen 8080;
server_name localhost;
access_log logs/host.access.log;
location / {
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
}
也就是说原来的Nginx网关,有一个所有请求默认入口,所有localhost:8080的请求,都兜底转发到这里。灰常简单的配置,现在有一个新需求了,小明有一个管理平台,假设地址是http://192.168.134.35:18080/,也想通过这个Nginx进行代理,但为了业务区分,Nginx网关需要新增一个local前缀,也就是类似这样的配置,这样以来就不会干扰其他业务了,OA,ERP啥的用不同前缀区分,但公网的域名还是一个:
ini
http {
server {
listen 8080;
server_name localhost;
access_log logs/host.access.log;
location / {
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# TODO
location ^~ /xiaoming/ {
# TODO
}
}
}
但其实这个方案是不行的,因为除了第一个请求会显式带http://localhost:8080/xiaoming/外,剩余的js请求可能是不带的,因为会存在js硬编码的情况,让我举个例子,慢慢分析。
1. 环境准备
为了使例子更加真实,我下载了一个github上star很多的管理平台vue-pure-admin,
bash
docker pull freddyshen/vue-pure-admin:latest
docker run -dp 18080:80 --name pure-admin freddyshen/vue-pure-admin:latest
# 打开页面 http://192.168.134.35:18080/


可以看出,功能都有很全了。好了,我们现在要代理这个网站,并且要以前缀的形式http://localhost:8080/xiaoming/
2. 方案一:rewrite+跳转
我们直接配置为这样的Nginx配置看看可不可以:
ini
http {
server {
listen 8080;
server_name localhost;
access_log logs/host.access.log;
location / {
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location ^~ /xiaoming/ {
proxy_pass http://192.168.134.35:18080/;
proxy_set_header Host $host;
}
}
}
我开启一个无痕窗口和wireshark抓包,然后输入反代的地址:http://127.0.0.1:8080/xiaoming/
发现无法加载起来,抓包可以看出,除了第一个请求,其余请求都没有带这个xiaoming的前缀,这样导致路由不到了:

所以,我们的直觉就是需要让他后续加上这个前缀,那么就是在每个响应的报文中,把他的接口地址给改了,比如他的第一个index.html返回的是:
bash
... ...
<script type="module" crossorigin src="/static/js/index-Hy0LnPHS.js"></script>
<link rel="stylesheet" crossorigin href="/static/css/index-C0mLDhWK.css">
<link rel="stylesheet" href="/assets/layout-theme-light.css" id="theme-link-tag">
</head>
... ...
通过ngnix rewrite功能给他给改了,sub_filter关键字可以对reponse的内容进行简单的字符串替换:
bash
location ^~ /xiaoming/ {
proxy_pass http://192.168.134.35:18080/;
proxy_set_header Host $host;
proxy_redirect http://$host/ http://$host/xiaoming/;
sub_filter_once off;
sub_filter_types application/json text/javascript;
sub_filter 'src="/' 'src="/xiaoming/';
sub_filter 'href="/' 'href="/xiaoming/';
}
好滴,我们继续打开代理页面:

oh no依然不行,我们查看抓包和Nginx error log可以清楚的看到问题所在:

其余都改了,为啥有一个特立独行的呢?我逐一查看响应包就会发现,他是一个js中请求的一个url:

typescript
er({method:"get",url:`${qPe}platform-config.json`}).then(({data:t})=>{let n=e.config.globalProperties.$config;return e&&n&&typeof t=="object"&&(n=Object.assign(n,t),e.config.globalProperties.$config=n,YPe(n)),n}).catch(()=>{throw"public platform-config.json"})}),Sl=()=>u5().ResponsiveStorageNameSpace,
这种情况就不好通过subfilter rewrite了,风险很大,我们不知道哪里有url。我们虽然可以用跳转的方式比如,他请求的不是/xiaoming开头的,我就自动302给他跳转到/xiaoming,这样虽然可以解决,但是这个Nginx只能给xiaoming独享了,后面假设有别的业务/xiaohong就不行了:
bash
location / {
# xiaoming独享了,xiaohong该怎么办
if ($request_uri !~ ^/xiaoming/) {
rewrite ^/(.*)$ /xiaoming/$1 permanent;
}
proxy_pass http://192.168.134.35:18080/;
proxy_set_header Host $host;
}
location ^~ /xiaoming/ {
proxy_pass http://192.168.134.35:18080/;
proxy_set_header Host $host;
proxy_redirect http://$host/ http://$host/xiaoming/;
sub_filter_once off;
sub_filter_types application/json text/javascript;
sub_filter 'src="/' 'src="/xiaoming/';
sub_filter 'href="/' 'href="/xiaoming/';
}
# xiaohong该怎么办
location ^~ /xiaohong
}
3. 方案二:cookie+map
所以,我们自然想到能不能基于客户请求的会话进行转发,比如他请求http://127.0.0.1:8080/xiaoming/时,这个第一个请求给他打上标签,接着所有后续请求客户都带上这个标签,这样我基于这个标签就行转发,就按这个思路走:
ini
map $cookie_jwt $proxy_tag {
default "off";
~^eyJ "xiaoming_tag";
}
server {
... ...
location / {
if ($proxy_tag = "xiaoming_tag") {
proxy_pass http://192.168.134.35:18080;
}
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location ^~ /xiaoming/ {
root html;
index index.html index.htm;
proxy_pass http://192.168.134.35:18080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
add_header Set-Cookie "jwt=eyJhbGciOi; Path=/; HttpOnly";
}
... ...
}
我来解释一下这个配置,第一个请求http://127.0.0.1:8080/xiaoming/时,会匹配location ^~ /xiaoming/,此时Nginx会主动告诉客户端后续cookie要带上jwt=eyJhbGciOi,这样后续请求有这个cookie时,会首先命中map关键字,如果coockie包含eyJ,则给我自定义的变量proxy_tag赋值为xiaoming_tag,这样后续即使请求不带xiaoming的前缀,匹配到默认的location /时,也会根据if ($proxy_tag = "xiaoming_tag")来正确转发到后端。

