参考文档:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/WebSocket
问题结论
先给问题结论,这是由于创建websocket对象时第二个可变参数格式错误导致的:
// 即下面代码中的{ rejectUnauthorized: false }
var ws = new WebSocket('wss://localhost:3000/', { rejectUnauthorized: false });
参考websocket的API可以看到,第二个可变参数的格式应该是字符串或者字符串数组,所以传递一个对象时页面就会提示自协议无效(即The subprotocol ‘[object Object]’ is invalid.)。
问题背景
笔者由于业务开发要求需要在本地搭建一个wss服务供页面调试,在网上找了基于nodejs-websocket的服务端代码(nodejs-websocket版本为1.7.2,证书是通过openssl制作的自签名证书):
var ws = require('nodejs-websocket');
var fs = require('fs');
var options = {
secure: true,
key: fs.readFileSync("server.key"),
cert: fs.readFileSync("server.crt"),
passphrase: '123456'
};
var server = ws.createServer(options, function (socket) {
var count = 1;
socket.on('text', function (str) {
console.log(str);
socket.sendText('服务端返回消息:' + count++);
});
}).listen(3000);
然后是页面代码:
<html></html>
<script>
var ws = new WebSocket('wss://localhost:3000/');
ws.onopen = function() {
setInterval(function() {
ws.send('客户端发送消息');
},2000);
}
ws.onmessage = function(e) {
console.log(e.data)
}
</script>
此时运行页面会报错:client.html:4 WebSocket connection to ‘wss://localhost:3000/’ failed: ,且没有具体错误原因。笔者猜测是自签名证书的问题,百度以后还真有挺多文章说在创建websocket对象时第二个参数应该传递{ rejectUnauthorized: false },这样可以忽略自签名证书的校验,因此修改页面代码如下:
<html></html>
<script>
var ws = new WebSocket('wss://localhost:3000/', { rejectUnauthorized: false });
ws.onopen = function() {
setInterval(function() {
ws.send('客户端发送消息');
},2000);
}
ws.onmessage = function(e) {
console.log(e.data)
}
</script>
但是照抄过来仍然报错:The subprotocol ‘[object Object]’ is invalid.(不知道是哪个憨憨写的代码,也不跑起来验证一下)。
根因分析
其实初始写的代码是没有问题的,页面报错也确实是由于自签名证书引起的,只是解决方案弄错了。虽然websocket是允许跨域的,但是当服务端使用的是自签名证书时,浏览器仍然会阻止访问,只是不会像访问使用自签名证书的https地址那样有提示且允许忽略。当明确问题后,可以通过下面的方法来规避:
新打开一个页面访问https://localhost:3000/,然后忽略浏览器的安全提示,此时再刷新原来报错的页面可以看到页面显示正常了,websocket也可以正常发送和接收消息了。
注意:上面访问的地址是https,而不是wss。
而网上关于这个问题的解答很少的原因,笔者推测为跨域访问自签名websocket的场景实际基本不存在,因此大家几乎没有遇到这个问题的机会,大多数的业务场景是在自签名的页面中访问同域下的websocket,这样我们在忽略自签名页面的安全提示后,同域下的websocket就不再会被浏览器阻止访问了。