在前后端分离的开发中,前端的静态资源存于本机上,通过 localhost 访问,如果直接调用服务端 api 则会因为跨域的问题不能正常访问。解决跨域问题可以通过 JSONP 或者让服务端设置 Access-Control-Allow-Origin
解除限制。但是并不是所有接口都可以这么搞的,所以一般需要前端自己配置代理来解决跨域问题。
背景
最近接手了一个项目,代理配置过程很有意思,记录下来。简单描述下场景:projectA
是一个普通的页面,但里面部分区域和部分弹层是通过 iframe 的形式引用的 projectB
,而且后端接口限制出于安全考虑 只接受来自指定域名 (*.server.com
) 的请求,对于来自 localhost 或者 IP 的请求会被直接重定向至 server 域的登陆接口(server.com/login
)。需要同时启动 projectA
(localhost:8087)和 projectB
(localhost:8085)两个项目。
devServer.proxy大法
前端配置代理最常用的就是利用webpack的devServer.proxy了。
我首先尝试的是在 projectA
的 devServer 中这样配置:
devServer: {
proxy: {
'/api': { // 需要直接代理到线上环境的接口
target: 'http://ad.server.com',
changeOrigin: true,
headers: {
// 后端要校验请求源,那改下 host 或者 origin 不就美滋滋了?
Host: 'ad.server.com',
Origin: 'ad.server.com'
},
},
'/apitest': { // 需要与后端联调的接口
target: '10.8.0.1:9909',// 后端本地开发环境
},
'/iframe': {
target: 'http://localhost:8085',
}
// 另外还有 iframe 里的 api 调用指向线上环境
}
}
复制代码
然而,后端接口残酷地给跳转到登陆接口去了 😭。猜想一下可能后端的接口是通过 cookie 来判断当前登陆域的,从 localhost 过去的请求不带 cookie。后来尝试发现在 proxy的headers里再增加 cookie 即可破跳转,但是请求过去返回结果还是在报错,后端表示仍然是认证失败。
SwitchyOmega
既然从 localhost 访问不行那只好就从 server.com 这个域来访问了。简单的做法就是利用浏览器插件 SwitchyOmega
配置代理。即 http://ad.server.com/*
的所有请求全部指向 localhost8087, http://ad.server.com/iframe/*
的请求全部指向 localhost8085, 然后将 api 的部分拎出来直接代理回线上。
坑爹的是有的静态资源也不需要被代理到本地,例如 http://ad.server.com/static/*
本地可能根本就没有,得重新指回到线上。这时候 iframe 里的 projectB 又开始作妖了,里面的静态资源存于好几个目录,得挨着挨着配置,最后出来的完整配置就变得无比长。
SwitchyOmega
既可以对全局进行代理设置,也可以对单独域进行设置(点开 SwitchyOmega
看到下面那个漏斗便是)。这就导致了我的噩梦:期间我在做其他项目的时候进行一些小流量测试也是在用这个 SwitchyOmega
代理,也会涉及到对 server.com
域的修改,几番折腾后已经分不清到底当前是在全局代理还是对单独域代理了(而且实际项目中不止 server.com
这一个域),每日疑惑便是:“现在到底把哪个域代理到哪去了?”
为了避免这种困惑最后只能左手一个 Chrome,右手一个 Canary(Chrome 的开发版),两个不同浏览器分别独立的 SwitchyOmega
配置。
Charles
后来感觉用 SwitchyOmega
这种上网工具来做开发不太专业,而且切换浏览器来代理的做法也实在不方便,就改用 Charels
。开启代理后利用其 Map Remote
功能,可以配置具体哪些请求代理到哪个地方去。因为其界面是图形化的,功能也比较强大,把 SwitchyOmega
里的配置抄一份过来便可用了。比 SwitchyOmega
相比,不用再把本就应该代理到线上的静态资源再写一遍,所以配置会少一些。
最后让我放弃 Charles 的原因是:实在太卡了 😭。同时配置了多个 url 代理的情况下,电脑风扇转得飞起,请求一个被代理了的 url 半天回不了结果。而且 SwitchyOmega
和 Charles
都不能批量复制修改,得一个一个在图形界面改,有的时候就很麻烦(比如将线上环境切到后端的开发环境)。
Nginx
想更灵活地复制修改配置,当然最理想的就是一个可编辑的配置文件了。Nginx
正好可以非常方便的修改配置文件,而且本地启一个 Nginx
进程不会像 Charels
那么耗资源。最后配置出来的 Nginx
大概长这个样子:
http:{
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream localhost8083 {
server 127.0.0.1:8083;
keepalive 2000;
}
upstream localhost8087 {
server 127.0.0.1:8087;
keepalive 2000;
}
upstream rd {
server 10.8.0.1:9909;
keepalive 2000;
}
upstream server {
server ad.server.com;
keepalive 2000;
}
server {
listen 80;
# api接口用于和后端开发联调
location ^~ /api/{
proxy_pass http://rd;
proxy_set_header Host $host:$server_port;
}
# 代理 projectA 的页面
location ~ /(path1|path2|path3)/ {
proxy_pass http://localhost8087;
proxy_set_header Host $host:$server_port;
}
# 代理 projectA 的静态资源
location ^~ /projectA_static/ {
proxy_pass http://localhost8087;
proxy_set_header Host $host:$server_port;
}
# 代理 projectA 的 hot-update
location ~ \.hot-update.js$ {
proxy_pass http://localhost8087;
proxy_set_header Host $host:$server_port;
}
# 代理 projectB 的 iframe 页面
location ~ /(path_iframe1|path_iframe2|path_iframe3) {
proxy_pass http://localhost8085;
proxy_set_header Host $host:$server_port;
}
# 代理 projectB 的静态资源
location ^~ /projectB_static/ {
proxy_pass http://localhost8085;
proxy_set_header Host $host:$server_port;
}
# 剩下的请求正常走 server.com 线上环境
location / {
proxy_pass http://server;
proxy_set_header Host server;
}
}
复制代码
利用 Nginx
灵活的规则,可以合并一些相似的代理规则,不用像配置 Charles
那样得一个一个自己粘贴了。另外还有个好处就是可以灵活管理自己的代理环境, 如果今后再有像这种代理比较麻烦的项目,可以分别拆成两个 nginx.conf
文件,使用时指定用哪个 conf 文件即可。如果用 Charles
管理的话,所有代理都一个配置列表里面,代理多了之后会非常难以维护。
不过本机用 Nginx
代理一般得配合改 hosts 文件,在 hosts 文件里将 test.server.com
(自己瞎编的一个域,这里满足 *.server.com
就能通过后端的校验) 指向 localhost即可。启动 Nginx
指定监听 80 端口,这样访问 test.server.com
的时候(实际上访问的就是 localhost:80
) 就会被 Nginx
给代理了。配置好了之后,访问 ad.server.com/
就是线上环境,改成 test.server.com/
就是本机环境了,无痛切换,非常舒服。
总结
以上几种办法都在一定场景下能实现代理的需求。后来发现社区有专门的轮子 xswitch、zan-proxy。不过作为前端开发,多熟悉下 Charels
和 Nginx
也是个不错的选择。