业务中关于本地 devServer 代理和 nginx 代理的弯弯绕绕以及规范(附赠cookie特典)

前言

由于新加入前端大哥在不到三个月之内就离职了,之前他负责的项目中还缺少了获奖名单的需求,正好我没活了就由我接手了。乍一看代理配置,好家伙,乱中麻(代码虽然能走通,但增加了后续维护的理解成本)。

由于涉及到 SSO 单点登录,需要域名才能正常登录(即请求登录和用户信息的接口),因此需要在 nginx 服务器配置代理,导致了代理绕来绕去。

需要域名的原因:

由于是国际服,账号登录使用的是 SSO 单点登录,需要借助 facebook, google 等登录接口(以 facebook 为例,即登录后 facebook 会返回 token 等用户信息),由于 facebook 规定借助 SSO 单点登录时需要域名,因此团队中通常用的 ip 地址不能作为测试环境,而需要有域名的 nginx 服务器代理到测试环境,才能进行登录。

而在开发过程中,通常设置了登录逻辑,因此必须通过登录(即成功发送登录请求)才能执行其他操作(其他接口请求,页面跳转等)。但由于后端那边的设计,把普通邮箱的登录也设置成了需要域名才能发送请求,因此需要借助 nginx 服务器代理到普通 ip 的测试环境。

正文

在本文中,普通测试地址为 10.10.13.187:8082/,带有域名的 nginx 服务器为 debug-test.example.com:59998/,后端接口 ip 地址分别为 登录接口 12.12.5.205:21301/,业务接口 12.12.5.205:21302/,上传接口 12.12.5.205:21303/

环境变量 env.js 的配置

关于环境变量,由于不同的环境需要不同的跳转链接,代理地址等等,即开发环境有开发环境相匹配的 env,镜像环境有镜像环境的 env,正式环境有正式环境的 env。

在我们团队比较老的项目中,通常是这样部署的

.env 复制代码
// .env.development
NODE_ENV=development
VUE_APP_ENV=development
PUBLIC_PATH=/account

通过 process.env.VUE_APP_ENV ,拿到此时的开发环境,再做相应的匹配,但这样做交给后端打包部署后,后端是无法看到 env 的配置的

而最新的项目中,则是放到 public 文件下,后端把项目打包后能够暴露出来看到

js 复制代码
// public/env.js
window.$$env = {}

// src/utils/env.ts
export const env = (window as any).$$env as Env;

axios 的配置

一开始的 axios 的配置

account.js

js 复制代码
const instance = Axios.create({
  baseURL: "https://" + env.baseUrl.login + "/",
});

instance.interceptors.request.use((config) => {
      return intercepeReq(
        env.baseUrl.login,
        env.signature.secret
      );
});

从这里可以初略看出,baseURL 过于不合理,按理来说axios应该是由本地发送请求的,但这里居然拼接了https成了一个网址,有点不符合认知。

interceptors 请求拦截部分,关于 intercepeReq 函数,是用于传签名的,但按照语义来看,命名应该是放入签名 signature 中的变量却放入了请求基本路径 baseUrl 的变量当中,并且签名和基本路径居然同用一个变量。

一开始的 env 配置

js 复制代码
window.$$env = {
  baseUrl: {
    login: "debug-test.example.com:59998",
    contribute: "debug-test.example.com:59998",
    upload: "debug-test.example.com:59998",
  },
  signature: {
    secret: "WAxxhxxa8afxxqaWTxxwyt8BxxBeQpHcTxxG6xxaknA=",
  },
}

修改后的 axios 配置

js 复制代码
const instance = Axios.create({
  baseURL: env.baseUrl.account,
});

instance.interceptors.request.use((config) => {
      return intercepeReq(
        env.signature.backend,
        env.signature.secret
      );
});

修改后的 env 配置

js 复制代码
window.$$env = {
  baseUrl: {
    account: "/account-web-sso",
    createjam: "/1st-co-creation/createjam",
    uploadFile: "/1st-co-creation/upload",
  },
  signature: {
    secret: "WAxxhxxa8afxxqaWTxxwyt8BxxBeQpHcTxxG6xxaknA=",
    backend: "debug-test.example.com:59998",
  },
}

其中,由于请求头设置了签名的原因,签名中包含了主机 host 即 signature 中的 backend,客户端发出的网络请求的主机host必须与设置的签名的主机host相同,考虑到该项目由于登录权限必须使用到域名,因此签名中的主机host设置统一设置为域名的测试环境主机host,而不是本地 ip 或者其他测试环境的。

一开始其他请求的 axios 配置

contribute.js

js 复制代码
const instance = Axios.create({
  baseURL: "https://" + env.baseUrl.contribute + "/createjam/",
});

instance.interceptors.request.use((config) => {
      return intercepeReq(
        env.baseUrl.contribute,
        env.signature.secret
      );
});

upload.js

js 复制代码
const instance = Axios.create({
  baseURL: "https://" + env.baseUrl.upload + "/",
});

instance.interceptors.request.use((config) => {
      return intercepeReq(
        env.baseUrl.upload,
        env.signature.secret
      );
});

devServer 配置

一开始 devServer 的配置

js 复制代码
  【1】"/apis/v1/userinfo": {
         target: "http://10.10.5.205:21301/",
         changeOrigin: true,
       },
  【1】"/apis/v1/logout": {
         target: "http://10.10.5.205:21301/",
         changeOrigin: true,
       },
  【2】"/apis/v1/upload": {
         target: "http://10.10.5.205:21303/",
         changeOrigin: true,
       },
  【3】"/apis/": {
         target: "http://10.10.5.205:21302/",
         changeOrigin: true,
       },

再结合 axios 的 各个接口请求配置片段

js 复制代码
// account.js
export function getUserinfo() {
  return instance.get("/apis/v1/userinfo");
}
export function postLogout() {
  return instance.get("/apis/v1/logout");
}

// contribute.js
export function contribute(path: string, data: ContributeData) {
  return instance.post(`/apis/v1/works/submit/${path}`, data);
}

// upload.js       
export function uploadFile (file: File) {
  return instance.post(`/apis/v1/upload/anniversary`, file);
}

可以看出,每一个接口请求都基本在 deServer 中配置了,然后其他特殊接口匹配不上就会匹配常用业务接口,难以区分出每个代理的意义,这真的对后续开发人员极其不友好(后续开发者要缕一遍逻辑才能知道这接口是连接谁的)。从上述可以看出, account.js 中的 两个 /apis/v1/xxx 分别配置了两个,都是指向的同一个 ip 地址,这明显不太合适。

修改后

每个 devServer 的配置最好有个前缀,可以用 vue.config.js 中的 publicPath 的值,即标明了这个接口是哪个项目的,并且统一,比如以 /1st-co-creation 为例。

结合 axios 中的 baseUrl 的值,代理前缀 /1st-co-creation 加上 baseUrl,再搭配正则表达式对接口重写来请求后端提供的接口地址。

而关于 devServer 的配置可以看这篇文章 白嫖即食:构建工具的proxy代理配置区别(解决跨域)

js 复制代码
"/1st-co-creation/createjam": {
    target: "http://10.10.5.205:21302/",
    changeOrigin: true,
    pathRewrite: {
      "/1st-co-creation/createjam": "",
    },
}
"/1st-co-creation/upload": {
    target: "http://10.10.5.205:21303/",
    changeOrigin: true,
    pathRewrite: {
      "/1st-co-creation/upload": "",
    },
  },

然后再说说通用接口,比如登录接口在业务中肯定有复用的,所以直接表明该接口的功能就行了。

js 复制代码
 "/account-web-sso": {
    target: "http://10.10.5.205:21301/",
    changeOrigin: true,
    pathRewrite: {
      "/account-web-sso": "",
    },
  },

聊到这里,前端的代理基本聊完了,但最坑爹的就是涉及到 SSO 单点登录需要域名,然后我们就需要在 nginx 再做一层代理。所以也不能怪老哥,毕竟搁谁来也得蒙,我隔壁呆了好几年安卓转前端的哥也蒙。

nginx 的配置

而只要你敢用域名代理到本地(即 nginx 中【1】的代理,我就敢说它返回的就是一个 html 文档(整个文档是项目经过webpack打包之后放入代理后服务器中的),然后根据 html 文档里的js请求路径,以此时域名发送请求,即你上面在 devServer 中的代理配置根本就是白费力气,因为它是以debug域名发送请求的,而你的 devServer 代理配置是以代理后服务器请求的。

nginx 复制代码
【1】location /1st-co-creation/ {
        proxy_pass http://10.10.13.187:8082/1st-co-creation/;
    }

    location /apis/v1/userinfo {
        proxy_pass http://10.10.5.205:21301;
    }
    location /apis/v1/logout {
        proxy_pass http://10.10.5.205:21301;
    }
    location /apis/v1/works/submit {
        proxy_pass http://10.10.5.205:21302;
    }
    location /apis/v1/upload {
        proxy_pass http://10.10.5.205:21303;
    }

仍然可以看到,犯得错误和 devServer 中的一模一样,而且这里是 nginx 的配置,很多需要用到debug的项目都会到这里配置,所以标明是哪个项目非常重要,并且要注意不要污染了整个配置(即只要你用了整个代理名字,其他人就不能用了)

其实nginx代理的配置和devServer代理的配置原理基本一样,都是匹配成功后把匹配后的字段往后接。 如果在 nginx 没有配置相应的代理或者没有匹配上,会返回 404

修改后的

nginx 复制代码
location /1st-co-creation/ {
    proxy_pass http://10.10.13.187:8082/1st-co-creation/;
}

location /1st-co-creation/createjam/ {
    proxy_pass http://10.10.5.205:21301/;
}

location /account-web-sso/ {
    proxy_pass http://10.10.5.205:21302/;
}

location /1st-co-creation/upload/ {
    proxy_pass http://10.10.5.205:21303/;
}

看看就可以

由于以下设定,导致了客户端的 html 会进行缓存,而 js 或者 css 不会进行缓存,因此当你更新 env.js 时,不能对 env.js 进行删除和改动这两操作,因为当你更新 env.js 并上线后,此时 env.js 是最新的,为了以防客户端拿到是缓存的(即老的html文件),这时 env.js 是最新的,老的 html 文件无法找到 env.js 中东西而导致报错。

nginx 复制代码
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;
    access_log  /var/log/nginx/host.access.log  main;

    location ~* \\.(?:css|js)$ {
        add_header Cache-Control "no-cache";
        root        /usr/share/nginx/html;
        try_files $uri =404;
    }

    location / {
        root        /usr/share/nginx/html;
        try_files   $uri $uri/ /index.html;
    }
}

因为借助了 nginx 服务器代理来启动项目,导致UI设计开发时不能热更新,需手动刷新才能更新一次,但在设计UI时可使用本地,涉及到接口时使用域名测试环境。

特典

由于在开发过程中,涉及到了 cookie 相关的问题,即由于代理问题导致了后端没有给前端下发cookie,返回了401的错误

然后经此发现,我对 Cookie 除了简单的了解(它是一个类似于 session 的本地存储空间)外,其他竟一无所知。

由于 HTTP 的请求是无状态的,浏览器发出的请求后端服务器没办法区分用户的身份和信息。而浏览器每次发送请求时都会在请求头上携带 Cookie 。而 Cookie 会存储一些用户的信息,比如 token,sessionid(通话id),偏好设置等。

通常 Cookie 是由后端设置的,大致流程如下:

  1. 首次访问网站时,浏览器发送请求中并未携带 Cookie 。(类似于登录)
  2. 后端看到请求中未携带 Cookie ,在 HTTP 的响应头中加入 Set-Cookie (里面放了 Cookie 的信息)返回给浏览器。
  3. 浏览器收到 Set-Cookie 后,会自动将 Cookie 保存下来。
  4. 之后访问该网站时,HTTP 请求头就会携带 Cookie,用户就不需要再次登录什么的了。

当然,也有由前端收集,后端存储的,比如日夜间模式,语言等偏好设置由前端收集好,发送请求后携带上由后端存储和管理。

掘金token请求中响应头set-Cookie

掘金请求中请求头携带的cookie

注意:请求头中携带的cookie是以key=value,";"隔开的形式携带的。

而想要更直观的看,可以查看浏览器Network中的cookie选项

cookie中属性

属性 含义
Name Cookie的名称
Value 对应名称的值
Domain Cookie作用的域名
Path Cookie生效的路径
Expires 过期时间,过了这个时间后Cookie失效(具体的时间日期)
Max-age 生效时间,表示Cookie在多长时间后失效(倒计时)
Size Cookie的长度,为name和value的长度和
HttpOnly 防止通过JavaScript访问Cookie(即在控制台中通过document.cookie读取,设置cookie)
Secure 只在HTTPS协议的情况下才会将Cookie传到后端(http达咩)
SameSite 是否允许跨站请求时发送Cookie
Partitioned 第三方Cookie分区
Priority 优先级

作用范围

Domain

设置 Cookie 作用的域名,即 Cookie 在哪个网站生效。比如后端在 Domain 中设置了 .juejin.cn,那该 Cookie只能在这网站下生效。

Path

当我们希望 Cookie 只在部分路径下生效,就可以使用Path进行限制。如果 path=/user ,相当于除了 .juejin.cn/user 这个路由路径下能使用设置的 Cookie 外,其他路径都不能使用。

.juejin.cn 能使用

.juejin.cn/user 能使用

.juejin.cn/user/2239042325059533 能使用

.juejin.cn/edior 不能使用

作用时间

Session

如果Cookie的有效期为Session,类似于sessionStorge一样,即关闭浏览器就失效。

Expires

指定具体的失效时间,格式大致为2025-02-17T04:44:56.921Z,即到2025年2月17日4点44分56秒921毫秒失效

Max-Age

从拿到 Cookie 后开始倒计时设置 的Max-Age的值。例如Max-age=3000。当获取到该Cookie后开始倒计时,3000秒之后便失效。

Priority优先级

当Cookie的数量超过限制时,浏览器会清除一部分Cookie。清除哪些合适呢?Priority属性用来定义Cookie的优先级,低优先级的Cookie会优先被清除。

Priority属性有三种: Low, Medium, High

其他的过于复杂或者在上述概括中大致提到的就不详细说了。

相关推荐
qq_312920115 分钟前
安装lua-nginx-module实现WAF功能
nginx·junit·lua
方才coding36 分钟前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
man201738 分钟前
【2024最新】基于springboot+vue的闲一品交易平台lw+ppt
vue.js·spring boot·后端
阿征学IT40 分钟前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓40 分钟前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
发现你走远了40 分钟前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
前端小超超1 小时前
vue3 ts项目结合vant4 复选框+气泡弹框实现一个类似Select样式的下拉选择功能
前端·javascript·vue.js
大叔是90后大叔1 小时前
vue3中查找字典列表中某个元素的值
前端·javascript·vue.js
幸运小圣1 小时前
Vue3 -- 项目配置之prettier【企业级项目配置保姆级教程2】
前端·vue.js·vue
ZJ_.1 小时前
Electron 沙盒模式与预加载脚本:保障桌面应用安全的关键机制
开发语言·前端·javascript·vue.js·安全·electron·node.js