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头时:
可以看到有两种情况需要进行upgrade,一种是Connect方法进行HTTP请求时,另外一种就是http头包含了upgrade的字段;
然后就会进行准备upgrade流程:
准备一个Pending状态的upgrade放到State里面。
通过pending_upgrade可以获取到是否有需要upgrade需要处理。
然后在Connection的poll循环中,拿到upgrade,然后简单的把IO包装为Upgraded类型通过channel发送过去就完成了websocket的upgrade
那么应用程序怎么得到这个io呢
放到了头的extensions里面,通过获取OnUpgrade类型的值就可以获取到。
Upgrade实现了Read和Write,就可以当一个双向IO进行数据发送通信了
可以看到,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结构体里面。
再来看warp提供的websokcet例子:
结合warp的代码,可以看到,会启动一个tokio 异步任务,等待hyper的upgrade channel返回io,然后将io封装了一下,为WebSocket,然后方便使用,之后执行具体的业务逻辑。
同时后面回的响应中可以看到
状态码为: 101,表示协议切换
connection: upgrade
upgrade: websocket
sec-websocket-accept:
这儿是计算sec-websocket-accept的地方:
可见和websocket的流程是一致的。
这儿我有一个疑问,如果在tokio::spawn之后,立马进行io的写入,会不会影响到websocket到response呢??
其实应该是没有问题,hyper已经考虑到了,他会在整个http连接done之后,才会拿出io来通过channel发送给用户:
会把所有read和write的请求都处理完成之后,才会拿出io给upgrade。
可以看到,所谓的upgrade,其实就是在连接用于某些特定的操作之前,进行的一个协商过程,一般都是一个req,一个resp,之后就按照新的协议进行数据传输了。