WebSocket and SSE in Hyperlane
Project Code:https://github.com/hyperlane-dev/hyperlane
Hyperlane is a lightweight, high-performance, cross-platform Rust HTTP server library built on Tokio. Beyond standard HTTP request-response cycles, Hyperlane provides first-class support for two important real-time communication protocols: WebSocket and Server-Sent Events (SSE). In this article, we will explore how to implement both protocols using Hyperlane's powerful middleware and streaming APIs.
Table of Contents
- Introduction to Real-Time Communication
- WebSocket Protocol Overview
- WebSocket Implementation in Hyperlane
- WebSocket Frame Handling
- Broadcasting with WebSocket
- Server-Sent Events (SSE) Overview
- SSE Implementation in Hyperlane
- Client-Side Code Examples
- WebSocket vs SSE: When to Use Each
- Conclusion
Introduction to Real-Time Communication
Modern web applications often require real-time data delivery — think chat applications, live dashboards, notification systems, and collaborative editing tools. Hyperlane supports two protocols that enable real-time communication:
- WebSocket: A full-duplex, bidirectional communication channel over a single TCP connection.
- SSE (Server-Sent Events): A unidirectional channel where the server pushes events to the client over HTTP.
Both protocols are supported through Hyperlane's streaming and middleware APIs, making it straightforward to add real-time capabilities to your application.
WebSocket Protocol Overview
WebSocket is a communication protocol that provides full-duplex communication channels over a single TCP connection. Unlike HTTP, which follows a request-response model, WebSocket allows both the client and server to send messages independently at any time after the initial handshake.
The connection starts as an HTTP request with an Upgrade: websocket header. If the server accepts, the protocol is upgraded, and the connection remains open for ongoing message exchange.
WebSocket Implementation in Hyperlane
Hyperlane makes it easy to handle WebSocket connections through its middleware system. The key is to use the #[is_ws_upgrade_type] attribute macro to detect WebSocket upgrade requests and the #[try_get_websocket_request] macro to extract the WebSocket frame data.
Basic WebSocket Handler
#[route("/ws_upgrade_type")]
struct Websocket;
impl ServerHook for Websocket {
async fn new(_: &mut Stream, _: &mut Context) -> Self {
Self
}
#[is_ws_upgrade_type]
#[try_get_websocket_request(body)]
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
let body_list: Vec<ResponseBody> = WebSocketFrame::create_frame_list(&body);
stream.send_list(body_list).await;
Status::Continue
}
}
How It Works
-
Route Registration: The
#[route("/ws_upgrade_type")]macro maps the WebSocket endpoint to theWebsocketstruct. -
Protocol Detection: The
#[is_ws_upgrade_type]attribute checks whether the incoming request is a WebSocket upgrade request. -
Frame Extraction: The
#[try_get_websocket_request(body)]attribute extracts the WebSocket frame data from the request and binds it to thebodyvariable. -
Frame Processing:
WebSocketFrame::create_frame_list(&body)converts the raw frame data into a list of response frames. -
Response Sending:
stream.send_list(body_list).awaitsends all frames back to the client.
Attribute Macros for Sending
Hyperlane provides several attribute macros for sending data over WebSocket connections:
#[try_send]
#[send]
#[try_flush]
#[flush]
#[closed]
These macros provide fine-grained control over the send operations, including error handling (try_send vs send), flushing buffers, and closing connections.
WebSocket Frame Handling
WebSocket communication revolves around frames. Each frame contains a small header and a payload. Hyperlane abstracts this complexity through the WebSocketFrame type and its helper methods.
Creating Frame Lists
let body_list: Vec<ResponseBody> = WebSocketFrame::create_frame_list(&body);
This function takes raw body data and produces a list of ResponseBody frames ready for transmission. The frame list can then be sent using stream.send_list().
Sending Frames
stream.send_list(body_list).await;
For individual frame sending, you can use:
let data: Vec<u8> = ctx.get_mut_response().build();
stream.try_send(data).await;
stream.send(data).await;
stream.try_send_list(&frame_list).await;
stream.try_flush().await;
Connection Management
After establishing a WebSocket connection, you need to manage its lifecycle:
stream.set_closed(true);
let keep_alive: bool = stream.is_keep_alive(ctx.get_request().is_enable_keep_alive());
while stream.try_get_http_request().await.is_ok() {
if !ctx.get_request().is_enable_keep_alive() {
stream.set_closed(true);
break;
}
}
This pattern allows you to keep the connection alive for multiple message exchanges and gracefully close it when needed.
Broadcasting with WebSocket
A common use case for WebSocket is broadcasting messages to multiple connected clients. While Hyperlane provides the low-level primitives, you can build a broadcast mechanism by maintaining a collection of connected streams and iterating over them to send messages.
The hyperlane-broadcast utility from the recommended tools list is specifically designed for this purpose, making it easy to implement pub/sub patterns with WebSocket connections.
Server-Sent Events (SSE) Overview
Server-Sent Events (SSE) is a simpler protocol compared to WebSocket. It allows the server to push events to the client over a standard HTTP connection. SSE is unidirectional — only the server can send messages — but it has several advantages:
- Simplicity: SSE uses standard HTTP, so it works with existing infrastructure.
- Automatic Reconnection: Browsers automatically reconnect if the connection drops.
- Text-Based: SSE messages are plain text, making them easy to debug.
SSE is ideal for use cases like live news feeds, stock tickers, progress indicators, and notification streams.
SSE Implementation in Hyperlane
Implementing SSE in Hyperlane involves setting the appropriate headers and then streaming events to the client.
Setting Up the SSE Response
let data: Vec<u8> = ctx
.get_mut_response()
.set_header(CONTENT_TYPE, TEXT_EVENT_STREAM)
.set_body(Vec::new())
.build();
stream.try_send(data).await;
The key header is Content-Type: text/event-stream, which tells the browser to treat the response as an SSE stream. The body is initially empty — events will be sent incrementally.
Sending SSE Events
for i in 0..10 {
let body: String = format!("data:{i}{HTTP_DOUBLE_BR}");
stream.try_send(&body).await;
}
Each event follows the SSE format: data: <message>\n\n. The HTTP_DOUBLE_BR constant represents the double newline that marks the end of each event.
SSE Event Format Details
A complete SSE event can include several fields:
-
data:— The event payload. -
event:— The event type name (optional). -
id:— The event ID for reconnection tracking (optional). -
retry:— The reconnection time in milliseconds (optional).
In the example above, we use the simplest form with just the data: field.
Client-Side Code Examples
To complete the picture, here is how a browser client would connect to both WebSocket and SSE endpoints.
WebSocket Client (JavaScript)
const ws = new WebSocket('ws://localhost:8080/ws_upgrade_type');
ws.onopen = () => {
console.log('WebSocket connected');
ws.send('Hello Server!');
};
ws.onmessage = (event) => {
console.log('Received:', event.data);
};
ws.onclose = () => {
console.log('WebSocket disconnected');
};
SSE Client (JavaScript)
const eventSource = new EventSource('http://localhost:8080/sse');
eventSource.onmessage = (event) => {
console.log('SSE Event:', event.data);
};
eventSource.onerror = () => {
console.log('SSE connection error, reconnecting...');
};
Both APIs are built into modern browsers, requiring no additional libraries.
WebSocket vs SSE: When to Use Each
| Feature | WebSocket | SSE |
|---|---|---|
| Direction | Bidirectional | Server-to-client only |
| Protocol | ws:// (separate protocol) | HTTP (standard) |
| Reconnection | Manual implementation | Automatic (browser) |
| Binary Data | Supported | Text only |
| Complexity | Higher | Lower |
| Use Case | Chat, gaming, collaboration | Feeds, notifications, dashboards |
Choose WebSocket when you need bidirectional communication, binary data support, or low-latency message exchange. Choose SSE when you only need server-to-client push and want simpler implementation with automatic reconnection.
Conclusion
Hyperlane provides robust support for both WebSocket and SSE, enabling you to build real-time web applications in Rust. The WebSocket implementation leverages Hyperlane's attribute macros like #[is_ws_upgrade_type] and #[try_get_websocket_request] to provide a clean, declarative API for handling WebSocket connections. The SSE implementation uses standard HTTP streaming with the text/event-stream content type.
Both protocols have their place in modern web development. WebSocket excels in bidirectional communication scenarios, while SSE shines in simple server-to-client push use cases. By understanding both patterns and leveraging Hyperlane's streaming APIs, you can build responsive, real-time applications that deliver data to users instantly.
The key building blocks — stream.send(), stream.try_send(), stream.send_list(), and the various attribute macros — give you full control over how data is transmitted, while the middleware system ensures clean separation of concerns between your real-time logic and the rest of your application.
Project Code:https://github.com/hyperlane-dev/hyperlane
Top comments (0)