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

The websocket feature is on by default. A WebSocket uses the session’s TLS fingerprint, proxy rules, and cookie jar. Use wss://: any other scheme, ws:// included, fails with Kind::Request before Leyline picks a proxy or connects.

Connect, send, and receive

Session::websocket(url) returns a WebSocketBuilder. Call connect(), or await the builder. send takes a WsMessage: Text, Binary, Ping, Pong, or Close.

#![allow(unused)]
fn main() {
use leyline::WsMessage;

async fn run() -> leyline::Result<()> {
let session = leyline::Session::new();
let mut ws = session.websocket("wss://example.com/live").connect().await?;

ws.send(WsMessage::Text("ping".to_owned())).await?;
ws.send(WsMessage::Binary(vec![1, 2, 3])).await?;

if let Some(msg) = ws.recv().await? {
    println!("{msg:?}");
}
ws.close().await?;
Ok(())
}
}

When the peer closes the connection, recv first returns Some(WsMessage::Close(frame)), where frame is an Option<CloseFrame> with the close code and reason. The next recv returns Ok(None). close sends a close frame and shuts the connection down.

A send, recv, or close that fails on the transport or on a closed connection gives Kind::Io. A protocol violation, or a message over the WebSocketConfig limits, gives Kind::Body.

Set handshake options

Builder methodEffect
config(WebSocketConfig)Limits and transport for this connection
header(name, value), headers(pairs)Append handshake headers. A repeated name adds a value. An invalid name or value fails connect() with Kind::Request. A user-agent or origin header replaces the one Leyline sends
proxy(url)Overrides the session proxy

The handshake sends the cookies that the jar holds for the URL, matched as for an https:// request. A Cookie header from the session default headers or from header() replaces them. A Set-Cookie header in the handshake response does not update the jar.

A wss:// URL follows the proxy rules of an https:// URL, including environment discovery and NO_PROXY. See Proxies.

Shutdown and host limits

The handshake takes a host-limit slot for its origin, the same slot an https:// request to that host takes, and releases it when the handshake completes or fails. An open connection holds no slot. Session::shutdown() stops a handshake in flight and fails new ones with the shut-down error. It does not close an established connection.

HTTP/2 or HTTP/1.1

WebSocketConfig::prefer_http2 defaults to true: Leyline first tries RFC 8441 extended CONNECT on a pooled HTTP/2 connection. It falls back to the RFC 6455 HTTP/1.1 upgrade on a fresh connection in two cases, and logs the reason:

  • The TLS handshake does not select h2. Later WebSockets to that origin skip the HTTP/2 attempt.
  • The peer does not advertise SETTINGS_ENABLE_CONNECT_PROTOCOL.

Any other error is returned, not retried. prefer_http2(false) skips the HTTP/2 attempt. protocol() returns the subprotocol the origin selected.

#![allow(unused)]
fn main() {
use leyline::WebSocketConfig;

async fn run() -> leyline::Result<()> {
let session = leyline::Session::new();
let ws = session
    .websocket("wss://example.com/live")
    .config(
        WebSocketConfig::new()
            .max_message_size(1 << 20)
            .prefer_http2(false),
    )
    .connect()
    .await?;
println!("protocol={:?}", ws.protocol());
Ok(())
}
}

Split the connection

split() returns a WsSink with send and close, and a WsStream with recv, so one task can write while another reads.

#![allow(unused)]
fn main() {
use leyline::WsMessage;

async fn run() -> leyline::Result<()> {
let session = leyline::Session::new();
let ws = session.websocket("wss://example.com/live").connect().await?;
let (mut sink, mut stream) = ws.split();

let reader = tokio::spawn(async move {
    while let Ok(Some(msg)) = stream.recv().await {
        println!("{msg:?}");
    }
});

sink.send(WsMessage::Text("hello".to_owned())).await?;
sink.close().await?;
reader.await.expect("reader task panicked");
Ok(())
}
}

Next

Read HTTP/3.