Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

WebSocket

Implementation of RFC6455 and RFC7692. WebSocket is a communication protocol that enables full-duplex communication between a client (typically a web browser) and a server over a single TCP connection. Unlike traditional HTTP, which is request-response based, WebSocket allows real-time data exchange without the need for polling.

In-house benchmarks are available at https://c410-f3r.github.io/wtx-bench. If you are aware of other benchmark tools, please open a discussion in the GitHub project.

To use this functionality, it is necessary to activate the web-socket feature.

WebSocket Benchmark

Autobahn Reports

  1. fuzzingclient
  2. fuzzingserver

Compression

The “permessage-deflate” extension is the only supported compression format and is backed by the zlib-rs project that performs as well as zlib-ng.

At the current time WTX is the only crate that allows lock-free reader and writer parts with compression support.

To get the most performance possible, try compiling your program with RUSTFLAGS='-C target-cpu=native' to allow zlib-rs to use more efficient SIMD instructions.

No masking

Although not officially endorsed, the no-masking parameter described at https://datatracker.ietf.org/doc/html/draft-damjanovic-websockets-nomasking-02 is supported to increase performance. If such a thing is not desirable, please make sure to check the handshake parameters to avoid accidental scenarios.

To make everything work as intended both parties, client and server, need to implement this feature. For example, web browsers won’t stop masking frames.

Ping and Close frames

A received Ping frame automatically triggers an internal Pong response. Similarly, when a Close frame is received an automatic Close frame response is also sent.

//! WebSocket client that reads and writes frames in the same task.

extern crate tokio;
extern crate wtx;
extern crate wtx_examples;

use tokio::net::TcpStream;
use wtx::{
  collections::Vector,
  misc::Uri,
  rng::{ChaCha20, CryptoSeedableRng},
  tls::{TlsConfig, TlsConnector},
  web_socket::{OpCode, WebSocketConnector, WebSocketPayloadOrigin},
};
use wtx_examples::{LocalTlsMode, ROOT_CA, uri_from_args};

#[tokio::main]
async fn main() -> wtx::Result<()> {
  let uri = Uri::new(uri_from_args());
  let stream = TcpStream::connect(uri.hostname_with_implied_port()).await?;
  let mut ws = WebSocketConnector::default()
    .connect(
      TlsConnector::new(
        TlsConfig::from_trust_anchors_pem(LocalTlsMode::default(), [ROOT_CA])?,
        ChaCha20::from_getrandom()?,
        stream,
      ),
      &uri.to_ref(),
    )
    .await?;
  let mut buffer = Vector::new();
  loop {
    let frame = ws.read_frame(&mut buffer, WebSocketPayloadOrigin::Adaptive).await?;
    match (frame.op_code(), frame.text_payload()) {
      // `read_frame` internally already sent a Close response
      (OpCode::Close, _) => {
        break;
      }
      // `read_frame` internally already sent a Pong response
      (OpCode::Ping, _) => {}
      // For any other type, `read_frame` doesn't automatically send frames
      (_, text) => {
        if let Some(elem) = text {
          println!("Received text frame: {elem}")
        }
      }
    }
  }
  Ok(())
}

The same automatic behavior does not happen with concurrent instances because there are multiple ways to synchronize resources. In other words, you are responsible for managing replies.

//! WebSocket client that reads and writes frames in different tasks.
//!
//! Special frames aren't automatically handled by the system in concurrent scenarios because there are
//! multiple ways to synchronize resources. In this example, special frames are managed using a
//! mutex but you can utilize any other method.
//!
//! See `tls-client-concurrent` to see an example using a channel.

extern crate tokio;
extern crate wtx;
extern crate wtx_examples;

use tokio::net::TcpStream;
use wtx::{
  collections::Vector,
  misc::Uri,
  rng::{ChaCha20, CryptoSeedableRng as _},
  sync::{Arc, AsyncMutex},
  tls::{TlsConfig, TlsConnector},
  web_socket::{Frame, OpCode, WebSocketConnector, WebSocketPayloadOrigin},
};
use wtx_examples::{LocalTlsMode, ROOT_CA, uri_from_args};

#[tokio::main]
async fn main() -> wtx::Result<()> {
  let uri = Uri::new(uri_from_args());
  let stream = TcpStream::connect(uri.hostname_with_implied_port()).await?;
  let ws = WebSocketConnector::default()
    .connect(
      TlsConnector::new(
        TlsConfig::from_trust_anchors_pem(LocalTlsMode::default(), [ROOT_CA])?,
        ChaCha20::from_getrandom()?,
        stream,
      ),
      &uri.to_ref(),
    )
    .await?;
  let (stream_bridge, mut stream_reader, stream_writer) = ws.into_split()?;
  let stream_writer_bridge = Arc::new(AsyncMutex::new(stream_writer));
  let stream_writer_writer = stream_writer_bridge.clone();

  let bridge_fut = async {
    while let Some(data) = stream_bridge.listen().await {
      stream_writer_bridge.lock().await.manage_brige_data(data).await?;
    }
    wtx::Result::Ok(())
  };

  let reader_fut = async {
    let mut buffer = Vector::new();
    loop {
      let frame = stream_reader.read_frame(&mut buffer, WebSocketPayloadOrigin::Adaptive).await?;
      match (frame.op_code(), frame.text_payload()) {
        // A special version of this frame has already been sent to the bridge
        (OpCode::Close, _) => break,
        // A `Pong` frame with the same content has already been sent to the bridge
        (OpCode::Ping, _) => {}
        (_, text) => {
          if let Some(elem) = text {
            println!("Received text frame: {elem}")
          }
        }
      }
    }
    wtx::Result::Ok(())
  };

  let writer_fut = async {
    stream_writer_writer
      .lock()
      .await
      .write_frame(&mut Frame::new_fin(OpCode::Close, *b"Bye")?)
      .await?;
    wtx::Result::Ok(())
  };

  let (bridge_rslt, reader_rslt, writer_rslt) = tokio::join!(bridge_fut, reader_fut, writer_fut);
  bridge_rslt?;
  reader_rslt?;
  writer_rslt?;
  Ok(())
}

Alternative replying methods can be found at web-socket in the wtx-examples crate.

Server Example

//! Serves requests using low-level WebSockets resources alongside self-made certificates.

extern crate tokio;
extern crate wtx;
extern crate wtx_examples;

use tokio::net::TcpListener;
use wtx::{
  collections::Vector,
  rng::{ChaCha20, CryptoSeedableRng},
  tls::{TlsAcceptor, TlsConfig},
  web_socket::{OpCode, WebSocketAcceptor, WebSocketPayloadOrigin},
};
use wtx_examples::{LocalTlsMode, PUBLIC_KEY, SECRET_KEY, host_from_args};

#[tokio::main]
async fn main() -> wtx::Result<()> {
  let listener = TcpListener::bind(&host_from_args()).await?;
  let mut rng = ChaCha20::from_getrandom()?;
  loop {
    let conn_rng = ChaCha20::from_crypto_rng(&mut rng)?;
    let (stream, _) = listener.accept().await?;
    let _jh = tokio::spawn(async move {
      let fut = async {
        let mut buffer = Vector::new();
        let mut ws = WebSocketAcceptor::default()
          .accept(TlsAcceptor::new(
            TlsConfig::from_keys_pem(LocalTlsMode::default(), PUBLIC_KEY, SECRET_KEY)?,
            conn_rng,
            stream,
          ))
          .await?;
        let (mut common, mut reader, mut writer) = ws.split_mut();
        loop {
          let origin = WebSocketPayloadOrigin::Adaptive;
          let mut frame = reader.read_frame(&mut buffer, &mut common, origin).await?;
          match frame.op_code() {
            OpCode::Binary | OpCode::Text => writer.write_frame(&mut common, &mut frame).await?,
            OpCode::Close => break,
            _ => {}
          }
        }
        wtx::Result::Ok(())
      };
      if let Err(err) = fut.await {
        eprintln!("Error: {err}");
      }
    });
  }
}