You are reading the docs for rws 18.1 The latest release is 18.8. Read this page for the latest release.

WebSocket

Add WebSocket endpoints with RFC 6455 frame-level I/O built in — no third-party WebSocket crates required.

Overview

rust-web-server includes a complete WebSocket implementation in src/websocket/mod.rs. It covers upgrade detection, the RFC 6455 opening handshake (SHA-1 + base64 key derivation), and frame-level read/write for all standard opcodes.

Integration pattern

WebSocket connections require taking over the raw TCP stream after the 101 upgrade response. They are handled in a custom accept loop rather than through the normal Controller / Application pipeline (which has no access to the underlying stream).

use std::io::{Read, Write};
use std::net::TcpListener;
use rust_web_server::request::Request;
use rust_web_server::websocket::{WebSocket, Frame};

let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

for stream in listener.incoming() {
    let mut stream = stream.unwrap();

    // Read the HTTP upgrade request.
    let mut buf = vec![0u8; 4096];
    let n = stream.read(&mut buf).unwrap();
    let request = Request::parse(&buf[..n]).unwrap();

    if WebSocket::is_upgrade_request(&request) {
        // Send 101 Switching Protocols.
        let mut response = WebSocket::handshake_response(&request).unwrap();
        stream.write_all(&response.generate()).unwrap();

        // Enter the frame loop.
        loop {
            match WebSocket::read_frame(&mut stream) {
                Ok(Frame::Text(msg)) => {
                    WebSocket::write_frame(&mut stream, Frame::Text(msg)).unwrap();
                }
                Ok(Frame::Ping(data)) => {
                    WebSocket::write_frame(&mut stream, Frame::Pong(data)).unwrap();
                }
                Ok(Frame::Close(code, reason)) => {
                    WebSocket::send_close(&mut stream, code.unwrap_or(1000), &reason).ok();
                    break;
                }
                Ok(_) => {}
                Err(_) => break,
            }
        }
    }
}

Detecting upgrade requests

WebSocket::is_upgrade_request(request: &Request) -> bool

Returns true when the request has all three required headers:

  • Upgrade: websocket
  • Connection: Upgrade (or any value containing "upgrade")
  • Sec-WebSocket-Key: <base64>

Handshake response

WebSocket::handshake_response(request: &Request) -> Result<Response, String>

Builds an HTTP 101 Switching Protocols response with the correct Sec-WebSocket-Accept value. The accept value is computed as:

base64(SHA-1(client_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))

Both SHA-1 and base64 are implemented from scratch in src/websocket/mod.rs with no external dependencies.

If the request includes a Sec-WebSocket-Protocol header, the first listed sub-protocol is echoed back in the response.

Frame types

Frame is an enum covering all RFC 6455 opcodes:

Variant Opcode Description
Frame::Text(String) 0x1 A complete UTF-8 text message
Frame::Binary(Vec<u8>) 0x2 A binary message
Frame::Ping(Vec<u8>) 0x9 Ping — respond with a matching Pong
Frame::Pong(Vec<u8>) 0xA Pong
Frame::Close(Option<u16>, String) 0x8 Close with optional status code and reason
Frame::Continuation { fin, data } 0x0 Continuation fragment for fragmented messages

Reading frames

WebSocket::read_frame(stream: &mut impl Read) -> Result<Frame, String>

Reads one complete frame. Client-to-server masking (required by RFC 6455) is handled automatically — the payload is unmasked before being returned. Extended payload lengths (16-bit and 64-bit) are supported.

A frame whose declared payload is larger than WebSocket::MAX_FRAME_PAYLOAD (16 MiB) is an error, returned before any of the payload is read, and the payload is read as it arrives rather than allocated at the size the client claims (since 18.1.0; before, a frame claiming an enormous length made the process abort). For another limit:

WebSocket::read_frame_limited(stream: &mut impl Read, max_payload: u64) -> Result<Frame, String>

Writing frames

WebSocket::write_frame(stream: &mut impl Write, frame: Frame) -> Result<(), String>

Writes one frame. Server-to-client frames are sent unmasked per the RFC. Payload length encoding (7-bit, 16-bit, 64-bit) is chosen automatically.

Convenience methods

// Send a text message
WebSocket::send_text(&mut stream, "hello")?;

// Send a close frame
WebSocket::send_close(&mut stream, 1000, "normal closure")?;

// Reply to a Ping with a Pong
WebSocket::send_pong(&mut stream, ping_payload)?;

Complete echo server example

use std::net::TcpListener;
use std::io::Read;
use rust_web_server::request::Request;
use rust_web_server::websocket::{WebSocket, Frame};

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    println!("WebSocket echo server listening on ws://127.0.0.1:7878");

    for stream in listener.incoming() {
        std::thread::spawn(|| {
            let mut stream = stream.unwrap();
            let mut buf = vec![0u8; 4096];
            let n = stream.read(&mut buf).unwrap();

            let request = Request::parse(&buf[..n]).unwrap();
            if !WebSocket::is_upgrade_request(&request) { return; }

            let mut handshake = WebSocket::handshake_response(&request).unwrap();
            use std::io::Write;
            stream.write_all(&handshake.generate()).unwrap();

            loop {
                match WebSocket::read_frame(&mut stream) {
                    Ok(Frame::Text(msg)) => {
                        WebSocket::write_frame(&mut stream, Frame::Text(msg)).unwrap();
                    }
                    Ok(Frame::Binary(data)) => {
                        WebSocket::write_frame(&mut stream, Frame::Binary(data)).unwrap();
                    }
                    Ok(Frame::Ping(data)) => {
                        WebSocket::send_pong(&mut stream, data).unwrap();
                    }
                    Ok(Frame::Close(code, reason)) => {
                        WebSocket::send_close(&mut stream, code.unwrap_or(1000), &reason).ok();
                        break;
                    }
                    _ => break,
                }
            }
        });
    }
}
WebSocket proxy

src/ws_proxy/mod.rs provides WsProxy — a standalone WebSocket proxy that forwards upgrade requests and relays raw bytes bidirectionally between the client and a backend server.

Edit this page on GitHub