vlambda博客
学习文章列表

【投稿】actix-websocket 使用 protocol的一点个人理解

actix的docs和example对 protocol 的使用有点省略,对着源码实验了下,终于搞清楚了。
标准浏览器websocket的构造函数 WebSocket(url[, protocols]) 会有个可选参数 protocols ,即一个字符串形式的约定协议。
对于actix的websocket的例子一般如下,即用 actix_web_actors::ws::start 来初始化websocket。
 
   
   
 
struct MyWebSocket{}

async fn ws_index(r: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
ws::start(MyWebSocket::new(), &r, stream)
}
如果前端传了 protocol ,actix会响应请求然后自动关闭连接,并不能正常构建websocket连接。
这里整了我半天,后来才发现 actix_web_actors::ws 有一个初始函数叫 start_with_protocols ,必须用这个函数才能接收带有 protocol 的websocket连接。
 
   
   
 
pub fn start_with_protocols<A, T>(
actor: A,
protocols: &[&str],
req: &HttpRequest,
stream: T,
) -> Result<HttpResponse, Error>
where
A: Actor<Context = WebsocketContext<A>>
+ StreamHandler<Result<Message, ProtocolError>>,
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
{
let mut res = handshake_with_protocols(req, protocols)?;
Ok(res.streaming(WebsocketContext::create(actor, stream)))
}
start_with_protocols 比普通的 start 多了个 protocols 参数,试试了这个 protocols 其实代表的是合法的协议名列表,即前端传来的 protocol 必须在 protocols 里面才能正常构建websocket连接。
然后顺便看了 start 方法的源码,发现其实也调用了 handshake_with_protocols 这个方法,但会默认令合法协议 protocols=&[] ,也就是置空, 这样如果前端如果没传 protocol ,actix处理出的 protocol 则为 None ,可以满足构建要求, 如果不为空,则必然不在合法协议列表 protocols=&[] 里面,所以无法正常构建websocket连接。
不过令人困惑的是,在初始化以后,如果想在websocket的帧里获取 protocol 是什么,并不能像在处理路由句柄时里的 r: HttpRequest 里面直接 r.headers().get(&header::SEC_WEBSOCKET_PROTOCOL).unwrap().to_str().unwrap() 获取,所以我的解决办法是在自定义的 MyWebSocket 结构体里进行保存。
 
   
   
 
struct MyWebSocket {
protocol: String
}

impl MyWebSocket {
fn new(protocol: String) -> Self {
Self {protocol}
}
}

const PROTOCOLS: &[&str] = &["chat"];

async fn ws_index(r: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
let protocol = r.headers().get(&header::SEC_WEBSOCKET_PROTOCOL).unwrap().to_str().unwrap();
ws::start_with_protocols(MyWebSocket::new(protocol.to_string()), PROTOCOLS, &r, stream)
}
这样就可以在接收流处理句柄里拿到 protocol 了,根据websocket的帧协议来看,似乎确实不包含 protocol 的信息,所以大概也只能从请求头 headers 那里拿到 protocol 信息了。本来想在 ctx: &mut Self::Context 里找信息的,不过看了看源码似乎并没有找到存取 protocol 的api,实在整不明白,只能自己存了,不知道还有没有更正统的办法。
 
   
   
 
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWebSocket {
fn handle(
&mut self,
msg: Result<ws::Message, ws::ProtocolError>,
ctx: &mut Self::Context,
) {

println!("WS({}): {:?}", self.protocol, msg);

match msg {
Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
Ok(ws::Message::Pong(_)) => (),
Ok(ws::Message::Text(text)) => ctx.text(text),
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
Ok(ws::Message::Close(reason)) => {
ctx.close(reason);
ctx.stop();
}
_ => ctx.stop(),
}
}
}
总结一下,关于actix的websocket连接里获取 protocol 方法就是用 start_with_protocols 来初始化连接,通过 HttpRequest.headers().get(&header::SEC_WEBSOCKET_PROTOCOL).unwrap().to_str().unwrap() 来获取 protocol ,如果想在流处理获取 protocol 的信息,则需要在处理路由句柄时通过自定义结构体里提前存储 protocol 的信息。