webpack 代理 proxy 还是跨域?
proxy
代理了浏览器发起的请求还是跨域,但是复制 curl
后在终端执行正常,本文将对这个跨越问题进行分析与解决
proxy 配置
jsdoc
在js中获得类型提示,方便编辑器智能补全
/** @type {import('webpack-dev-server').ProxyConfigMap} */
const proxyConfig = {};
/**
* 根据环境变量的代理配置
* process.env.VUE_APP_PROXY_ENV(.env[.xxx]文件配置)
* npm script启动的时候 --mode会读取相应的 .env[.xxx]文件,设置环境变量
* 连接到不同环境调试的时候通过设置相对的环境变量,读取代理配置
*/
module.exports = function generateProxyConfig(isProduction) {
/** @type {import('webpack-dev-server').ProxyConfigMap} */
const proxyConfig = {};
pathToTarget.forEach((item) => {
proxyConfig[item.startPath] = {
target: item.target,
secure: true,
changeOrigin: true,
pathRewrite: item.pathRewrite || {},
headers: {
"X-Forwarded-Host": VUE_APP_API_URL.replace(/https?:\/\//, ""),
},
onProxyReq: (proxyReq, req, res) => {
if (proxyReq.method === 'OPTIONS') {
proxyReq.destroy();
res
.setHeader(
'Access-Control-Allow-Headers',
'Content-Type,Access-Control-Allow-Headers,Authorization,X-Requested-With'
)
.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, PUT')
.status(200)
.end();
}
},
onProxyRes: (proxyRes, req, res) => {
// 查看proxyRes
console.log(proxyRes);
},
};
});
if (!isProduction) console.table(proxyConfig);
// 保存到文件供查看代理配置
saveConfigJson(proxyConfig);
return proxyConfig;
};
发起请求
- req: 原始请求
- proxyReq: 代理请求
- proxyRes: 代理请求的响应,此时的
req
与proxyReq
一致
使用 curl 发起请求
- 终端执行 curl 发起请求
$ curl 'http://localhost:7200/v1/template?offset=0&limit=20' -H 'sec-ch-ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"' -H 'sec-ch-ua-mobile: ?0' -H 'Authorization: xxxxx==' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' -H 'Content-Type: application/json' -H 'Accept: application/json, text/plain, */*' -H 'Referer: http://localhost:7200/' -H 'sec-ch-ua-platform: "macOS"' --compressed
curl 内容如下(与上面的一样):
> curl 'http://localhost:7200/v1/template?offset=0&limit=20' \
> -H 'sec-ch-ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"' \
> -H 'sec-ch-ua-mobile: ?0' \
> -H 'Authorization: xxxxxx==' \
> -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \
> -H 'Content-Type: application/json' \
> -H 'Accept: application/json, text/plain, */*' \
> -H 'Referer: http://localhost:7200/' \
> -H 'sec-ch-ua-platform: "macOS"' \
> --compressed
- curl 发起的原始请求与代理请求的头部 header
浏览器发起请求
在浏览器的 network
那里看请求其实是有Authorization
头的
network
复制出来的原始请求fetch
fetch("http://localhost:7200/v1/template?offset=0&limit=20", {
headers: {
accept: "application/json, text/plain, */*",
authorization: "Basic xxxx",
"content-type": "application/json",
"sec-ch-ua":
'"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"macOS"',
},
referrer: "http://localhost:7200/",
referrerPolicy: "strict-origin-when-cross-origin",
body: null,
method: "GET",
mode: "cors",
credentials: "include",
});
- 浏览器发起的代理请求头部 header
可以看到
-
浏览器发起的原始请求,少了鉴权的
Authorization
头,导致代理请求也少了 -
然后在
onProxyRes
里面看到proxyRes
的状态码是401
-
加上
Authorization
头之后,发现是此时的proxyRes
的状态码是404
,是后端未设置OPTIONS
请求导致 -
curl请求与浏览器请求的差异是没有
OPTIONS
请求
简单请求
-
Request Method:(之一)
- GET
- HEAD
- POST
-
除了被用户代理自动设置的标头字段(例如
Connection
、User-Agent
或其他在 Fetch 规范中定义为禁用标头名称的标头),允许人为设置的字段为 Fetch 规范定义的对 CORS 安全的标头字段集合。该集合为:Accept
Accept-Language
Content-Language
Content-Type
(需要注意额外的限制)Range
(只允许简单的范围标头值 如bytes=256-
或bytes=127-255
)
-
Content-Type: (之一)
-
text/plain
-
multipart/form-data
-
application/x-www-form-urlencoded
-
复杂请求CORS
简单请求外就是复杂请求,浏览器会发起CORS 预检请求
服务器未设置允许
OPTIONS请求
的话,浏览器会识别为跨域
触发OPTIONS请求
CORS
的预检请求-OPTIONS
告知服务器,是否支持新增或修改请求头/请求方式,如下:
-
Access-Control-Request-Headers
'Content-Type,Authorization'
一般请求会设置
Content-Type: application/json
,这个是最容易忽略的
跳过OPTIONS请求
这里主要是拦截OPTIONS
请求(proxyReq.destroy()
),不发送到服务器,直接返回结果
设置响应头
-
Access-Control-Allow-Headers
'Content-Type,Access-Control-Allow-Headers,Authorization,X-Requested-With'
-
Access-Control-Allow-Methods
'POST, GET, DELETE, PUT'
onProxyReq: (proxyReq, req, res) => {
if (proxyReq.method === 'OPTIONS') {
proxyReq.destroy();
res
.setHeader(
'Access-Control-Allow-Headers',
'Content-Type,Access-Control-Allow-Headers,Authorization,X-Requested-With'
)
.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, PUT')
.status(200)
.end();
}
}
最后
其实这个问题是需要后端接口处理的,这里也算是在开发服务器处理了;
但是生命在于折腾,通过经过分析具体了解了跨域的原因,解决跨域的问题,也是一种学习和乐趣
转载自:https://juejin.cn/post/7271429136659660835