Skip to main content

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}