sse-stream
A lightweight Rust crate for decoding and encoding Server-Sent Events over any http_body-compatible HTTP body.
Repository Health
Technical Analysis
sse-stream is a Rust crate that converts between raw HTTP bodies and Server-Sent Events (SSE), built directly on the http_body::Body trait rather than a specific HTTP client or server. It exposes SseStream, a Stream that incrementally parses an incoming body into typed Sse events (event/data/id/retry fields), and SseBody, a Body implementation that encodes a stream of Sse values back into wire format, including an optional keep-alive heartbeat for long-lived connections.
Because it depends only on http-body and http-body-util, it works transparently with reqwest response bodies, hyper server responses, axum handlers, or any other client/server built on the http crate ecosystem, making it a drop-in primitive for building or consuming SSE-based APIs (chat streaming, live updates, MCP-style event transports) without hand-rolling a line-oriented parser.
What You Get
- Streaming SSE decoder (
SseStream) that incrementally parses event/data/id/retry fields from a chunked HTTP body, correctly handling partial lines split across network reads - SSE encoder (
SseBody) implementinghttp_body::Body, letting you turn any stream ofSsevalues into a ready-to-serve response body - Configurable keep-alive support (
KeepAlive) that injects heartbeat comments on a timer to keep idle SSE connections open through proxies and load balancers - A typed
Ssestruct with a builder-style API (.event(), .data(), .id(), .retry()) instead of hand-formatting SSE wire syntax - BOM header and CR/LF edge-case handling built into the parser, so it doesn’t break on real-world SSE streams from arbitrary origins
Common Use Cases
- Consuming a streaming LLM/chat API response (e.g. a reqwest body) as a typed sequence of SSE events instead of parsing raw bytes
- Building a server endpoint (axum/hyper) that streams live updates, notifications, or progress events to a browser EventSource client
- Implementing an MCP (Model Context Protocol) or webhook-style transport that layers structured JSON events on top of SSE framing
- Adding keep-alive heartbeats to long-lived SSE responses so they survive reverse-proxy idle timeouts
Under The Hood
Architecture
sse-stream is a small, two-module codec: stream.rs implements SseStream<B> as a manual pull-based Stream state machine wrapping an http_body_util::BodyDataStream<B> — it buffers an unfinished line across chunk boundaries, tracks an in-progress Sse event and a ready queue, and handles UTF-8 BOM detection via a small state enum. body.rs mirrors this on the encode side with SseBody<S, T>, a pin_project_lite-generated Body implementation that wraps an event stream plus an optional KeepAliveStream<T> driven by a generic Timer trait, so a runtime-specific timer can be plugged in without the crate depending on one async executor. The top-level Sse struct in lib.rs is a plain data type with a builder API and a From<Sse> for Bytes encoder. Decode and encode are independent, symmetric halves that both build on http_body::Body/bytes::Buf, and the one cross-cutting concern (timing) is abstracted behind a trait rather than hardcoded to a runtime.
Tech Stack
The dependency surface is minimal and protocol-focused: bytes, futures-util, http-body, http-body-util, and pin-project-lite, plus an optional tracing feature — no framework, ORM, or database, since this is a pure HTTP-body codec crate. Dev-dependencies pull in tokio (full), reqwest (stream feature), hyper/hyper-util, and axum, showing the crate is exercised against real reqwest, hyper, and axum stacks rather than only synthetic unit tests. CI (GitHub Actions) runs cargo clippy --all-targets --all-features -D warnings and cargo fmt --check on every push and PR, with a separate release workflow publishing to crates.io on tag.
Code Quality
Coverage comes from integration-style tests rather than inline unit tests: one suite exercises the decoder against malformed, partial, BOM-prefixed, and CRLF-terminated inputs, while separate suites round-trip real client and server bodies end-to-end through reqwest, hyper, and an actual axum handler. Error handling is explicit and typed via a dedicated Error enum (invalid line, duplicated event/id/retry fields, UTF-8 and integer parse failures) that implements std::error::Error with source() chaining; there is no unsafe code and no stray unwrap/panic in the parse path. Naming follows standard Rust convention, and the crate is clippy- and fmt-clean per its CI gates.
API Design
The public surface is deliberately small: a typed Sse struct with chainable builder methods (.event(), .data(), .id(), .retry(), .retry_duration()) replaces hand-formatted SSE text, and SseStream/SseBody slot directly into any http_body::Body-based client or server without adapter code. The main differentiator versus other Rust SSE crates is being transport- and runtime-agnostic on both the decode and encode side — most existing options hard-code one direction (a client-only event-source parser, or a server framework’s built-in SSE extractor) — while KeepAlive support is opt-in and pluggable via the Timer trait rather than assuming a specific async runtime. It is a faithful, spec-conformant implementation rather than a novel protocol, but the dual-direction, transport-agnostic design is a genuine ergonomic step up from single-direction alternatives.