harrow-server-monoio 0.9.2

Experimental monoio-based HTTP/1.1 server for Harrow
Documentation

Monoio-based HTTP/1.1 and HTTP/2 server for Harrow.

This crate provides a high-performance HTTP server using io_uring. It supports HTTP/1.1 with keep-alive and chunked transfer encoding, and HTTP/2 with multiplexed streams.

Features

  • io_uring-based I/O: Zero-copy where possible, minimal syscalls
  • Cancellation Safety: Proper handling of io_uring operation cancellation
  • Buffer Pooling: Reusable buffers to reduce allocator pressure
  • HTTP/2 Support: Multiplexed streams with flow control

Architecture

┌─────────────────────────────────────────────┐
│              Server (lib.rs)                │
└─────────────────────┬───────────────────────┘
                      │ TcpStream
                      ▼
┌─────────────────────────────────────────────┐
│           Connection Handler                │
│            (connection.rs)                  │
└─────────────────────┬───────────────────────┘
                      │
          ┌───────────┴───────────┐
          ▼                       ▼
┌─────────────────┐   ┌─────────────────────┐
│   H1 Handler    │   │   H2 Handler        │
│    (h1.rs)      │   │    (h2.rs)          │
└─────────────────┘   └─────────────────────┘

Example

fn main() {
    let app = App::new().get("/hello", hello);

    // High-level thread-per-core bootstrap.
    harrow_server_monoio::run(app, "127.0.0.1:3000".parse().unwrap()).unwrap();
}

For advanced cases where you already own a monoio runtime, use the async serve / serve_with_shutdown / serve_with_config entrypoints instead.

Cancellation Safety

This crate uses io_uring for async I/O. Unlike epoll-based runtimes, io_uring submits actual kernel operations. Dropping a Rust future does NOT automatically cancel the in-flight kernel operation.

This can lead to use-after-free (UAF) vulnerabilities:

  1. A read operation is submitted with a user buffer
  2. The future is dropped (e.g., due to timeout)
  3. The kernel writes to the buffer after it's been freed/reused

Mitigation

All I/O operations with timeout paths use CancelableAsyncReadRent and explicitly cancel kernel operations before returning:

let canceller = Canceller::new();
let handle = canceller.handle();

monoio::select! {
    result = stream.cancelable_read(buf, handle) => result,
    _ = timeout => {
        canceller.cancel(); // Explicit kernel cancellation
        // Await the operation to reclaim buffer
        let (_, buf) = read_fut.await;
        release_buffer(buf);
        return Err(Timeout);
    }
}

See cancel.rs for the implementation details.