问题描述
点击微信聊天记录中的链接,会使用微信内置浏览器打开该网站。现有用户反馈某项目在微信中无法登录,经测试发现登录请求http状态码为0,并提示network error 错误,请求失败,项目使用Vue技术栈和axios请求库。
问题排查思路
- 猜测代码兼容性问题,是否是es6代码转码es5失败,是否是axios源码中使用了微信浏览器不支持的API等等问题;
- 猜测是否是微信内置浏览器bug,微信开发者社区搜索其他人是否有类似问题,未发现有价值信息;
- 猜测http请求哪个地方出了问题,从http状态码为0入手,通过fiddler工具抓包http请求,查看http请求全过程;发现登录请求的发送了options预请求成功,但真实的post请求并未发送。
针对猜测1,认真检查编译后的项目代码,并未发现有未转码的es6代码;调试axios源码未找到问题原因,使用其他请求库替代axios依旧无效,基本排除代码方面的兼容性问题;
针对猜测2,搜索到有稍微类似问题:windows的pc版本浏览器站打开 浏览器请求发送完options请求后get/post等请求不发送
针对猜测3,根据抓包结果显示的options预检请求成功,及options预检请求的详细解释:跨源资源共享(CORS)MDN –(PS ^有问题还是得找MDN^) 推断登录请求为跨域请求并且在微信浏览器触发了cors同源限制策略,可能导致该http真实请求不发送问题出现。
CORS 协议字段
“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。”预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
预检请求的部分关键header字段如下:
OPTIONS请求头相关字段:
Host: http://b.com // 请求的Host
Origin: http://a.com // 当前页面域名地址
Access-Control-Request-Headers: authorization
Access-Control-Request-Method: GET
OPTIONS响应头相关字段:
Access-Control-Allow-Origin: * // 与请求头的Origin字段相对应
Access-Control-Allow-Headers: * // 与请求头的Access-Control-Request-Headers对应
Access-Control-Allow-Credentials: true // 是否可以跨域携带cookie
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS,HEAD // 与请求头的Access-Control-Request-Method对应
复制代码
浏览器根据OPTIONS请求头和响应头各对应字段是否有包含关系判断是否发送真实请求。
以上面为例:
- Allow-Origin:* 包含 Origin: a.com
- Allow-Headers: * 包含 Request-Headers: authorization
- Allow-Methods: GET,POST,PUT,DELETE,OPTIONS,HEAD 包含 Request-Method: GET
上面所有规则看起来都符合规范,所以在chrome浏览器里真实的post请求发送成功,但是在微信内置浏览器中并没有发送真实的post请求。令人抓狂。。。
正在百思不得其解之际,只能用最笨的方法测试OPTIONS响应头每一个和CORS相关的的字段。
使用NodeJS、Koa、Koa2-cors快速搭建Nodejs服务并配置CORS跨域。
const Koa = require('koa');
const app = new Koa();
const cors = require('koa2-cors');
app.use(cors({
// allowHeaders:['*'], // 注释这条规则,微信浏览器发送了真实请求
allowMethods:['GET','POST','PUT','DELETE','OPTIONS','HEAD'],
// origin: '*',
origin: '*',
// exposeHeaders:['*'],
// maxAge:'18000L',
credentials:true,
}))
app.use(async ctx => {
ctx.body = 'Hello World 111111';
});
app.listen(3000);
复制代码
经过测试发现只要配置Access-Control-Allow-Headers: * 这条规则就会导致微信内置浏览器出现该问题;猜测可能是微信内置浏览器不支持该配置值。
进一步配置nginx的配置文件,把Access-Control-Allow-Headers: * 这条规则改OPTIONS请求头的Access-Control-Request-Headers字段对应的值,动态设置而不是设定为通配符*;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
# add_header 'Access-Control-Allow-Headers' '*';
add_header 'Access-Control-Allow-Headers' $http_access_control_request_headers;
add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE,OPTIONS,HEAD';
add_header 'Access-Control-Allow-Credentials' 'true';
return 200;
}
复制代码
查看MDN文档上Access-Control-Allow-Headers的值为*时的解释:链接
对于没有凭据的请求(没有HTTP cookie或HTTP认证信息的请求),值“ *”仅作为特殊的通配符值。 在具有凭据的请求中,它被视为没有特殊语义的文字标头名称“ *”。 请注意,Authorization标头不能使用通配符,并且始终需要明确列出。
猜测真实原因:OPTIONS请求头里Access-Control-Request-Headers: authorization 值导致响应头Access-Control-Allow-Headers: * 值失效,进而导致微信内置浏览器真实请求不发送.
总结
经过一顿操作,终于找到问题的原因,解决问题的思路就是改变OPTIONS请求的响应头的Access-Control-Allow-Headers值为特定值即可。
回顾整个过程还是有很多值得改进的地方:
- 开始思路不够有条理,不够开阔,没有形成一个系统性的解决问题的思路,以上这些内容是后面思考总结成的思路。
- 开始未借助抓包工具,一致纠结于代码层面的问题,浪费了大量时间。