Expand description
§fastwebsockets-stream
fastwebsockets-stream provides an adapter that exposes a fastwebsockets::WebSocket
as an AsyncRead / AsyncWrite byte stream compatible with tokio and
utilities such as tokio_util::codec::Framed.
§Overview
The adapter type is WebSocketStream, which wraps a fastwebsockets::WebSocket<S>
and presents websocket application payloads as a continuous byte stream.
This is useful when you want to reuse existing codecs (length-delimited,
line-based, protobuf, etc.) or any code that operates on AsyncRead /
AsyncWrite without reimplementing websocket framing logic.
The adapter supports both text and binary payloads (controlled by
PayloadType) and will validate that incoming data frames match the
configured payload type. Control frames (Ping/Pong) are handled automatically
by the underlying fastwebsockets::WebSocket (auto-pong) and Close frames
are translated to EOF for AsyncRead consumers.
§Key types
WebSocketStream<S>— the main adapter implementingtokio::io::AsyncReadandtokio::io::AsyncWrite.PayloadType— selects whether the stream works with Text or Binary application frames.
§Examples
The example below demonstrates a minimal server and client that speak a
simple binary protocol in the same process: it creates an actual TCP
listener, upgrades the connection to WebSocket using fastwebsockets +
hyper, and then uses WebSocketStream as an AsyncRead/AsyncWrite
stream on both ends.
use fastwebsockets::{WebSocketError, handshake, upgrade};
use fastwebsockets_stream::{PayloadType, WebSocketStream};
use http_body_util::Empty;
use hyper::body::Bytes;
use hyper::body::Incoming;
use hyper::header::{CONNECTION, UPGRADE};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use std::future::Future;
use std::net::Ipv4Addr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
struct SpawnExecutor;
impl<F> hyper::rt::Executor<F> for SpawnExecutor
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
fn execute(&self, fut: F) {
tokio::task::spawn(fut);
}
}
// Server-side connection handler: upgrades the request, then echoes a
// single binary message back to the client over `WebSocketStream`.
async fn handle(mut request: Request<Incoming>) -> Result<Response<Empty<Bytes>>, WebSocketError> {
assert!(upgrade::is_upgrade_request(&request));
let (response, ws_fut) = upgrade::upgrade(&mut request)?;
tokio::spawn(async move {
let ws = ws_fut.await.unwrap();
let mut ws_stream = WebSocketStream::new(ws, PayloadType::Binary);
let mut buf = [0u8; 6];
ws_stream.read_exact(&mut buf).await.unwrap();
ws_stream.write_all(&buf).await.unwrap();
ws_stream.shutdown().await.unwrap();
});
Ok(response)
}
// Bind an ephemeral local port and remember the address we actually got.
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0u16)).await.unwrap();
let addr = listener.local_addr().unwrap();
// Accept and upgrade incoming connections in the background.
tokio::spawn(async move {
loop {
let (stream, _) = listener.accept().await.unwrap();
let io = TokioIo::new(stream);
tokio::spawn(async move {
let _ = http1::Builder::new()
.serve_connection(io, service_fn(handle))
.with_upgrades()
.await;
});
}
});
// Client: connect to the port the server actually bound above.
let stream = TcpStream::connect(addr).await.unwrap();
let request = Request::builder()
.method("GET")
.uri(format!("ws://{addr}"))
.header("Host", addr.to_string())
.header(UPGRADE, "websocket")
.header(CONNECTION, "upgrade")
.header("Sec-WebSocket-Key", handshake::generate_key())
.header("Sec-WebSocket-Version", "13")
.body(Empty::<Bytes>::new())
.unwrap();
let (ws, _response) = handshake::client(&SpawnExecutor, request, stream)
.await
.unwrap();
let mut ws_stream = WebSocketStream::new(ws, PayloadType::Binary);
let n = ws_stream.write(b"Hello!").await.unwrap();
assert_eq!(n, 6);
let mut buf = [0u8; 6];
ws_stream.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"Hello!");§Notes and caveats
- Each call to
AsyncWrite::poll_writewill produce a single WebSocket data frame containing exactly the bytes provided in that call. If you need to stream a large logical message across multiple websocket frames, you should implement framing at the codec layer above theWebSocketStream. - If the peer sends a data frame with an opcode that doesn’t match the
configured
PayloadType(e.g. the stream is configuredBinarybut the peer sendsText), reads will return an error. into_inneron the adapter will return the innerWebSocketonly if no read/write future currently owns it (i.e. there is no in-flight read or write). If an operation is in-progress,into_innerwill returnNone.
§See Also
§License
Licensed under the MIT License.
See the LICENSE file in the repository root for details.
Structs§
- WebSocket
Stream - An
AsyncRead/AsyncWriteadapter over afastwebsockets::WebSocket.
Enums§
- Payload
Type - Stream payload type.