【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...

相关推荐
ai小鬼头12 分钟前
AIStarter最新版怎么卸载AI项目?一键删除操作指南(附路径设置技巧)
前端·后端·github
Touper.18 分钟前
SpringBoot -- 自动配置原理
java·spring boot·后端
一只叫煤球的猫1 小时前
普通程序员,从开发到管理岗,为什么我越升职越痛苦?
前端·后端·全栈
一只鹿鹿鹿1 小时前
信息化项目验收,软件工程评审和检查表单
大数据·人工智能·后端·智慧城市·软件工程
专注VB编程开发20年1 小时前
开机自动后台运行,在Windows服务中托管ASP.NET Core
windows·后端·asp.net
程序员岳焱1 小时前
Java 与 MySQL 性能优化:MySQL全文检索查询优化实践
后端·mysql·性能优化
一只叫煤球的猫2 小时前
手撕@Transactional!别再问事务为什么失效了!Spring-tx源码全面解析!
后端·spring·面试
旷世奇才李先生2 小时前
Ruby 安装使用教程
开发语言·后端·ruby
沃夫上校5 小时前
Feign调Post接口异常:Incomplete output stream
java·后端·微服务
LeeGe5 小时前
SpringAOP中@within和@annotation以及 @within和@target的区别
后端