Skip to main content

rws_server/websocket/
connection.rs

1//! WebSocket endpoints in an ordinary handler: [`WebSocket::upgrade`]
2//! returns the `101 Switching Protocols` response, and once the server has
3//! sent it, the connection goes to the handler as a [`WebSocketConnection`]
4//! on a thread of its own (tokio's blocking pool in async builds, a new
5//! thread in the `http1` build). HTTP/1.1 only: an HTTP/2 or HTTP/3 request
6//! can't upgrade and gets `426 Upgrade Required`.
7//!
8//! How the handler reaches the server: the response carries an internal
9//! header with a random token, and the handler waits under that token in a
10//! process-wide table. The server removes the header, takes the handler and
11//! hands it the socket (with any bytes the client sent after the request).
12//! A response that never reaches a client (replaced by a middleware) leaves
13//! its handler behind for at most [`UPGRADE_TTL`]. An upstream can't use
14//! the header: tokens are random, and unknown ones do nothing.
15
16use std::collections::HashMap;
17use std::hash::{BuildHasher, Hasher};
18use std::io::{Read, Write};
19use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
20use std::sync::{Arc, Mutex, OnceLock};
21use std::time::{Duration, Instant};
22
23use super::{close_payload, encode_frame, read_raw_frame, Frame, RawFrame, WebSocket};
24use crate::core::New;
25use crate::header::Header;
26use crate::request::Request;
27use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
28
29/// The internal header carrying the token from handler to server.
30const UPGRADE_HEADER: &str = "X-Rws-WebSocket-Upgrade";
31/// How long a handler waits for its response to be sent.
32pub const UPGRADE_TTL: Duration = Duration::from_secs(60);
33/// The default limit on one message (all its fragments): 16 MiB.
34pub const MAX_MESSAGE: usize = 16 * 1024 * 1024;
35
36/// Runs on the upgraded connection's reader and writer.
37type Handler = Box<dyn FnOnce(Box<dyn Read + Send>, Box<dyn Write + Send>) + Send>;
38
39/// A complete message from the client: fragments joined, text checked to
40/// be UTF-8.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum Message {
43    Text(String),
44    Binary(Vec<u8>),
45}
46
47/// An upgraded connection. [`recv`](Self::recv) returns the client's
48/// messages, answering pings and the closing handshake itself; send with
49/// the methods here or a [`WebSocketSender`] from [`sender`](Self::sender),
50/// which another thread can use while this one waits in `recv`.
51pub struct WebSocketConnection {
52    reader: Box<dyn Read + Send>,
53    sender: WebSocketSender,
54    max_message: usize,
55    closed: bool,
56}
57
58/// Sends to one WebSocket client; cheap to clone, usable from any thread.
59#[derive(Clone)]
60pub struct WebSocketSender {
61    writer: Arc<Mutex<Box<dyn Write + Send>>>,
62    closed: Arc<AtomicBool>,
63}
64
65impl WebSocketSender {
66    fn send(&self, frame: Frame) -> Result<(), String> {
67        let closing = matches!(frame, Frame::Close(..));
68        if self.closed.load(Ordering::Acquire) {
69            return Err("the WebSocket is closed".to_string());
70        }
71        if closing {
72            self.closed.store(true, Ordering::Release);
73        }
74        let bytes = encode_frame(frame);
75        let mut writer = self.writer.lock().unwrap_or_else(|e| e.into_inner());
76        writer.write_all(&bytes).and_then(|_| writer.flush()).map_err(|e| format!("write error: {e}"))
77    }
78
79    /// Send a text message.
80    pub fn send_text(&self, text: &str) -> Result<(), String> {
81        self.send(Frame::Text(text.to_string()))
82    }
83
84    /// Send a binary message.
85    pub fn send_binary(&self, data: &[u8]) -> Result<(), String> {
86        self.send(Frame::Binary(data.to_vec()))
87    }
88
89    /// Send a ping (at most 125 bytes); the client's pong is consumed by
90    /// [`WebSocketConnection::recv`].
91    pub fn ping(&self, payload: &[u8]) -> Result<(), String> {
92        if payload.len() > 125 {
93            return Err("a ping payload is at most 125 bytes".to_string());
94        }
95        self.send(Frame::Ping(payload.to_vec()))
96    }
97
98    /// Start the closing handshake with `code` (1000 = normal) and `reason`;
99    /// nothing more can be sent after it.
100    pub fn close(&self, code: u16, reason: &str) -> Result<(), String> {
101        self.send(Frame::Close(Some(code), reason.to_string()))
102    }
103
104    /// Whether a close frame was sent (by either side's handshake).
105    pub fn is_closed(&self) -> bool {
106        self.closed.load(Ordering::Acquire)
107    }
108}
109
110/// A close code the client may send (RFC 6455 §7.4).
111fn valid_close_code(code: u16) -> bool {
112    matches!(code, 1000..=1003 | 1007..=1011 | 3000..=4999)
113}
114
115impl WebSocketConnection {
116    /// A connection over `reader` (client → server) and `writer`.
117    #[doc(hidden)]
118    pub fn new(reader: Box<dyn Read + Send>, writer: Box<dyn Write + Send>) -> Self {
119        WebSocketConnection {
120            reader,
121            sender: WebSocketSender { writer: Arc::new(Mutex::new(writer)), closed: Arc::new(AtomicBool::new(false)) },
122            max_message: MAX_MESSAGE,
123            closed: false,
124        }
125    }
126
127    /// Refuse messages over `bytes` (default [`MAX_MESSAGE`]) with close
128    /// code 1009.
129    pub fn max_message_size(mut self, bytes: usize) -> Self {
130        self.max_message = bytes;
131        self
132    }
133
134    /// A sender for this connection, for other threads.
135    pub fn sender(&self) -> WebSocketSender {
136        self.sender.clone()
137    }
138
139    /// Send a text message.
140    pub fn send_text(&self, text: &str) -> Result<(), String> {
141        self.sender.send_text(text)
142    }
143
144    /// Send a binary message.
145    pub fn send_binary(&self, data: &[u8]) -> Result<(), String> {
146        self.sender.send_binary(data)
147    }
148
149    /// Start the closing handshake; [`recv`](Self::recv) then returns
150    /// `Ok(None)` when the client answers.
151    pub fn close(&self, code: u16, reason: &str) -> Result<(), String> {
152        self.sender.close(code, reason)
153    }
154
155    /// Fail the connection: a close frame with `code`, then `Err(reason)`.
156    fn fail<T>(&mut self, code: u16, reason: String) -> Result<T, String> {
157        self.closed = true;
158        let _ = self.sender.close(code, "");
159        Err(reason)
160    }
161
162    /// The next message; `Ok(None)` once the connection closed cleanly
163    /// (the closing handshake done, whoever started it). Pings are
164    /// answered and pongs dropped on the way. A protocol error — an
165    /// unmasked frame, a bad fragment, invalid UTF-8, an oversized message
166    /// — closes the connection with the matching code and is returned as
167    /// `Err`, as is the client disappearing without a close frame.
168    pub fn recv(&mut self) -> Result<Option<Message>, String> {
169        if self.closed {
170            return Ok(None);
171        }
172        let mut message: Option<(u8, Vec<u8>)> = None;
173        loop {
174            let assembled = message.as_ref().map_or(0, |(_, data)| data.len());
175            let limit = self.max_message.saturating_sub(assembled) as u64;
176            let RawFrame { fin, rsv, opcode, masked, payload } = match read_raw_frame(&mut self.reader, limit) {
177                Ok(frame) => frame,
178                Err(e) if e.contains("exceeds") => return self.fail(1009, format!("message over {} bytes", self.max_message)),
179                Err(e) => {
180                    self.closed = true;
181                    return Err(e);
182                }
183            };
184            if rsv != 0 {
185                return self.fail(1002, "reserved bits set (no extension was negotiated)".to_string());
186            }
187            if !masked {
188                return self.fail(1002, "a client frame wasn't masked".to_string());
189            }
190            if opcode >= 0x8 && (!fin || payload.len() > 125) {
191                return self.fail(1002, "a control frame was fragmented or over 125 bytes".to_string());
192            }
193            match opcode {
194                0x8 => {
195                    let (code, _) = match close_payload(&payload) {
196                        Ok(close) => close,
197                        Err(e) => return self.fail(1007, e),
198                    };
199                    if payload.len() == 1 || code.is_some_and(|c| !valid_close_code(c)) {
200                        return self.fail(1002, format!("invalid close code {code:?}"));
201                    }
202                    self.closed = true;
203                    // Answer the client's close, unless this ends ours.
204                    if !self.sender.is_closed() {
205                        let _ = self.sender.close(code.unwrap_or(1000), "");
206                    }
207                    return Ok(None);
208                }
209                0x9 => {
210                    let _ = self.sender.send(Frame::Pong(payload));
211                }
212                0xA => {}
213                0x1 | 0x2 if message.is_some() => return self.fail(1002, "a new message began inside a fragmented one".to_string()),
214                0x1 | 0x2 => message = Some((opcode, payload)),
215                0x0 => match message.as_mut() {
216                    Some((_, data)) => data.extend_from_slice(&payload),
217                    None => return self.fail(1002, "a continuation frame with no message".to_string()),
218                },
219                other => return self.fail(1002, format!("unknown opcode 0x{other:X}")),
220            }
221            if fin && opcode < 0x8 {
222                let Some((kind, data)) = message.take() else { continue };
223                return match kind {
224                    0x1 => match String::from_utf8(data) {
225                        Ok(text) => Ok(Some(Message::Text(text))),
226                        Err(_) => self.fail(1007, "a text message isn't UTF-8".to_string()),
227                    },
228                    _ => Ok(Some(Message::Binary(data))),
229                };
230            }
231        }
232    }
233}
234
235// ── Upgrade ───────────────────────────────────────────────────────────────────
236
237/// A handler waiting for its connection.
238#[doc(hidden)]
239pub struct Upgrade(Handler);
240
241impl Upgrade {
242    /// Run the handler on `reader` / `writer` (blocks for the connection's
243    /// life).
244    #[doc(hidden)]
245    pub fn run(self, reader: Box<dyn Read + Send>, writer: Box<dyn Write + Send>) {
246        (self.0)(reader, writer)
247    }
248}
249
250fn waiting() -> &'static Mutex<HashMap<String, (Instant, Handler)>> {
251    static WAITING: OnceLock<Mutex<HashMap<String, (Instant, Handler)>>> = OnceLock::new();
252    WAITING.get_or_init(Default::default)
253}
254
255/// 128 random bits as hex: SipHash with the process's random keys over a
256/// counter and the time.
257fn token() -> String {
258    static KEYS: OnceLock<std::collections::hash_map::RandomState> = OnceLock::new();
259    static COUNTER: AtomicU64 = AtomicU64::new(0);
260    let keys = KEYS.get_or_init(Default::default);
261    let n = COUNTER.fetch_add(1, Ordering::Relaxed);
262    let nanos = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_nanos());
263    let half = |salt: u8| {
264        let mut hasher = keys.build_hasher();
265        hasher.write_u8(salt);
266        hasher.write_u64(n);
267        hasher.write_u128(nanos);
268        hasher.finish()
269    };
270    format!("{:016x}{:016x}", half(1), half(2))
271}
272
273impl WebSocket {
274    /// Answer a WebSocket upgrade request: returns the `101` response to
275    /// return from the handler, and runs `handler` with the connection once
276    /// it's sent. A request that isn't a valid upgrade (or comes over HTTP/2
277    /// or HTTP/3) gets `426 Upgrade Required` and `handler` never runs.
278    ///
279    /// ```rust,no_run
280    /// # extern crate rws_server as rust_web_server;
281    /// use rust_web_server::app::App;
282    /// use rust_web_server::websocket::{Message, WebSocket};
283    ///
284    /// let app = App::with_state(()).get("/echo", |request, _params, _conn, _state| {
285    ///     WebSocket::upgrade(request, |mut ws| {
286    ///         while let Ok(Some(message)) = ws.recv() {
287    ///             let _ = match message {
288    ///                 Message::Text(text) => ws.send_text(&text),
289    ///                 Message::Binary(data) => ws.send_binary(&data),
290    ///             };
291    ///         }
292    ///     })
293    /// });
294    /// ```
295    pub fn upgrade<F>(request: &Request, handler: F) -> Response
296    where
297        F: FnOnce(WebSocketConnection) + Send + 'static,
298    {
299        let version = request.header("Sec-WebSocket-Version").map(|h| h.value.trim().to_string()).unwrap_or_default();
300        let mut response = match WebSocket::handshake_response(request) {
301            Ok(response) if Self::is_upgrade_request(request) && version == "13" && request.method == "GET" => response,
302            _ => {
303                let mut refused = Response::new();
304                refused.status_code = *STATUS_CODE_REASON_PHRASE.n426_upgrade_required.status_code;
305                refused.reason_phrase = STATUS_CODE_REASON_PHRASE.n426_upgrade_required.reason_phrase.to_string();
306                refused.headers.push(Header { name: "Upgrade".into(), value: "websocket".into() });
307                refused.headers.push(Header { name: "Sec-WebSocket-Version".into(), value: "13".into() });
308                return refused;
309            }
310        };
311        Self::register(&mut response, Box::new(move |reader, writer| handler(WebSocketConnection::new(reader, writer))));
312        response
313    }
314
315    /// Put `handler` behind `response` (a `101`): the server gives it the
316    /// raw connection once the response is sent.
317    fn register(response: &mut Response, handler: Handler) {
318        let token = token();
319        let mut waiting = waiting().lock().unwrap_or_else(|e| e.into_inner());
320        let now = Instant::now();
321        waiting.retain(|_, (since, _)| now.duration_since(*since) < UPGRADE_TTL);
322        waiting.insert(token.clone(), (now, handler));
323        response.headers.push(Header { name: UPGRADE_HEADER.into(), value: token.into() });
324    }
325
326    /// Like [`WebSocket::upgrade`] for a proxy: `response` is the backend's
327    /// `101`, and `relay` gets the client connection's raw bytes.
328    #[doc(hidden)]
329    pub fn upgrade_raw<F>(mut response: Response, relay: F) -> Response
330    where
331        F: FnOnce(Box<dyn Read + Send>, Box<dyn Write + Send>) + Send + 'static,
332    {
333        Self::register(&mut response, Box::new(relay));
334        response
335    }
336}
337
338/// The handler waiting for `response`, if it's a `101` from
339/// [`WebSocket::upgrade`]; the internal header is removed either way.
340#[doc(hidden)]
341pub fn take_upgrade(response: &mut Response) -> Option<Upgrade> {
342    let at = response.headers.iter().position(|h| h.name.eq_ignore_ascii_case(UPGRADE_HEADER))?;
343    let token = response.headers.remove(at).value;
344    if response.status_code != 101 {
345        return None;
346    }
347    let (_, handler) = waiting().lock().unwrap_or_else(|e| e.into_inner()).remove(token.as_ref())?;
348    Some(Upgrade(handler))
349}
350
351/// The `101` response head as sent: the status line and the response's
352/// headers — no body, no `Content-Length` (RFC 9110 §8.6).
353#[doc(hidden)]
354pub fn upgrade_head(response: &Response) -> Vec<u8> {
355    let mut head = format!("HTTP/1.1 101 {}\r\n", response.reason_phrase);
356    for header in &response.headers {
357        if header.name.eq_ignore_ascii_case("content-length") || header.name.eq_ignore_ascii_case("transfer-encoding") {
358            continue;
359        }
360        head.push_str(&header.name);
361        head.push_str(": ");
362        head.push_str(&header.value);
363        head.push_str("\r\n");
364    }
365    head.push_str("\r\n");
366    head.into_bytes()
367}
368
369/// Blocking `Read`/`Write` over one half of an async stream, for a handler
370/// on the blocking pool.
371#[cfg(feature = "http2")]
372struct Blocking<T> {
373    inner: T,
374    handle: tokio::runtime::Handle,
375}
376
377#[cfg(feature = "http2")]
378impl<T: tokio::io::AsyncRead + Unpin> Read for Blocking<T> {
379    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
380        self.handle.block_on(tokio::io::AsyncReadExt::read(&mut self.inner, buf))
381    }
382}
383
384#[cfg(feature = "http2")]
385impl<T: tokio::io::AsyncWrite + Unpin> Write for Blocking<T> {
386    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
387        self.handle.block_on(tokio::io::AsyncWriteExt::write(&mut self.inner, buf))
388    }
389
390    fn flush(&mut self) -> std::io::Result<()> {
391        self.handle.block_on(tokio::io::AsyncWriteExt::flush(&mut self.inner))
392    }
393}
394
395/// Give an async connection (plain or TLS) to `upgrade` on the blocking
396/// pool, `pending` (bytes already read past the request) first.
397#[cfg(feature = "http2")]
398#[doc(hidden)]
399pub fn hand_over_async<S>(stream: S, pending: Vec<u8>, upgrade: Upgrade)
400where
401    S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static,
402{
403    let handle = tokio::runtime::Handle::current();
404    let (read, write) = tokio::io::split(stream);
405    let reader = std::io::Cursor::new(pending).chain(Blocking { inner: read, handle: handle.clone() });
406    let writer = Blocking { inner: write, handle };
407    tokio::task::spawn_blocking(move || upgrade.run(Box::new(reader), Box::new(writer)));
408}