fastwebsockets_stream/stream.rs
1use bytes::Bytes;
2use bytes::BytesMut;
3use fastwebsockets::{Frame, OpCode, Payload, WebSocket, WebSocketError};
4use futures::FutureExt;
5use futures::future::BoxFuture;
6use std::fmt::Debug;
7use std::io;
8use std::pin::Pin;
9use std::task::{Context, Poll};
10use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
11
12/// Future output type for operations that temporarily own the websocket.
13///
14/// The future returns either an owned `WebSocket<S>` back together with a
15/// result value `T`, or a `WebSocketError` if the operation failed.
16type FutureResult<S, T> = Result<(WebSocket<S>, T), WebSocketError>;
17
18/// Internal owned frame representation.
19///
20/// When we read a frame from `WebSocket::read_frame()` it borrows internal
21/// buffers. To be able to return both the websocket and the payload across an
22/// `await` point we copy the payload into an owned `Bytes` and store the opcode.
23struct PayloadFrame {
24 /// Opcode of the frame (Text/Binary/Close/etc).
25 opcode: OpCode,
26 /// Owned payload bytes of the frame.
27 payload: Bytes,
28}
29
30/// Read state machine for `WebSocketStream`.
31///
32/// We encode whether we are idle or currently running an owned future that has
33/// taken ownership of the underlying `WebSocket` to perform an asynchronous
34/// read operation. The owned future returns the websocket together with the
35/// read `PayloadFrame`.
36enum ReadState<S> {
37 /// No read in progress.
38 Idle,
39 /// A boxed future that owns the websocket and will produce a `PayloadFrame`
40 /// (and the websocket) when complete.
41 Reading(BoxFuture<'static, FutureResult<S, PayloadFrame>>),
42}
43
44/// Write state machine for `WebSocketStream`.
45///
46/// Similar to `ReadState`, but represents a write operation that owns the
47/// websocket until it completes. This single state is shared by regular
48/// writes, flushes, and the close performed by `poll_shutdown` — see
49/// [`PendingOp`] for how we keep track of which one is actually in flight.
50enum WriteState<S> {
51 /// No write in progress.
52 Idle,
53 /// A boxed future that owns the websocket and will complete the
54 /// operation, returning the websocket.
55 Writing(BoxFuture<'static, FutureResult<S, ()>>),
56}
57
58/// Describes which operation the future stored in `WriteState::Writing`
59/// actually represents.
60///
61/// `poll_write`, `poll_flush`, and `poll_shutdown` all funnel through the
62/// same `WriteState`, so it is possible for one of them to find an
63/// in-flight future that a *different* method started (e.g. `poll_flush`
64/// is called while a previous `poll_write` call is still pending). Tracking
65/// the operation kind alongside the future lets every caller correctly wait
66/// for whatever is in flight and then do its own job, instead of
67/// misreporting someone else's operation as its own (e.g. reporting a
68/// flush's completion as "0 bytes written").
69enum PendingOp {
70 /// A plain data write; carries the number of bytes to report once the
71 /// underlying frame has actually been written.
72 Write(usize),
73 /// A flush requested via `poll_flush`.
74 Flush,
75 /// A close frame written by `poll_shutdown`.
76 Close,
77}
78
79/// Stream payload type.
80///
81/// This enum specifies whether the `WebSocketStream` will send/receive Text or
82/// Binary application data. It is used to construct frames when writing and
83/// validated on frames read from the peer.
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum PayloadType {
86 /// Binary frames.
87 Binary,
88 /// UTF-8 Text frames.
89 Text,
90}
91
92impl From<PayloadType> for OpCode {
93 fn from(value: PayloadType) -> Self {
94 match value {
95 PayloadType::Binary => OpCode::Binary,
96 PayloadType::Text => OpCode::Text,
97 }
98 }
99}
100
101/// Map a `WebSocketError` into an `io::Error` for compatibility with the
102/// `AsyncRead`/`AsyncWrite` trait surfaces.
103fn make_io_err(e: WebSocketError) -> io::Error {
104 io::Error::other(format!("Websocket error: {}", e))
105}
106
107/// Helper: create a boxed future that owns the websocket and reads a frame.
108///
109/// The returned future will call `websocket.read_frame().await`, copy the
110/// payload into an owned `Bytes`, and return `(websocket, PayloadFrame)` on
111/// success or `WebSocketError` on failure.
112///
113/// This helper is private because it requires taking ownership of the
114/// `WebSocket` (which is stored as `Option` inside `WebSocketStream`) and
115/// boxing the resulting future so the `WebSocketStream` state machine can store
116/// it.
117fn read<S>(mut websocket: WebSocket<S>) -> BoxFuture<'static, FutureResult<S, PayloadFrame>>
118where
119 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
120{
121 async move {
122 // read_frame() returns Frame<'_> which borrows the websocket's buffers;
123 // we immediately copy the payload into an owned Bytes so the PayloadFrame
124 // can be returned with the websocket.
125 match websocket.read_frame().await {
126 Ok(frame) => {
127 let payload = match frame.payload {
128 Payload::BorrowedMut(buf) => Bytes::from(buf.to_vec()),
129 Payload::Borrowed(buf) => Bytes::from(buf.to_vec()),
130 Payload::Owned(vec) => Bytes::from(vec),
131 Payload::Bytes(bytes) => bytes.freeze(),
132 };
133
134 let owned = PayloadFrame {
135 opcode: frame.opcode,
136 payload,
137 };
138 Ok((websocket, owned))
139 }
140 Err(e) => Err(e),
141 }
142 }
143 .boxed()
144}
145
146/// Helper: create a boxed future that owns the websocket and writes the provided payload.
147///
148/// This helper constructs a single-frame message with the chosen `payload_type`
149/// (Text or Binary) and writes it with `websocket.write_frame(...)`. The future
150/// returns the websocket on success so ownership can be restored to the stream.
151fn write<S>(
152 mut websocket: WebSocket<S>,
153 payload: BytesMut,
154 payload_type: PayloadType,
155) -> BoxFuture<'static, FutureResult<S, ()>>
156where
157 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
158{
159 async move {
160 let frame = Frame::new(true, payload_type.into(), None, Payload::Bytes(payload));
161 match websocket.write_frame(frame).await {
162 Ok(()) => Ok((websocket, ())),
163 Err(e) => Err(e),
164 }
165 }
166 .boxed()
167}
168
169/// Helper: create a boxed future that owns the websocket and flushes it.
170///
171/// This issues a flush on the underlying `WebSocket` (which may flush any
172/// internal write buffers) and returns the websocket afterwards.
173fn flush<S>(mut websocket: WebSocket<S>) -> BoxFuture<'static, FutureResult<S, ()>>
174where
175 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
176{
177 async move {
178 match websocket.flush().await {
179 Ok(()) => Ok((websocket, ())),
180 Err(e) => Err(e),
181 }
182 }
183 .boxed()
184}
185
186/// Helper: create a boxed future that owns the websocket and sends a Close frame.
187///
188/// This writes a close frame and returns the websocket. Used by `poll_shutdown`.
189fn close<S>(mut websocket: WebSocket<S>) -> BoxFuture<'static, FutureResult<S, ()>>
190where
191 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
192{
193 async move {
194 let frame = Frame::close_raw(Vec::new().into());
195 match websocket.write_frame(frame).await {
196 Ok(()) => Ok((websocket, ())),
197 Err(e) => Err(e),
198 }
199 }
200 .boxed()
201}
202
203/// An `AsyncRead` / `AsyncWrite` adapter over a `fastwebsockets::WebSocket`.
204///
205/// `WebSocketStream<S>` wraps a `WebSocket<S>` and exposes a byte-stream view
206/// (implementing `tokio::io::AsyncRead` and `tokio::io::AsyncWrite`) so that
207/// websocket application payloads can be used with existing I/O and codec
208/// infrastructure such as `tokio_util::codec::Framed`.
209///
210/// ## Behavior
211///
212/// * Incoming WebSocket data frames (Text or Binary depending on the stream's
213/// `PayloadType`) are presented as a continuous byte stream. Each data frame's
214/// payload is returned in-order; if a read buffer provided by the caller is
215/// smaller than a frame payload, the remainder is buffered internally and
216/// served on subsequent reads.
217/// * Control frames (Ping/Pong) are handled by the underlying `WebSocket`
218/// (auto-pong) or ignored by this adapter. A `Close` frame marks EOF and
219/// subsequent reads return `Ok(())` with zero bytes (standard EOF semantics).
220/// * Writes produce single complete WebSocket data frames of the configured
221/// `PayloadType`. Each `poll_write` call sends one WebSocket data frame with
222/// the provided bytes as payload. The number of bytes reported as written is
223/// the length of `buf` supplied to `poll_write`.
224///
225/// ## Notes on threading and ownership
226///
227/// The adapter temporarily takes ownership of the inner `WebSocket` when it
228/// needs to perform an asynchronous read or write operation. To achieve this
229/// without requiring `WebSocket` itself to be `Sync`/`Send` across await points
230/// we spawn a boxed future that owns the websocket and returns it when the
231/// operation completes. This is implemented internally using `ReadState` and
232/// `WriteState`.
233///
234/// ## Example
235///
236/// ```rust
237/// use tokio::io::{AsyncReadExt, AsyncWriteExt};
238/// use tokio::net::TcpStream;
239/// use fastwebsockets::WebSocket;
240/// use fastwebsockets_stream::{WebSocketStream, PayloadType};
241///
242/// // Wrap the websocket and apply a line-based codec:
243/// async fn example<S>(_ws: WebSocket<S>)
244/// where S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static {
245/// // This example is illustrative: constructing a real `WebSocket` requires
246/// // an underlying transport (e.g. a `TcpStream`) and the fastwebsockets
247/// // connection/handshake. Assume `ws` is a valid WebSocket<TcpStream>.
248///
249/// let ws: WebSocket<S> = unimplemented!();
250/// let mut ws_stream = WebSocketStream::new(ws, PayloadType::Binary);
251///
252/// // Write bytes -> sends a Binary frame
253/// let _n = ws_stream.write(b"hello").await;
254///
255/// // Read bytes
256/// let mut buf = vec![0_u8; 1024];
257/// let _ = ws_stream.read(&mut buf).await;
258///
259/// // Shutdown (sends Close)
260/// let _ = ws_stream.shutdown().await;
261/// }
262/// ```
263///
264/// Another common usage is to use `tokio_util::codec::Framed` to apply a codec
265/// on top of `WebSocketStream` (for example a length-delimited or line-based
266/// codec). Example:
267///
268/// ```rust
269/// use tokio_util::codec::{Framed, LinesCodec};
270/// use fastwebsockets::WebSocket;
271/// use fastwebsockets_stream::{WebSocketStream, PayloadType};
272///
273/// // Wrap the websocket and apply a line-based codec:
274/// async fn example<S>(_ws: WebSocket<S>)
275/// where S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static {
276/// let ws: WebSocket<S> = unimplemented!();
277/// let stream = WebSocketStream::new(ws, PayloadType::Text);
278/// let mut framed = Framed::new(stream, LinesCodec::new());
279///
280/// // Now you can use framed.read() / framed.send() to work with String frames.
281/// }
282/// ```
283pub struct WebSocketStream<S> {
284 /// The inner websocket. Stored as `Option`
285 /// to allow temporarily taking ownership when starting an owned future
286 websocket: Option<WebSocket<S>>,
287
288 /// Buffer containing leftover bytes from the current
289 /// incoming message that didn't fit the last caller-provided read buffer
290 read_buf: BytesMut,
291
292 /// State machine for an in-progress read future that owns the websocket
293 read_state: ReadState<S>,
294
295 /// State machine for an in-progress write future that owns the websocket
296 write_state: WriteState<S>,
297
298 /// If `Some(op)`, an operation is in progress in `write_state` and `op`
299 /// records which one (write/flush/close) so its completion can be
300 /// reported correctly regardless of which method ends up driving it to
301 /// completion.
302 pending_op: Option<PendingOp>,
303
304 /// Expected and emitted payload type (Text or Binary). Received frames with
305 /// a different data opcode are treated as errors
306 payload_type: PayloadType,
307
308 /// Set to `true` after a Close frame has been observed.
309 /// When `closed` is true, subsequent reads return EOF
310 closed: bool,
311}
312
313impl<S> WebSocketStream<S>
314where
315 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
316{
317 /// Create a new `WebSocketStream` wrapping the provided `WebSocket`.
318 ///
319 /// This will enable automatic Pong replies and automatic Close handling on
320 /// the wrapped `WebSocket` and initialize internal buffers and state.
321 ///
322 /// `payload_type` selects whether this stream should read/write Text or
323 /// Binary data. If the peer sends data frames with an opcode that does not
324 /// match `payload_type`, reads will return an error.
325 pub fn new(mut websocket: WebSocket<S>, payload_type: PayloadType) -> Self {
326 // Set auto pong and close
327 websocket.set_auto_pong(true);
328 websocket.set_auto_close(true);
329
330 Self {
331 websocket: Some(websocket),
332 read_buf: BytesMut::with_capacity(8 * 1024),
333 read_state: ReadState::Idle,
334 write_state: WriteState::Idle,
335 pending_op: None,
336 payload_type,
337 closed: false,
338 }
339 }
340
341 /// Consume the adapter and attempt to return the inner `WebSocket`.
342 ///
343 /// This returns `Some(WebSocket<S>)` if the websocket currently resides in
344 /// the adapter. If there is an outstanding future that currently owns the
345 /// websocket (i.e. a read or write in progress) this method will return
346 /// `None` because the adapter cannot recover the websocket until that
347 /// future completes.
348 pub fn into_inner(mut self) -> Option<WebSocket<S>> {
349 // If there is an outstanding future that currently owns the websocket,
350 // we cannot recover it here. We only return the inner websocket if it
351 // currently resides in `self.ws`.
352 self.websocket.take()
353 }
354
355 /// Returns `true` if we've observed a Close frame from the peer and the
356 /// stream reached EOF.
357 pub fn is_closed(&self) -> bool {
358 self.closed
359 }
360
361 /// Drives whatever operation currently owns `write_state` (if any) to
362 /// completion, restoring the websocket and returning which [`PendingOp`]
363 /// just finished.
364 ///
365 /// Returns `Poll::Ready(Ok(None))` immediately if nothing is in flight.
366 /// Callers (`poll_write`, `poll_flush`, `poll_shutdown`) are responsible
367 /// for checking whether the returned `PendingOp` is the one they care
368 /// about; if it isn't (e.g. `poll_flush` drained a plain write that a
369 /// previous call started), they should loop and call this again so
370 /// their own operation actually gets started and driven.
371 fn poll_drive(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Option<PendingOp>>> {
372 match &mut self.write_state {
373 WriteState::Idle => Poll::Ready(Ok(None)),
374 WriteState::Writing(fut) => match fut.as_mut().poll(cx) {
375 Poll::Pending => Poll::Pending,
376 Poll::Ready(Ok((websocket, ()))) => {
377 self.websocket = Some(websocket);
378 self.write_state = WriteState::Idle;
379 Poll::Ready(Ok(self.pending_op.take()))
380 }
381 Poll::Ready(Err(e)) => {
382 self.write_state = WriteState::Idle;
383 self.pending_op = None;
384 Poll::Ready(Err(make_io_err(e)))
385 }
386 },
387 }
388 }
389}
390
391impl<S> AsyncRead for WebSocketStream<S>
392where
393 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
394{
395 fn poll_read(
396 mut self: Pin<&mut Self>,
397 cx: &mut Context<'_>,
398 buf: &mut ReadBuf<'_>,
399 ) -> Poll<io::Result<()>> {
400 // If there are buffered bytes from previous frame, satisfy the read.
401 if !self.read_buf.is_empty() {
402 let to_copy = std::cmp::min(self.read_buf.len(), buf.remaining());
403 buf.put_slice(&self.read_buf.split_to(to_copy));
404 return Poll::Ready(Ok(()));
405 }
406
407 // If we've previously observed Close/EOF, report EOF by returning Ok(())
408 if self.closed {
409 return Poll::Ready(Ok(()));
410 }
411
412 loop {
413 // Match current read future state
414 match &mut self.read_state {
415 ReadState::Idle => {
416 // Start a new read future by taking the websocket
417 let websocket = match self.websocket.take() {
418 Some(websocket) => websocket,
419 None => {
420 return Poll::Ready(Err(io::Error::other("Websocket not available")));
421 }
422 };
423 let future = read(websocket);
424 self.read_state = ReadState::Reading(future);
425 }
426 ReadState::Reading(fut) => {
427 // Poll the future. If Pending, return Pending. If Ready,
428 // reinstate websocket and handle frame.
429 match fut.as_mut().poll(cx) {
430 Poll::Pending => return Poll::Pending,
431 Poll::Ready(res) => {
432 // Transition back to Idle
433 self.read_state = ReadState::Idle;
434 match res {
435 Ok((websocket, frame)) => {
436 // Put websocket back
437 self.websocket = Some(websocket);
438
439 match frame.opcode {
440 OpCode::Binary | OpCode::Text | OpCode::Continuation => {
441 if matches!(frame.opcode, OpCode::Binary | OpCode::Text)
442 && frame.opcode != self.payload_type.into()
443 {
444 return Poll::Ready(Err(io::Error::other(
445 "The received data type is different \
446 from the stream data type",
447 )));
448 }
449
450 // Check frame payload
451 let payload = frame.payload;
452 if payload.is_empty() {
453 // Nothing to return; loop to read next frame
454 continue;
455 }
456
457 // If payload fits entirely into buf, copy and return.
458 return if payload.len() <= buf.remaining() {
459 buf.put_slice(&payload);
460 Poll::Ready(Ok(()))
461 } else {
462 // Copy a part and stash remainder
463 let take = buf.remaining();
464 buf.put_slice(&payload[..take]);
465 self.read_buf.extend_from_slice(&payload[take..]);
466 Poll::Ready(Ok(()))
467 };
468 }
469
470 OpCode::Close => {
471 // Mark EOF and return 0 bytes read (Ok(()))
472 self.closed = true;
473 return Poll::Ready(Ok(()));
474 }
475 _ => {
476 // Ignore control frames (Ping/Pong) and loop to
477 // read the next frame.
478 continue;
479 }
480 }
481 }
482 Err(e) => {
483 // restore websocket if possible? We don't have it on error.
484 // Map error to io::Error
485 return Poll::Ready(Err(make_io_err(e)));
486 }
487 }
488 }
489 }
490 }
491 }
492 }
493 }
494}
495
496impl<S> AsyncWrite for WebSocketStream<S>
497where
498 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
499{
500 fn poll_write(
501 mut self: Pin<&mut Self>,
502 cx: &mut Context<'_>,
503 buf: &[u8],
504 ) -> Poll<io::Result<usize>> {
505 loop {
506 match self.poll_drive(cx) {
507 Poll::Pending => return Poll::Pending,
508 Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
509 Poll::Ready(Ok(Some(PendingOp::Write(n)))) => return Poll::Ready(Ok(n)),
510 Poll::Ready(Ok(Some(_))) => {
511 // A flush/close that a previous call started just finished;
512 // write_state is Idle again, loop around to actually start
513 // the write this call was asked to perform.
514 continue;
515 }
516 Poll::Ready(Ok(None)) => {
517 // Nothing in flight: start a new write, taking the websocket
518 // and creating a future that writes it.
519 let websocket = match self.websocket.take() {
520 Some(websocket) => websocket,
521 None => {
522 return Poll::Ready(Err(io::Error::other("Websocket not available")));
523 }
524 };
525
526 // Copy buffer into an owned BytesMut so the future can own it.
527 let payload = BytesMut::from(buf);
528 let len = payload.len();
529 self.pending_op = Some(PendingOp::Write(len));
530 self.write_state =
531 WriteState::Writing(write(websocket, payload, self.payload_type));
532 // Loop back around to actually drive the future we just created.
533 }
534 }
535 }
536 }
537
538 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
539 loop {
540 match self.poll_drive(cx) {
541 Poll::Pending => return Poll::Pending,
542 Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
543 Poll::Ready(Ok(Some(PendingOp::Flush))) => return Poll::Ready(Ok(())),
544 Poll::Ready(Ok(Some(_))) => {
545 // A write/close that was already in flight just finished;
546 // we still owe the caller an actual flush, so continue on.
547 continue;
548 }
549 Poll::Ready(Ok(None)) => {
550 let websocket = match self.websocket.take() {
551 Some(websocket) => websocket,
552 None => {
553 return Poll::Ready(Err(io::Error::other("Websocket not available")));
554 }
555 };
556 self.pending_op = Some(PendingOp::Flush);
557 self.write_state = WriteState::Writing(flush(websocket));
558 }
559 }
560 }
561 }
562
563 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
564 // Implemented by sending a Close frame through the same state machine
565 // used for regular writes/flushes.
566 loop {
567 match self.poll_drive(cx) {
568 Poll::Pending => return Poll::Pending,
569 Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
570 Poll::Ready(Ok(Some(PendingOp::Close))) => return Poll::Ready(Ok(())),
571 Poll::Ready(Ok(Some(_))) => {
572 // A write/flush that was already in flight just finished;
573 // we still owe the caller an actual close, so continue on.
574 continue;
575 }
576 Poll::Ready(Ok(None)) => {
577 let websocket = match self.websocket.take() {
578 Some(websocket) => websocket,
579 None => {
580 return Poll::Ready(Err(io::Error::other("Websocket not available")));
581 }
582 };
583 self.pending_op = Some(PendingOp::Close);
584 self.write_state = WriteState::Writing(close(websocket));
585 }
586 }
587 }
588 }
589}
590
591impl<S> Debug for WebSocketStream<S> {
592 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593 // Helper to stringify read_state/write_state variants without requiring Debug on futures.
594 fn read_state_name<T>(s: &ReadState<T>) -> &'static str {
595 match s {
596 ReadState::Idle => "Idle",
597 ReadState::Reading(_) => "Reading",
598 }
599 }
600
601 fn write_state_name<T>(s: &WriteState<T>) -> &'static str {
602 match s {
603 WriteState::Idle => "Idle",
604 WriteState::Writing(_) => "Writing",
605 }
606 }
607
608 fn pending_op_name(op: &Option<PendingOp>) -> &'static str {
609 match op {
610 None => "None",
611 Some(PendingOp::Write(_)) => "Write",
612 Some(PendingOp::Flush) => "Flush",
613 Some(PendingOp::Close) => "Close",
614 }
615 }
616
617 f.debug_struct("WebSocketStream")
618 .field("read_buf_len", &self.read_buf.len())
619 .field("read_state", &read_state_name(&self.read_state))
620 .field("write_state", &write_state_name(&self.write_state))
621 .field("pending_op", &pending_op_name(&self.pending_op))
622 .field("closed", &self.closed)
623 .finish()
624 }
625}