Websockets 101
#javascript #websockets

介绍

免责声明:1周前,我对Websockets了解不多,我在WebSockets上获得的所有经验都是我使用JS框架开发了2016年的聊天应用程序,该框架试图成为Ruby在Rauds实施中,称为SailsJS,因此,我决定研究这项技术并消耗多个资源,这些资源将在此博客文章和每个部分中链接。

WebSockets是处理全双工通信(或双向通信)的一种方式,它们对于构建需要实时数据(例如聊天应用程序或库存仪表板)的应用程序非常有用。 WebSockets以相比以前的解决方案来到:

  • 投票

最简单的解决方案包括以设定的间隔进行HTTP请求

  • Long Polling

绝望的时代要求拼命措施

这只是意味着客户端将调用HTTP端点,并且服务器不会立即解决请求,但要等到需要向客户端传递消息,此方法有效,但有很多缺点,例如超时,延迟,延迟,从一开始就听起来很刺耳。

  • HTTP流和服务器量事件(SSE)

这些解决方案运行良好,但更适合单向通信,因为在服务器需要向其用户发送通知的用例中,例如:创建了一个新帖子,等等。

WebSocket服务器可以(但不必)与普通的HTTP服务器一起运行,因为WebSockets使用了其他协议wswss(对于HTTPS(例如HTTPS)。

WebSockets从用户的角度来看是一个简单的协议,因为它们仅由3个不同的事件组成:

  • open

  • 关闭

  • 消息

连接(握手)

Handshake
所有WebSocket连接都将从客户端始终发起的握手开始,如果成功将连接从HTTP升级到ws/wss协议。

注意:

  • 客户端可以创建尽可能多的连接。

  • 设置WebSocket连接时,无法在浏览器中设置任意标头。

以下所有行为都是由W3的HTML5 Websocket API规范在浏览器级别定义的,并通过RFC 6455在协议级别定义了“ WebSocket协议”,并且当然是由系统管理的(JavaScript中的浏览器),并且您可以DON需要担心,以下是出于信息目的

握手过程包括以下步骤:

  • 客户端在标准http(s)请求下发送以下标题
HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: hXcK3GM6B2r6yj/4L0Vuqw==
Origin: http://localhost:3000
Sec-WebSocket-Version: 13
  • Sec-WebSocket-Key

此标头是一个随机的字符串,服务器将采用这些字节并附加special string 58EAFA5-E914-47DA-95CA-C5AB0DC85B11,将其放置并在base64中进行编码,然后返回Sec-WebSocket-Accept header。

如果服务器接受连接,它将以以下标题响应:

HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: rXzSb8mhB4ljxko8kbyiCohJ4Fc=
Upgrade: websocket

仅当服务器:

时,该连接才被视为成功
  • 用状态代码101

  • 回复
  • 包括Connection标头,带有value Upgrade

  • 包括Upgrade标头,带有value websocket

JavaScript中的示例

所有这些复杂性已经由浏览器处理,建立Websocket连接非常简单:

const socket = new WebSocket('wss://example.com/socket');

socket.addEventListener('open', (event) => {
  console.log('WebSocket connection established!');
});

消息交换

建立了连接后,它将在服务器和客户端中保持活力。

服务器和客户端都可以随时在textbinary数据(BLOB或ARRAYBUFFER对象)中发送消息。在二进制数据中发送消息的速度更快,但速度不大,因为发送text数据需要UTF-8转换,但是如今此过程非常快。

JavaScript中的示例

发送消息很简单:

socket.send(message); // e.g: '{a: 1]'

并收到它们:

socket.addEventListener('message', (event) => {
  console.log('Received message:', event.data);
});

关闭

客户端或服务器可以随时关闭WebSocket连接。

从客户的角度来看

这很简单:

socket.close();

从服务器:

这可能会有些棘手,因为如果您等待客户断开连接,客户可能会悬挂并最终会引起性能问题,可能会有问题。解决此问题的两种方法是使用以下代码(从Stackoverflow获取):

  • 硬关闭

使用这种方法,我们只是终止连接而不等待客户断开连接

// Soft close
socket.close();

process.nextTick(() => {
if ([socket.OPEN, socket.CLOSING].includes(socket.readyState)) {
    // Socket still hangs, hard close
    socket.terminate();
}
});
  • 软关闭

在这里,我们给客户一些时间在终止之前断开连接

// First sweep, soft close
wss.clients.forEach((socket) => {
  socket.close();
});

setTimeout(() => {
  // Second sweep, hard close
  // for everyone who's left
  wss.clients.forEach((socket) => {
    if ([socket.OPEN, socket.CLOSING].includes(socket.readyState)) {
      socket.terminate();
    }
  });
}, 10000);

身份和安全性

不幸的是,WebSocket协议并没有提出一种处理身份验证的方法,但是随着时间的推移,许多人提出了许多解决方案。

如前所述,在WebSockets中,无法在浏览器中不支持发送自定义标头,这是在现代网络中使用标记为JWT的最传统方式。

这使我们提供了像in this document的解决方案:

  • 将凭据作为WebSocket连接中的第一条消息

此方法完全可靠,但是将身份验证移至应用程序层,如果您不够小心,可以将您的泄漏信息暴露于泄漏信息。另一个负数是,它允许每个人与服务器打开WebSocket连接。

  • 在Websocket URI中添加凭据作为查询参数

另一种方法是在打开WebSocket连接时将令牌作为查询参数发送,例如:wss://localhost:3000/ws?token=myToken的缺点是,此信息可能最终出现在您系统的logs中,一种减轻此信息的方法是使用此信息。一次性代币,但行业倾向于将这种风险视为无法接受的。

  1. 在Websocket URI
  2. 的域上设置cookie

,只要您的WebSocket服务器与http服务器在同一域中运行,则此解决方案也是可靠的,如果不是这种情况,则不会有可能在不同的起源。

但是有两种方法可以克服这个问题:

1. Move the WebSocket server to a subdomain of the main `http` server, e.g: `websocket.example.com`

2. Use an `iframe` running on the same WebSocket domain to set the cookie

如果您采用这种方法,则需要考虑到今天 [2023-07-18 TUE] Google Chrome chrome chrome chrome否则将使用这种方法还设置SameSiteSecure属性,例如
document.cookie = 'my_token=token; SameSite=None; Secure;'

WebSocket Auth and Security

跨站点Websocket劫持

WebSocket Cross
要注意基于Cookie的身份验证解决方案的一件事是,Websocket连接不受same-origin policy的限制,因此它打开了称为Cross-Site WebSocket Hijacking的向量攻击。

这意味着,如果您为websocket domain设置了一个cookie,则该cookie在连接到服务器时都会发送到网站,无论是哪个网站,Websocket Connection的网站都可以打开安全问题,因为MALIGN网站可以利用用户已经优势有一个cookie设置用于域,还可以使用WebSocket服务器订阅消息。

减轻此问题的一种方法是在建立连接之前始终验证客户端的Origin

您可以在此article

中提供更多信息

资源