最近钻研了下 WebSocket,总结下目前对 WebSocket 的认知。本文不是基于 WebSocket 开展的一个从 0 到 1 的具体介绍。如果你素来没有理解过 WebScoket,倡议能够先搜一些介绍 WebSocket 的文章,这类文章还是挺多的,我就不再赘述了。
上面的内容是基于你对 WebSocket 有根本理解后开展的几个小的知识点:
- ping/pong 协定;
- 如何使
ERROR_INTERNET_DISCONNECTED
错误信息不显示在控制台;
ping/pong 协定
背景:连贯 WebSocket 的时候,发现 WebSocket 刚连贯上没过多久就断开了,为了放弃长时间的连贯,就想到了 ping/pong 协定。
问题:
- ping/pong 是一种非凡的帧类型吗,还是说只是一种设计思维?
- JS 有原生办法反对发送 ping/pong 音讯吗
通过 WebSocket 协定,发现 ping/pong 的确是一种非凡的帧类型:
The Ping frame contains an opcode of 0x9.
The Pong frame contains an opcode of 0xA.
那么,下面所说的 opcode 又是什么货色呢?讲 opcode 就得说到帧数据格式:
通过上图能够发现,除了最初面的 Payload Data,也就是咱们要发送的数据之外,还会有一些其余信息。我感觉能够类比 http 申请的申请头局部。上图中第 5 - 8 位示意的就是 opcode 的内容。其余字段的含意能够参考上述 WebSocket 标准,或者搜 WebSocket 协定数据帧格局,这类博客还是挺多的。
拿 nodeJS 举个例子:
在浏览器端发动 WebSocket 的时候,会发送一个 http 申请,留神申请头外面的 Upgrade 字段,意思就是我要降级到 websocket 连贯:
GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
此时,nodeJS 就能够监听 upgrade 事件,去做回绝或者降级操作,留神下这个事件外面有个参数 socket:
socket: <stream.Duplex> Network socket between the server and client
socket 有一个 write 办法,该办法是能够用来写帧数据的,也就是下面帧格局外面的 全副 数据,而不仅仅是 Payload Data。
ws 仓库就是应用了 socket 的 write 办法发送了依据 WebSocket 协定定义的 ping/pong,局部要害代码如下:
doPing(data, mask, readOnly, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x09, // ping opcode
mask,
readOnly
}),
cb
);
}
doPong(data, mask, readOnly, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x0a, // pong opcode
mask,
readOnly
}),
cb
);
}
sendFrame(list, cb) {if (list.length === 2) {this._socket.cork();
this._socket.write(list[0]);
this._socket.write(list[1], cb);
this._socket.uncork();} else {this._socket.write(list[0], cb);
}
}
所以,nodeJS 是能够实现 WebSocket 协定定义的 ping/pong 帧的。起因是咱们能够拿到 socket 对象,并且该对象提供了能够发送残缺帧数据的办法。那么浏览器端呢?
浏览器提供了原生的 WebSocket 构造函数用来创立一个 WebSocket 实例,该实例只提供了一个 send 办法,并且该 send 办法只能用来发送上述协定中 Payload Data 的内容,浏览器会依据 send 的参数主动生成一个残缺的帧数据。所以,在浏览器端是没法管制除了 Payload Data 之外的帧内容的,也就是无奈自定义 opcode。所以,也就实现不了 WebSocket 标准定义的 ping/pong 协定。
此时,咱们就能够把 ping/pong 当成一种用来解决特定问题的设计模式。既然咱们只能自定义 Payload Data 的内容,那么咱们能够简略的在 Payload Data 外面增加一个字段用于辨别是 ping/pong 帧,还是一般的数据帧,比方 type。当 type 字段是 ping/pong 的时候表明是 ping/pong 帧,如果是其余字段才是一般的数据帧。
如何使 ERROR_INTERNET_DISCONNECTED
错误信息不显示在控制台
当断网的时候,连贯 WebSocket 会发现浏览器控制台会 log 一个错误信息:
WebSocket connection to 'ws://...' failed: Error in connection establishment: net::ERR_INTERNET_DISCONNECTED
原先的开发教训是,控制台如果有报错的话,必定是代码某个中央有谬误,并且没有被咱们的代码捕捉到,所以就会在控制台抛出,如果应用了 try catch 或者全局的 window.onerror 捕捉到了错误信息,就不会在控制台打印了。所以,我就尝试了上述办法,发现捕获不到,还是会在控制台 log。
另外,WebSocket 提供了两个事件,onerror 和 onclose。当产生上述错误信息的时候,onerror 和 onclose 是会被调用的。然而,此时控制台还是会有上述报错信息。
通过一番查找,发现无奈阻止上述错误信息显示在控制台。
那么,为什么浏览器会设计这样的行为呢?猜想起因如下:
下面说到通过 onerror 和 onclose 事件是能够捕捉到 WebSocket 创立失败的,然而,查看这两个事件的参数,咱们只能从中找到一个 code 是 1006 的属性,输入在控制台的错误信息 ERR_INTERNET_DISCONNECTED
在参数外面找不到。接着,看一下 code1006 相干的货色:
User agents must not convey any failure information to scripts in a way that would allow a script to distinguish the following situations:
* A server whose host name could not be resolved.
* A server to which packets could not successfully be routed.
* A server that refused the connection on the specified port.
* A server that failed to correctly perform a TLS handshake (e.g., the server certificate can't be verified).
* A server that did not complete the opening handshake (e.g. because it was not a WebSocket server).
* A WebSocket server that sent a correct opening handshake, but that specified options that caused the client to drop the connection (e.g. the server specified a subprotocol that the client did not offer).
* A WebSocket server that abruptly closed the connection after successfully completing the opening handshake.
In all of these cases, the the WebSocket connection close code would be 1006, as required by WebSocket Protocol.
Allowing a script to distinguish these cases would allow a script to probe the user's local network in preparation for an attack.
从上述标准能够看到,标准是禁止浏览器向脚本传递下述造成 WebSocket 连贯失败的具体起因的,只容许向脚本传递一个 1006 的 code 码,否则,用户就能够探测到部分网的信息,进而发动攻打。举个例子,下面那种断网的状况,脚本中只能失去 1006 的状态码,比方上面这种报错
Error in connection establishment: net::ERR_CONNECTION_REFUSED
也只能从 onerror 中取得一个 1006 的 code 码。
所以,作为开发人员,浏览器要怎么在通知咱们具体的错误信息的同时又阻止有可能产生的攻打呢?答案就是在控制台把具体的错误信息 log 进去。
总结
基于目前理解的常识总结的一篇博客,如有谬误,欢送留言探讨。