Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
h2ts-server
Make a WebSocket look like a raw TCP byte stream — then serve or proxy HTTP/2 over it.
h2ts-server is the Rust server side of h2ts, which gives browsers a real HTTP/2 client by tunnelling HTTP/2 frames inside a WebSocket. This crate terminates that WebSocket and hands the raw bytes to any HTTP/2 server — as a standalone proxy, or in-process behind your own routes. It pairs with the @debdattabasu/h2ts TypeScript client and the h2ts-client Rust/WASM client — or any client that forwards a byte stream over a WebSocket.
A WebSocket carries discrete messages; HTTP/2 needs a continuous byte stream. This crate bridges the two — WebSocket message payloads become a byte stream, so h2c (cleartext HTTP/2, prior-knowledge) framing rides straight through.
Entry points
WsByteStream— a WebSocket presented asAsyncRead + AsyncWrite. Anything that speaks TCP works over it unchanged.serve_h2(ws, service)— run anyhyper::Service(aservice_fn, anaxum::Router, atowerservice) as HTTP/2 over the tunnel.bridge(ws, peer)— full-duplex byte pump between a WebSocket and anyAsyncRead + AsyncWritepeer (a TCP upstream, an in-process server, …).accept(&mut req)— the server-side WebSocket handshake as a plain hyper handler, pluggable into any hyper/axum route.h2ts-proxy— a standalone WS → upstream-h2c proxy binary that ships with the crate; a drop-in, in-Rust replacement forwebsockify.
Sub-frame streaming
Framing is done by wslay (vendored C, via the wslay-sys crate). Driven through its event API with buffering off, it streams each frame's payload incrementally — it never buffers a whole frame, no matter how large — which matters for a proxy carrying arbitrary TCP. The RFC 6455 handshake is done in-crate (sha1 + base64); there is no pure-Rust WebSocket-framing dependency.
Usage
In-process — serve any hyper service over the tunnel
use ;
// In a hyper/axum route handler:
async
Prefer a raw byte tunnel instead of HTTP/2? Hand the upgraded ws to WsByteStream::new(ws) (AsyncRead + AsyncWrite) or bridge(ws, peer).
Standalone proxy
# └ listen (ws) └ upstream h2c └ keepalive secs (0/omit = off)
Now a WebSocket client connecting to ws://127.0.0.1:8091 reaches the HTTP/2 server on :8000. The proxy requires the h2ts subprotocol by default; add --allow-implicit-codec to accept any offered subprotocol (a generic byte tunnel / websockify replacement).
Control frames & keepalive
wslay auto-answers pings with pongs and echoes closes. A BridgeConfig (passed via the *_with_config / bridge_with / serve_h2_with variants) lets you go further — and it applies identically to both the raw-byte and HTTP/2 pathways, since control frames are handled at the WebSocket layer beneath:
- Observe every control frame:
on_ping,on_pong, andon_close(which fires once with why the tunnel ended — peer close, keepalive timeout, or1006abnormal). - Send your own control frames via a
control_channel():WsControl::ping/pong/close. - Set the close code + reason sent on teardown.
- Turn on server-initiated keepalive (
KeepAlive): ping when idle, close if no pong arrives in time.
Idle connections (serve)
serve_h2_with_config adds an optional HTTP/2 idle timeout to the serve path: after that long with no open HTTP/2 streams, the connection is closed with a graceful GOAWAY (then the WebSocket close), so a healthy but idle client reconnects fresh. It's distinct from keepalive — which detects a dead peer — so a client that keeps answering keepalive pings but opens no streams is still reaped; pings never reset the timer, only streams do. Off by default. (The standalone proxy can't do this: it forwards raw bytes and has no HTTP/2-stream visibility, so idle reaping there is the upstream server's job.)
use ;
use Duration;
// keepalive stays on (BridgeConfig default); reap after 5 min with no streams.
let _ = serve_h2_with_config.await;
Subprotocol negotiation
The client offers the h2ts subprotocol. accept echoes it and rejects a client that doesn't offer it (400 via err.rejection_response()). accept_with hands your handler the full offered list to choose from; accept_with_options with allow_implicit_codec accepts whatever codec the client offered first.
Build requirements
This crate depends on wslay-sys, which compiles vendored C and generates bindings at build time:
- A C compiler (
ccfinds one automatically). libclang(required bybindgen). macOS: ships with the Command Line Tools. Debian/Ubuntu:apt install libclang-dev.
License
Dual-licensed under MIT or Apache-2.0, at your option. Part of h2ts.