No description
Find a file
damfle 0a550428de
Some checks failed
CI / Build (push) Successful in 23s
CI / Test (push) Successful in 24s
CI / Lint (push) Successful in 23s
CI / Create Tag (push) Failing after 6s
min: move estd
2026-02-11 11:37:58 +01:00
.forgejo/workflows ci: add ci 2026-02-09 09:57:20 +01:00
src min: lint 2026-02-10 17:59:26 +01:00
.gitignore init: initial commit 2026-02-07 15:35:45 +01:00
Cargo.toml min: move estd 2026-02-11 11:37:58 +01:00
LICENSE init: initial commit 2026-02-07 15:35:45 +01:00
README.md doc: clean README 2026-02-09 10:12:23 +01:00

estd-websockets

A WebSocket library built on estd and estd-tls.

Features

  • WebSocket Server: Handle multiple connections with async support
  • WebSocket Client: Connect to WebSocket servers
  • Frame Protocol: Full RFC 6455 compliant frame handling
  • Message Types: Text and binary messages
  • Keep-Alive: Automatic ping/pong support
  • TLS Support: Secure WebSocket connections (WSS) via estd-tls
  • No External Dependencies: Only estd, and estd-tls

Quick Start

WebSocket Server

use estd_websockets::{Server, Message};
use std::io::Cursor;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut server = Server::new("127.0.0.1:8080");
    
    server.on_connect("/ws", |stream, path| {
        // Handle WebSocket connection
        loop {
            match stream.read_message() {
                Ok(msg) => {
                    match msg {
                        Message::Text(text) => {
                            println!("Received: {}", text);
                            stream.send_text("Echo: ".to_string() + &text)?;
                        }
                        Message::Binary(data) => {
                            println!("Received binary data: {} bytes", data.len());
                            stream.send_binary(data)?;
                        }
                    }
                }
                Err(_) => break,
            }
        }
        Ok(())
    })?;

    // Listen for connections (integrate with estd-http server)
    Ok(())
}

WebSocket Client

use estd_websockets::{Client, Message};
use std::io::Cursor;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to server (typically after TCP/TLS connection)
    let socket = Cursor::new(Vec::new());
    let mut client = Client::new(socket, "ws://example.com:8080/ws");

    // Send a message
    client.send_text("Hello, WebSocket!")?;

    // Read a message
    match client.read_message()? {
        Message::Text(text) => println!("Server said: {}", text),
        Message::Binary(data) => println!("Server sent binary: {} bytes", data.len()),
    }

    // Close connection
    client.close()?;
    Ok(())
}

Module Structure

Core Modules

  • error - Error types and result for WebSocket operations
  • frame - WebSocket frame parsing and serialization (RFC 6455)
  • message - Message types (Text and Binary)
  • handshake - HTTP upgrade handshake negotiation
  • stream - WebSocket connection stream abstraction
  • server - WebSocket server implementation
  • client - WebSocket client implementation

Frame Opcodes

  • Continuation (0x0) - Continuation frame for fragmented messages
  • Text (0x1) - Text frame
  • Binary (0x2) - Binary frame
  • Close (0x8) - Connection close frame
  • Ping (0x9) - Ping control frame
  • Pong (0xA) - Pong control frame

Message Types

use estd_websockets::Message;

// Text message
let msg = Message::text("Hello");
assert!(msg.is_text());
assert_eq!(msg.as_text(), Some("Hello"));

// Binary message
let msg = Message::binary(vec![1, 2, 3]);
assert!(msg.is_binary());
assert_eq!(msg.as_binary(), Some([1u8, 2, 3].as_ref()));

Stream Operations

use estd_websockets::Stream;
use std::io::Cursor;

let socket = Cursor::new(Vec::new());
let mut stream = Stream::new(socket, false); // false = server

// Send messages
stream.send_text("Hello")?;
stream.send_binary(vec![1, 2, 3])?;

// Keep-alive
stream.ping(b"ping")?;
stream.pong(b"pong")?;

// Close
stream.close()?;

Handshake Process

The handshake follows RFC 6455:

  1. Client sends HTTP upgrade request with:

    • Upgrade: websocket
    • Connection: Upgrade
    • Sec-WebSocket-Key (24-byte base64 encoded)
    • Sec-WebSocket-Version: 13
  2. Server validates request and responds with:

    • HTTP/1.1 101 Switching Protocols
    • Upgrade: websocket
    • Connection: Upgrade
    • Sec-WebSocket-Accept (SHA-1 hash of key)
use estd_websockets::Handshake;
use std::collections::HashMap;

let mut headers = HashMap::new();
headers.insert("upgrade".to_string(), "websocket".to_string());
headers.insert("connection".to_string(), "Upgrade".to_string());
headers.insert("sec-websocket-key".to_string(), "dGhlIHNhbXBsZSBub25jZQ==".to_string());
headers.insert("sec-websocket-version".to_string(), "13".to_string());

// Parse request
let key = Handshake::parse_upgrade_request(&headers)?;

// Generate accept response
let accept_key = Handshake::generate_accept_key(&key)?;

Integration with estd-http

The library integrates seamlessly with estd-http for HTTP upgrade handling:

use estd_http::{Server, StatusCode, ResponseBuilder};
use estd_websockets::{Server as WsServer, Handshake};
use std::collections::HashMap;

let http_server = Server::new("127.0.0.1:8080");
let ws_server = WsServer::new("127.0.0.1:8080");

http_server.get("/ws", |req, _ctx| {
    // Extract headers from HTTP request
    let mut headers = HashMap::new();
    
    // Parse WebSocket upgrade
    match ws_server.handle_upgrade("/ws", &headers) {
        Ok(accept_key) => {
            // Send 101 Switching Protocols response
            ResponseBuilder::new()
                .status(StatusCode::SwitchingProtocols)
                .header("Upgrade", "websocket")
                .header("Connection", "Upgrade")
                .header("Sec-WebSocket-Accept", &accept_key)
                .build()
        }
        Err(_) => {
            ResponseBuilder::new()
                .status(StatusCode::BadRequest)
                .build()
        }
    }
})?;

Frame Masking

  • Client frames must be masked (XOR with 4-byte mask key)
  • Server frames must not be masked
  • Masking is automatic in the Frame struct

Async Support

The library works with estd::sync::go for async operations:

use estd::sync;
use estd_websockets::Client;

let handle = sync::go(|| {
    // Blocking WebSocket operations in background
    let mut client = Client::new(socket, "ws://example.com/ws");
    client.send_text("Hello")?;
    Ok::<(), Box<dyn std::error::Error>>(())
});

// Continue with other work
let result = handle.join();

TLS Support

Use with estd-tls for secure WebSocket connections (WSS):

use estd_tls::Connector;
use estd_websockets::Client;

// Create TLS connection
let connector = Connector::new();
let tls_stream = connector.connect("example.com:443")?;

// Wrap with WebSocket protocol
let mut client = Client::new(tls_stream, "wss://example.com/ws");

Build and Test

# Build the library
cargo build

# Run tests
cargo test

# Run examples
cargo run --example ws-server
cargo run --example ws-client

Architecture

Frame Serialization

Frames are serialized according to RFC 6455:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|(4bits)|A|     (7)     |             (0/16/64)          |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

Message Fragmentation

Messages can be split across multiple frames:

  1. First frame with opcode Text or Binary and FIN=0
  2. Middle frames with opcode Continuation and FIN=0
  3. Final frame with opcode Continuation and FIN=1

(Fragmentation support is tracked as TODO)

Performance Considerations

  • Zero-copy frame parsing where possible
  • Efficient base64 and SHA-1 implementations
  • No unnecessary allocations in hot paths
  • Proper socket buffering through underlying stream

License

ISC License - See LICENSE for details

Author

Damien FLETY

Dependencies