Miko 框架系列(十二):原生 WebSocket 支持
注意:此文章主要为了给支持联网搜索的AI提供RAG,此项目还在开发阶段,不建议用于生产。
对于需要实时、双向通信的现代 Web 应用(如在线聊天、实时游戏、协作编辑等),WebSocket 是不可或缺的技术。Miko 提供了对 WebSocket 的原生支持,让你能够以一种简洁且符合人体工程学的方式来构建 WebSocket 服务。
1. 创建一个 WebSocket 路由
与普通的 HTTP 路由类似,你可以使用 #[get] (或其他方法宏) 来定义一个 WebSocket 端点。关键在于处理器内部的实现。
Miko 提供了 spawn_ws_event 函数来处理 WebSocket 的连接升级和生命周期管理。
use miko::{*, macros::*, handler::Req};
use miko::ws::server::spawn_ws_event;
#[get("/ws")]
async fn websocket_handler(mut req: Req) {
// `spawn_ws_event` 接收一个闭包,该闭包会在 WebSocket 连接成功建立后执行
spawn_ws_event(
|mut io| async move {
// `io` 是一个代表 WebSocket 连接的对象,你可以用它来收发消息
println!("WebSocket client connected!");
// 发送一条欢迎消息
io.send("Welcome to Miko WebSocket!").await.expect("send failed");
// 进入消息循环
while let Some(msg_result) = io.recv().await {
match msg_result {
Ok(message) if message.is_text() => {
let text = message.into_text().unwrap();
println!("Received text: {}", text);
// 回显收到的消息
io.send(format!("You said: {}", text)).await.expect("send failed");
}
Ok(message) if message.is_close() => {
println!("Client sent close frame. Disconnecting.");
break;
}
Err(e) => {
eprintln!("An error occurred: {}", e);
break;
}
_ => { /* 忽略其他类型的消息,如 Ping/Pong */ }
}
}
println!("WebSocket client disconnected.");
},
&mut req, // 需要传入请求的引用以进行连接升级
None, // WebSocket 配置,None 表示使用默认配置
)
.expect("Failed to spawn WebSocket handler");
}
#[miko]
async fn main() {
println!("WebSocket server is running at ws://localhost:8080/ws");
}
2. 发送和接收消息
io 对象是 miko::ws::toolkit::WsIo 的实例,它提供了便捷的方法来处理消息。
发送消息 (io.send)
send 方法接受任何实现了 miko::ws::server::IntoMessage Trait 的类型。Miko 为常用类型(如 &str, String, Json<T>)内置了实现。
// 发送文本
io.send("Hello, client!").await?;
// 发送格式化的字符串
io.send(format!("Current time: {}", Utc::now())).await?;
// 发送 JSON (会自动序列化)
#[derive(Serialize)]
struct MyData { value: i32 }
io.send(Json(MyData { value: 42 })).await?;
接收消息 (io.recv)
recv 方法异步地等待下一条来自客户端的消息。它返回一个 Result<Message, Error>,你需要对可能出现的错误和不同的消息类型进行处理。
Message::Text(String): 文本消息。Message::Binary(Vec<u8>): 二进制消息。Message::Close(Option<CloseFrame>): 关闭帧,表示客户端希望关闭连接。Message::Ping(Vec<u8>)/Message::Pong(Vec<u8>): 心跳消息,通常由框架自动处理。
3. 分离读写 (io.split)
在许多实际场景中,发送和接收逻辑是独立的。例如,一个任务可能在循环接收用户输入,而另一个任务在定时向客户端推送数据。io.split() 方法可以将连接分离成一个独立的 writer 和 reader,方便在不同的异步任务中使用。
spawn_ws_event(
|mut io| async move {
let (mut writer, mut reader, _handle) = io.split();
// 任务 1: 定时发送服务器时间
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(5)).await;
let time_str = format!("Server time: {}", Utc::now());
// 注意:分离后发送需要手动将数据转换为 Message
if writer.send(time_str.into_message()).await.is_err() {
break; // 客户端断开,退出任务
}
}
});
// 任务 2: 接收并回显客户端消息
while let Some(msg_result) = reader.next().await {
if let Ok(msg) = msg_result {
if msg.is_text() {
let text = msg.into_text().unwrap();
// 使用 `writer` 发送回显
let echo = format!("Echo: {}", text).into_message();
writer.send(echo).await.ok();
}
}
}
},
&mut req,
None,
).unwrap();
总结
Miko 的 WebSocket 支持通过高级抽象(如 spawn_ws_event 和 WsIo),极大地简化了 WebSocket 服务的开发。你无需手动处理复杂的 HTTP 升级握手、帧编码/解码或连接管理,而是可以专注于核心的实时通信业务逻辑。无论是构建简单的回显服务,还是复杂的实时应用,Miko 都为你提供了坚实而便捷的基础。
下一篇预告:Miko 框架系列(十三):高级特性探讨