Skip to content

striped-zebra-dev/http-streaming-proto

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Outbound API Gateway — Prototype

A Rust prototype exploring a single unified interface for proxying HTTP requests, SSE streams, bidirectional WebSockets, and (planned) WebTransport — all through one method.

The Idea

An outbound API gateway sits between your service and upstream APIs. It resolves endpoints, authenticates, rate-limits, and forwards traffic. The challenge: HTTP, SSE, WebSocket, and WebTransport have very different semantics, yet from a gateway's perspective they all follow the same pattern — receive a request, forward it upstream, return a response.

This prototype proves that a single trait method can unify all of them:

#[async_trait]
pub trait GatewayProxy: Send + Sync {
    async fn proxy_request(
        &self,
        req: http::Request<Body>,
    ) -> Result<http::Response<Body>, StreamingError>;
}

The trick is the Body enum:

pub enum Body {
    Empty,              // GET, HEAD, DELETE — no payload
    Bytes(Bytes),       // Regular HTTP — buffered JSON, form data, etc.
    Stream(BodyStream), // SSE, WebSocket messages, chunked uploads
}

Each protocol maps naturally onto Request<Body>Response<Body>:

Protocol Request Body Response Body
HTTP Bytes or Empty Bytes
SSE Bytes or Empty Stream (events)
WebSocket Stream (→ upstream) Stream (← upstream)
WebTransport Stream Stream

No framework types leak into the gateway trait. The handler layer (e.g. axum) converts framework-specific types at the edges — the proxy itself works exclusively with http crate types and Body.

Protocol Detection

The proxy inspects the request/response to dispatch automatically:

  • WebSocket — detected via Connection: Upgrade + Upgrade: websocket headers on the request
  • SSE — detected via Content-Type: text/event-stream on the upstream response
  • HTTP — everything else; response body is buffered into Bytes

Project Structure

src/
├── lib.rs                    # Public API exports
├── error.rs                  # StreamingError enum
├── gateway/
│   ├── proxy.rs              # GatewayProxy trait
│   ├── mock_proxy.rs         # Reference implementation (reqwest + tungstenite)
│   ├── body.rs               # Body enum, BodyStream type alias
│   ├── detect.rs             # is_websocket_upgrade()
│   └── sse_stream.rs         # ServerEventsStream<T>, FromSseEvent trait
├── sse/
│   ├── event.rs              # SseEvent struct (W3C-compliant fields)
│   ├── detect.rs             # is_sse_response()
│   ├── parse.rs              # parse_sse_stream() — chunk-level SSE parser
│   └── response.rs           # sse_response() — rebuild SSE from event stream
└── ws/
    ├── upstream.rs           # connect_upstream(), UpstreamWs type
    └── bridge.rs             # bridge_websocket() — bidirectional forwarding

Key Components

GatewayProxy trait

The core abstraction. Implement this to build a gateway that handles all supported protocols through proxy_request. The included MockGatewayProxy is a reference implementation that forwards to a real upstream using reqwest (HTTP/SSE) and tokio-tungstenite (WebSocket).

Body and BodyStream

Body abstracts over empty, buffered, and streaming payloads. Conversions go both ways:

  • body.into_bytes() — buffers a stream into Bytes (async)
  • body.into_stream() — wraps any variant as a BodyStream

SSE Tooling

  • parse_sse_stream(body) — parses a raw byte stream into SseEvents following the W3C EventSource spec (multi-line data, comments, retry, event types)
  • sse_response(events) — serializes an event stream back into an HTTP response with correct headers (text/event-stream, no-cache, X-Accel-Buffering: no)
  • ServerEventsStream<T> — ergonomic wrapper that detects SSE from a Response<Body>, parses events, and optionally deserializes them into a custom type via FromSseEvent
  • sse_json_event!(MyType) — one-line macro to implement FromSseEvent for JSON-deserializable types

WebSocket Tooling

  • connect_upstream(url, headers) — establishes a WebSocket connection to an upstream, forwarding custom headers (auth, etc.)
  • bridge_websocket(client_ws, url, headers) — bidirectional frame forwarding between an axum WebSocket and an upstream, handling Text, Binary, Ping/Pong, and Close frames via tokio::select!

Usage Examples

HTTP proxy

let req = http::Request::builder()
.method("POST")
.uri("/echo")
.header("content-type", "application/json")
.body(Body::Bytes(Bytes::from(r#"{"hello":"world"}"#)))
.unwrap();

let resp = gateway.proxy_request(req).await?;
let bytes = resp.into_body().into_bytes().await?;

SSE — typed event stream

#[derive(Deserialize)]
struct ChatChunk {
    delta: String
}
sse_json_event!(ChatChunk);

let resp = gateway.proxy_request(req).await?;
let mut stream = ServerEventsStream::<ChatChunk>::from_response(resp)
.expect("expected SSE response");

while let Some(Ok(chunk)) = stream.next().await {
print!("{}", chunk.delta);
}

WebSocket — stream-based proxy in a handler

// In the handler layer, convert axum WebSocket frames to/from Body::Stream,
// then call proxy_request — the gateway sees the same trait interface.
let req = http::Request::builder()
.uri("/ws/endpoint")
.header("connection", "Upgrade")
.header("upgrade", "websocket")
.body(Body::Stream(client_to_upstream_stream))
.unwrap();

let resp = gateway.proxy_request(req).await?;
// resp.into_body() is Body::Stream with upstream messages

Tests

The test suite covers all protocols end-to-end with a MockServer that provides HTTP echo, SSE, and WebSocket endpoints:

cargo test

Tests include HTTP round-trips, SSE parsing and serialization, SSE-to-JSON deserialization, WebSocket echo and streaming, bidirectional bridging, body conversions, and handler dispatch patterns.

Status

This is a prototype. It validates the unified proxy_request design across HTTP, SSE, and WebSocket. WebTransport support is planned but not yet implemented.

Dependencies

Crate Purpose
http Framework-agnostic HTTP types
bytes, futures-core, futures-util Stream and byte abstractions
reqwest (stream) HTTP client for upstream requests
tokio-tungstenite (rustls) WebSocket client for upstream connections
axum (ws) Web framework for handler layer and tests
tokio Async runtime
serde, serde_json JSON serialization
async-trait Async trait support
thiserror Error derive macro
tracing Structured logging

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages