1、WebSocket连接建立流程

整个WebSocket的连接建立时序图:

首先是客户端发送HTTP请求,头中携带Upgrade字段,值为websocket

  • 客户端发送一个 HTTP 请求到服务器,请求包括一个 Upgrade 头,表示客户端想要切换到 WebSocket 协议。
  • 此 HTTP 请求中还包括一个 Sec-WebSocket-Key 头,该头包含了一个 Base64 编码的随机值。
    Client                                                Server
    |                                                          |
    |  HTTP Request (Upgrade: websocket)  |
    |-------------------------------------->|

服务端接收到之后,发送响应,同时websocket

  • 服务器接收到请求后,如果同意切换到 WebSocket 协议,它会返回一个 HTTP 响应,该响应包括一个 Upgrade: websocket 头和一个 Sec-WebSocket-Accept 头。
  • Sec-WebSocket-Accept 头是由 Sec-WebSocket-Key 头的值加上一个特定的 GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11),然后通过 SHA-1 加密,最后再进行 Base64 编码得到的。
    |  HTTP Response (Upgrade: websocket)    |
    |<---------------------------------------|

    至此,websocket就建立成功了。

2、Hyper的实现

读取upgrade头

src/proto/h1/role.rs中,在解析HTTP头时:

Untitled

Untitled

可以看到有两种情况需要进行upgrade,一种是Connect方法进行HTTP请求时,另外一种就是http头包含了upgrade的字段;

然后就会进行准备upgrade流程:

Untitled

准备一个Pending状态的upgrade放到State里面。

Untitled

通过pending_upgrade可以获取到是否有需要upgrade需要处理。

Untitled

然后在Connection的poll循环中,拿到upgrade,然后简单的把IO包装为Upgraded类型通过channel发送过去就完成了websocket的upgrade

Untitled

Untitled

那么应用程序怎么得到这个io呢

Untitled

放到了头的extensions里面,通过获取OnUpgrade类型的值就可以获取到。

Upgrade实现了Read和Write,就可以当一个双向IO进行数据发送通信了

Untitled

可以看到,hyper只是提供了一个upgrade的机制,让所有携带了upgrade字段的请求,能够获取到tcp连接的io,也没有针对websocket的处理,也就是一个通用的upgrade,同时从代码中也能看到,如果是connect方法的请求,也会进行同样的upgrade处理。那么我们再来看一下warp中提供的websoket机制是在hyper的基础上完整实现websocket流程的。

3、warp中WebSocket的实现

首先,会判断Connection(在warp中就是请求头)中是否包含upgrade的字段,hyper能读取upgrade字段,当然app也能读取upgrade字段。然后判断upgrade的值是否为websocket,并且额外判断了sec-websocket-version的版本是否为13版本,看来warp只支持13版本,然后读取了seb-websocket-key的值和hyper准备好的OnUpgrade,封装为一个Ws结构体里面。

Untitled

再来看warp提供的websokcet例子:

Untitled

Untitled

Untitled

结合warp的代码,可以看到,会启动一个tokio 异步任务,等待hyper的upgrade channel返回io,然后将io封装了一下,为WebSocket,然后方便使用,之后执行具体的业务逻辑。

同时后面回的响应中可以看到

状态码为: 101,表示协议切换

connection: upgrade

upgrade: websocket

sec-websocket-accept:

这儿是计算sec-websocket-accept的地方:

Untitled

可见和websocket的流程是一致的。

这儿我有一个疑问,如果在tokio::spawn之后,立马进行io的写入,会不会影响到websocket到response呢??

其实应该是没有问题,hyper已经考虑到了,他会在整个http连接done之后,才会拿出io来通过channel发送给用户:

Untitled

Untitled

会把所有read和write的请求都处理完成之后,才会拿出io给upgrade。

可以看到,所谓的upgrade,其实就是在连接用于某些特定的操作之前,进行的一个协商过程,一般都是一个req,一个resp,之后就按照新的协议进行数据传输了。