WebSocket over HTTP/2

At the current time only servers support the handshake procedure defined in RFC8441.

While HTTP/2 inherently supports full-duplex communication, web browsers typically don't expose this functionality directly to developers and that is why WebSocket tunneling over HTTP/2 is important.

  1. Servers can efficiently handle multiple concurrent streams within a single TCP connection
  2. Client applications can continue using existing WebSocket APIs without modification

For this particular scenario the no-masking parameter defined in https://datatracker.ietf.org/doc/html/draft-damjanovic-websockets-nomasking-02 is also supported.

To use this functionality, it is necessary to activate the http2 and web-socket features.

Example

//! Low level HTTP/2 server that only accepts one WebSocket stream for demonstration purposes.
//!
//! It is worth noting that the optioned server in the `http2-server` example automatically
//! handles WebSocket connections.

extern crate tokio;
extern crate wtx;
extern crate wtx_instances;

use core::mem;
use tokio::net::TcpListener;
use wtx::{
  http::{is_web_socket_handshake, Headers, ReqResBuffer},
  http2::{Http2Buffer, Http2Params, Http2Tokio, WebSocketOverStream},
  misc::{simple_seed, Either, TokioRustlsAcceptor, Vector, Xorshift64},
  web_socket::{Frame, OpCode},
};

#[tokio::main]
async fn main() -> wtx::Result<()> {
  let listener = TcpListener::bind(&wtx_instances::host_from_args()).await?;
  let mut rng = Xorshift64::from(simple_seed());
  let (tcp_stream, _) = listener.accept().await?;
  let acceptor = TokioRustlsAcceptor::without_client_auth()
    .http2()
    .build_with_cert_chain_and_priv_key(wtx_instances::CERT, wtx_instances::KEY)?;
  let (frame_reader, mut http2) = Http2Tokio::accept(
    Http2Buffer::new(&mut rng),
    Http2Params::default()
      .set_enable_connect_protocol(true)
      .set_max_hpack_len((128 * 1024, 128 * 1024)),
    tokio::io::split(acceptor.accept(tcp_stream).await?),
  )
  .await?;
  tokio::spawn(frame_reader);
  let (mut stream, headers_opt) = match http2
    .stream(ReqResBuffer::empty(), |req, protocol| {
      let rslt = is_web_socket_handshake(&req.rrd.headers, req.method, protocol);
      rslt.then(|| mem::take(&mut req.rrd.headers))
    })
    .await?
  {
    Either::Left(_) => return Ok(()),
    Either::Right(elem) => elem,
  };
  let Some(_headers) = headers_opt else {
    return Ok(());
  };
  let mut buffer = Vector::new();
  let mut wos = WebSocketOverStream::new(&Headers::new(), false, rng, &mut stream).await?;
  loop {
    let mut frame = wos.read_frame(&mut buffer).await?;
    match (frame.op_code(), frame.text_payload()) {
      (_, Some(elem)) => println!("{elem}"),
      (OpCode::Close, _) => break,
      _ => {}
    }
    wos.write_frame(&mut Frame::new_fin(OpCode::Text, frame.payload_mut())).await?;
  }
  wos.close().await?;
  stream.common().clear(false).await?;
  Ok(())
}