#tokio-util #byte #performance #high #payload #tls

tokio-websockets

High performance, strict, tokio-util based WebSockets implementation

14 releases (7 breaking)

new 0.8.2 Apr 16, 2024
0.7.0 Feb 27, 2024
0.5.1 Dec 29, 2023
0.4.1 Nov 23, 2023
0.2.0 Dec 6, 2022

#9 in WebSocket

Download history 200/week @ 2023-12-23 571/week @ 2023-12-30 502/week @ 2024-01-06 609/week @ 2024-01-13 790/week @ 2024-01-20 373/week @ 2024-01-27 572/week @ 2024-02-03 610/week @ 2024-02-10 520/week @ 2024-02-17 1066/week @ 2024-02-24 678/week @ 2024-03-02 492/week @ 2024-03-09 435/week @ 2024-03-16 482/week @ 2024-03-23 694/week @ 2024-03-30 392/week @ 2024-04-06

2,084 downloads per month
Used in 2 crates

MIT license

140KB
2.5K SLoC

tokio-websockets

Crates.io GitHub Workflow Status (with event) Documentation

High performance, strict, tokio-util based WebSockets implementation.

Why use tokio-websockets?

  • Built with tokio-util, intended to be used with tokio from the ground up
  • Minimal dependencies: The base only requires:
    • tokio, tokio-util, bytes, futures-core, futures-sink
    • SHA1 backend, e.g. sha1_smol (see Feature flags)
  • Big selection of features to tailor dependencies to any project (see Feature flags)
  • SIMD support: AVX2, SSE2 or NEON for frame (un)masking and accelerated UTF-8 validation
  • Strict conformance with the WebSocket specification, passes the Autobahn test suite without relaxations by default
  • TLS support
  • Reusable TLS connectors
  • Uses widely known crates from the ecosystem for types, for example Uri from http in the client
  • Cheaply clonable messages due to Bytes as payload storage
  • Tuned for performance (see the benchmarks)

Feature flags

Feature flags in tokio-websockets are added to allow tailoring it to your needs.

  • simd will enable AVX2, SSE2 or NEON accelerated masking and UTF-8 validation. Additionally enabling the nightly feature when using a nightly compiler will also enable AVX512 accelerated masking
  • client enables a tiny client implementation
  • server enables a tiny server implementation

TLS is supported via any of the following feature flags:

The rustls-*-roots and rustls-platform-verifier features require a crypto provider for rustls. You can either enable the aws_lc_rs (optionally also FIPS-compliant via the fips feature) or ring features to use these crates as the providers and then use TlsConnector::new(), or bring your own with TlsConnector::new_rustls_with_crypto_provider().

One SHA1 implementation is required, usually provided by the TLS implementation:

  • ring or aws_lc_rs are used if the ring or aws_lc_rs features are enabled (recommended when rustls is used)
  • The openssl feature will use openssl, usually preferred on most Linux/BSD systems with native-tls
  • The sha1_smol feature can be used as a fallback if no TLS is needed

The client feature requires enabling one random number generator:

  • fastrand can be used as a PRNG
  • getrandom can be used as a cryptographically secure RNG
  • rand can be used as an alternative to fastrand and should be preferred if it is already in the dependency tree

Example

This is a simple WebSocket echo server without any proper error handling.

More examples can be found in the examples folder.

use futures_util::{SinkExt, StreamExt};
use http::Uri;
use tokio::net::TcpListener;
use tokio_websockets::{ClientBuilder, Error, Message, ServerBuilder};

#[tokio::main]
async fn main() -> Result<(), Error> {
  let listener = TcpListener::bind("127.0.0.1:3000").await?;

  tokio::spawn(async move {
    while let Ok((stream, _)) = listener.accept().await {
      let mut ws_stream = ServerBuilder::new()
        .accept(stream)
        .await?;

      tokio::spawn(async move {
        // Just an echo server, really
        while let Some(Ok(msg)) = ws_stream.next().await {
          if msg.is_text() || msg.is_binary() {
            ws_stream.send(msg).await?;
          }
        }

        Ok::<_, Error>(())
      });
    }

    Ok::<_, Error>(())
  });

  let uri = Uri::from_static("ws://127.0.0.1:3000");
  let (mut client, _) = ClientBuilder::from_uri(uri).connect().await?;

  client.send(Message::text("Hello world!")).await?;

  while let Some(Ok(msg)) = client.next().await {
    if let Some(text) = msg.as_text() {
      assert_eq!(text, "Hello world!");
      // We got one message, just stop now
      client.close().await?;
    }
  }

  Ok(())
}

MSRV

The current MSRV for all feature combinations is Rust 1.64.

Caveats / Limitations / ToDo

WebSocket compression is currently unsupported.

Dependencies

~3–31MB
~619K SLoC