【Map Or Rewrite】Nginx基于会话转发的一些实践

谜题

朋友们,又到了解题时间,假设有这么一个场景,现在我们有一个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")来正确转发到后端。

4. Reference

nginx.org/en/docs/htt...

nginx.org/en/docs/htt...

相关推荐
why1516 小时前
微服务商城-商品微服务
数据库·后端·golang
結城8 小时前
mybatisX的使用,简化springboot的开发,不用再写entity、mapper以及service了!
java·spring boot·后端
星辰离彬8 小时前
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
java·spring boot·后端·sql·mysql·性能优化
q_19132846958 小时前
基于Springboot+Vue的办公管理系统
java·vue.js·spring boot·后端·intellij idea
陪我一起学编程9 小时前
关于nvm与node.js
vue.js·后端·npm·node.js
舒一笑10 小时前
基于KubeSphere平台快速搭建单节点向量数据库Milvus
后端
JavaBuild10 小时前
时隔半年,拾笔分享:来自一个大龄程序员的迷茫自问
后端·程序员·创业
一只叫煤球的猫11 小时前
虚拟线程生产事故复盘:警惕高性能背后的陷阱
java·后端·性能优化
周杰伦fans12 小时前
C#中用于控制自定义特性(Attribute)
后端·c#
Livingbody12 小时前
GitHub小管家Trae智能体介绍
后端