前置
socket.io 版本:0.9.11,下载地址:cdnjs.com/libraries/s…
现象
在写完一个需求验证 ie9 环境的兼容性时,发现 SDK socket.io 一直连接失败,但是 ie10、ie11 可以连接成功。
ie9 环境
ie10 环境对比
实验
实验环境
ie9 浏览器。ie9 浏览器比较难找到,一般可以通过较早的 windows 10 版本的 ie 浏览器,设置为 ie9 的模式来模拟 ie9。
吐槽一下,较新的 windows 10 版本,打开 ie 浏览器,系统会直接使用 edge 浏览器打开,并且关闭 ie 浏览器,根本不给你设置 ie9 的机会。本人使用 VMware 虚拟机以及较早的 windows 10 版本,从而达到 ie9 的实验环境。
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./NIM_Web_NIM_xxx.js"></script>
<script>
window.nim = NIM.getInstance({
debug: true,
db: false,
appKey: "xxx",
account: "xxx",
token: "xxx"
});
</script>
</body>
</html>
实验步骤
- 使用 vscode 的 Live Server 插件,启动一个本地服务器,这样页面就可以在内网访问了。注意:默认是 http 服务。
- ie9 访问页面。
问题分析
心路历程
很奇怪的一个现象,在 ie10 和 ie11 环境下,sdk 可以正常使用,socket.io 可以连接上。但是在 ie9 上面就是一直连接不上。 web 端向服务端请求 socket.io 的 uuid 是可以成功的,但是在 socket.io 连接阶段一直失败。
ie9 环境
ie10 环境对比
首先排除 sdk 兼容性问题,如果 sdk 没有兼容 ie9,那么会报语法错误,sdk 根本不会进行 socket.io 的连接。下面附上兼容 ie9 的babel 配置。const cwd = process.cwd()
const path = require('path')
module.exports = function () {
// loaders
const rules = [
{
test: /\.js$/,
include: [path.join(cwd, 'src'), path.join(cwd, 'node_modules/@yxfe')],
use: [
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: false,
targets: {
browsers: ['last 2 versions', 'ie >= 9', 'android > 4']
}
}
]
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 3
}
]
],
sourceType: 'unambiguous'
}
}
]
}
]
const config = {
module: {
rules
}
}
return config
}
另外客户反馈,某一版本的 sdk 在 ie9 环境下是通过了验证的。于是我切到了同版本的sdk去验证,socket.io 还是连不上。长舒了一口气,不是我需求写的有问题...
突然想到,ie9 不支持 websocket,需要长轮询的方式。于是我设置了socket.io 的 transports 为 xhr-polling,但是结果不尽人意...
最后挣扎一下,我本地起的服务是 http 的,请求的服务是 https 的,一般 http 站点请求 https 服务是没问题的,但是它是 ie9 啊,并且我们使用的 socket.io 的版本比较低,不能当作一般情况处理。
设置 Live Server 启动 https 服务
本地生成 https 证书
- 在指定目录下执行以下命令生成 privkey.pem
openssl genrsa -out privkey.pem 1024/2048
- 使用密钥 privkey.pem 生成证书 server.pem
openssl req -new -x509 -key privkey.pem -out server.pem -days 365
- 按照步骤填写信息
- 最终生成了两个证书文件
设置Live Server
在.vscode 的 settings.json 中设置
{
"liveServer.settings.https": {
"enable": true,
"cert": "xxx/server.pem", // 证书
"key": "xxx/privkey.pem", // 私钥
"passphrase": ""
}
}
最后重新启动 Live Server
实验结果
果然是 http 服务的问题,本地服务设置为 https 服务,在 ie9 环境下,socket.io就可以连接成功了。
再次分析
虽然 socket.io 连接成功了,但是心里还是有疑问:为什么 socket.io 连接之前的 lbs 逻辑是正常的呢(lbs 是获取socket.io 的连接地址)?lbs 也是在 http 站点下请求 https 服务,为什么 socket.io 的连接逻辑走不通呢?难道是socket.io自身的限制?
日志有一个很关键的信息(见下图),于是我去代码里面搜索 onConnectFailed 关键字,发现只有 socket.io publish了connect_failed 事件,我们 sdk 代码才会打印带有 "link::onConnectFailed" 内容的日志。
于是我去 socket.io 源码里面搜索 connect_failed 关键字。我们是在 socket.io 连接阶段出了问题,带着这一筛选项,很快我就定位到了问题位置。
在问题位置打了断点,发现执行 self.getTransport 返回的数据是 null。继续看 getTransport 这个方法。transporst 这个变量值是 ["websocket", "xhr-polling"]。
搜索 check 方法,定位到以下代码。
先解释下 XDomainRequest,ChatGPT 给的答案是:XDomainRequest 是一种用于跨域请求的对象,用于在Internet Explorer 8和9中发送跨域请求。它是IE浏览器特定的,其他现代浏览器使用 XMLHttpRequest 对象来处理跨域请求。
那么 usesXDomReq 一定是 true 了。socket.options.secure 的默认值是 https,socketProtocol 被赋值为 https。global.location.protocol 就是站点的协议,我们启动的是 http 服务,isXProtocol 的也是 true 了。最终,io.Transport[transport].check 返回值是 false,getTransport 返回值是 null 。socket.io 在连接时因为 getTransport 返回值为 null,publish connect_failed 事件,导致 socket.io 连接失败,sdk 一直重连一直失败。
Q&A
Q1: 是不是 secure 设置为 http 就行了?
A1: 不一定。secure 设置为 http,如果向服务端请求的服务是 https 的,就会请求失败。
Q2: 是不是在正常版本的浏览器,把站点设置成 http,就可以复现上述问题?
A2: 不是。ie10 以上版本以及 google 常用版本使用的是 websocket,self.getTransport 不是 null,socket.io 可以连接成功。
Q3: 接问题2继续提问,是不是设置 socket.io 的传输协议为 xhr-polling 就行了?
A3: 不是。usesXDomReq 是 fasle,io.Transport[transport].check 返回值是 true,getTransport 返回值不为null,socket.io 可以正常连接。
结论
这个问题是 0.9.11 版本的 socket.io 的限制导致的。在特定的浏览器的版本下,例如:ie9 ,secure 值要与项目的协议一致。也就是说,secure 为 https 时,部署的项目必须是 https 的服务。