记一次windows 微信内置浏览器http请求发送失败原因排查

问题描述

点击微信聊天记录中的链接,会使用微信内置浏览器打开该网站。现有用户反馈某项目在微信中无法登录,经测试发现登录请求http状态码为0,并提示network error 错误,请求失败,项目使用Vue技术栈和axios请求库。

问题排查思路

  1. 猜测代码兼容性问题,是否是es6代码转码es5失败,是否是axios源码中使用了微信浏览器不支持的API等等问题;
  2. 猜测是否是微信内置浏览器bug,微信开发者社区搜索其他人是否有类似问题,未发现有价值信息;
  3. 猜测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请求头和响应头各对应字段是否有包含关系判断是否发送真实请求。
以上面为例:

  1. Allow-Origin:* 包含 Origin: a.com
  2. Allow-Headers: * 包含 Request-Headers: authorization
  3. 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值为特定值即可。
回顾整个过程还是有很多值得改进的地方:

  1. 开始思路不够有条理,不够开阔,没有形成一个系统性的解决问题的思路,以上这些内容是后面思考总结成的思路。
  2. 开始未借助抓包工具,一致纠结于代码层面的问题,浪费了大量时间。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享