Rust上的服务器发送事件:无需WebSocket即可实时发送数据

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

当WebSocket过于复杂,或者为什么SSE再次流行

\n\n

想象一下:您需要将数据从服务器实时发送到客户端。汇率、新消息通知、服务器日志、文件处理进度。首先想到的是WebSocket。但如果您不需要双向通道呢?如果客户端只监听,而服务器只发送呢?

\n\n

这时服务器发送事件(SSE)登场了——这是一种协议,允许服务器通过普通的HTTP连接向客户端发送数据。无需多余的握手,无需复杂的协议,无需为代理和负载均衡器头疼。而且,在Rust上,它的表现简直完美。

\n\n

在本文中,我们将讲解如何使用流行的框架从零开始在Rust上搭建SSE。您将看到可运行的示例,了解潜在陷阱,并明白何时SSE是您的最佳选择。

\n\n

什么是服务器发送事件,为什么选择Rust?

\n\n

SSE是W3C标准(EventSource API),允许服务器通过HTTP向客户端发送流式数据。客户端连接到端点,服务器开始以text/event-stream格式发送消息。这一切都基于普通的HTTP/1.1或HTTP/2,无需特殊代理,并得到所有现代浏览器的支持。

\n\n

为什么选择Rust?因为Rust为我们提供了:

\n
    \n
  • 无惊无喜的异步性——Tokio和async-std允许在单个线程上维持数千个SSE连接
  • \n
  • 内存安全——在多线程流式传输中,没有内存泄漏,没有竞态条件
  • \n
  • 极低的资源消耗——一个SSE连接仅消耗几KB
  • \n
  • 生态系统——Actix Web、Rocket、Axum——所有这些都原生支持SSE,或只需极少的扩展
  • \n
\n\n

SSE如何工作:解析协议

\n\n

客户端在浏览器中创建一个EventSource,指定URL。服务器以Content-Type: text/event-stream头响应,并开始按以下格式发送字符串:

\n\n
data: 你好,世界!\\n\\n
\n\n

每条消息是一个块,其中data:包含有效负载。消息之间用两个换行符分隔。可以传输JSON、数字、字符串——任何内容。此外,还有id、event和retry字段。

\n\n

以下是一个SSE消息的最小示例:

\n\n
id: 1\nevent: update\ndata: {"status": "processing", "progress": 42}\n\n
\n\n

JavaScript客户端将其作为update事件接收,其中data字段包含JSON。

\n\n

使用Actix Web编写Rust SSE服务器

\n\n

我们从最流行的框架——Actix Web开始。安装依赖项:

\n\n
[dependencies]\nactix-web = "4"\nactix-rt = "2"\ntokio = { version = "1", features = ["full"] }\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"
\n\n

现在编写一个简单的端点,每2秒发送一次当前时间:

\n\n
use actix_web::{get, web, App, HttpRequest, HttpResponse, HttpServer, middleware};\nuse actix_web::rt::time;\nuse futures::stream::StreamExt;\nuse std::time::Duration;\n\n#[get("/events")]\nasync fn stream_events(req: HttpRequest) -> HttpResponse {\n    let mut rx = tokio::sync::broadcast::channel::<String>(100).1;\n\n    let stream = async_stream::stream! {\n        loop {\n            let message = format!("data: {}\\n\\n", chrono::Local::now());\n            yield Ok::<_, actix_web::Error>(\n                web::Bytes::from(message)\n            );\n            time::sleep(Duration::from_secs(2)).await;\n        }\n    };\n\n    HttpResponse::Ok()\n        .insert_header(("Content-Type", "text/event-stream"))\n        .insert_header(("Cache-Control", "no-cache"))\n        .insert_header(("Connection"                

博客

书籍推荐