h2 Rust Documentation

repository·master·Indexed 23 days ago

https://github.com/hyperium/h2

A high-performance, Tokio-aware HTTP/2 client and server implementation for Rust. Designed as a low-level protocol engine that strictly implements the HTTP/2 specification, h2 serves as the core for higher-level libraries like Hyper. It focuses on performance and correctness, passing h2spec, but does not handle TCP connections, TLS, or HTTP 1.0 upgrades.

Tokens
11.3K
Snippets
12
Records
53
Agent score
81%

What's inside h2

  1. What is h2 and what are its limitations?

    master

    H2 is a Tokio-aware HTTP/2 client and server implementation for Rust that focuses on performance and correctness. It implements the full HTTP/2 specification and passes h2spec.

    Important: Scope of Responsibility h2 is strictly an implementation of the HTTP/2 protocol. It does not handle:

    • Managing TCP connections
    • HTTP 1.0 upgrades
    • TLS (Transport Layer Security)
    • Any feature not explicitly described by the HTTP/2 specification.

    For a complete HTTP stack including TCP and TLS, use hyper, which uses h2 as its HTTP/2 engine.

  2. Install the h2 crate

    master

    To use h2 in your Rust project, add it to your Cargo.toml dependencies. Note that h2 is a low-level HTTP/2 implementation and does not handle TCP connections or TLS; these must be managed by your application or a higher-level crate like hyper.

    [dependencies]
    h2 = "0.4"
  3. How StreamRef and OpaqueStreamRef differ

    master

    In h2, stream handles are split into two primary types based on their role and ownership model:

    1. StreamRef: A high-level handle used typically by the sender (or the owner of the stream) to drive the stream's lifecycle. It includes methods for sending headers, data, trailers, and managing flow control capacity.
    2. OpaqueStreamRef: A lightweight, reference-counted handle used primarily by the receiver (the client) to poll for incoming data, responses, or pushed streams. It is designed to be easily cloned and passed around while providing access to the stream's incoming buffer and state.
  4. How `Connection` and inbound streams work

    master

    A Connection instance represents an active HTTP/2 connection and is used to accept inbound streams. It implements futures::Stream, allowing you to iterate over incoming requests using .accept().await.

    When a stream is accepted, it returns a tuple: (Request<RecvStream>, SendResponse<B>).

    • Request<RecvStream>: Contains the HTTP request headers and a RecvStream to read the inbound data and trailers.
    • SendResponse<B>: Used to send the response, stream the response payload, send trailers, and initiate push promises.

    Important: You must actively drive the Connection state by calling either Connection::accept or Connection::poll_close. Simply operating on the individual stream handles (SendStream or RecvStream) will not advance the connection state.

  5. How the HTTP/2 client works: Connection and Request lifecycle

    master

    The h2 client operates using two primary components returned after a successful handshake:

    1. Connection: Manages the overall connection state and I/O. It must be polled (e.g., by spawning it onto an executor like Tokio) to drive the protocol forward. If Connection is not polled, no requests will be sent and no responses will be received.
    2. SendRequest: A handle used to initiate new HTTP/2 streams. It can be cloned to allow multiple tasks to send requests over the same connection.

    Lifecycle Summary:

    • Establish an underlying connection (e.g., TcpStream).
    • Perform a handshake using client::handshake or Builder::handshake.
    • Spawn the Connection object on an executor to drive the connection.
    • Use SendRequest to send requests and receive ResponseFuture and SendStream handles.
    use h2::client;
    use http::{Request, Method};
    use std::error::Error;
    use tokio::net::TcpStream;
    
    #[tokio::main]
    pub async fn main() -> Result<(), Box<dyn Error>> {
        // Establish TCP connection to the server.
        let tcp = TcpStream::connect("127.0.0.1:5928").await?;
        let (h2, connection) = client::handshake(tcp).await?;
        
        // The connection must be driven by an executor
        tokio::spawn(async move {
            connection.await.unwrap();
        });
    
        let mut h2 = h2.ready().await?;
        let request = Request::builder()
                        .method(Method::GET)
                        .uri("https://www.example.com/")
                        .body(())
                        .unwrap();
    
        // Send the request
        let (response, _) = h2.send_request(request, true).unwrap();
    
        let (head, mut body) = response.await?.into_parts();
        println!("Received response: {:?}", head);
    
        Ok(())
    }
  6. How flow control works in h2

    master

    Flow control is used to prevent an endpoint from sending unlimited data. h2 exposes both stream-level and connection-level flow control:

    • Stream-level: Each stream has an initial window size (the number of bytes an endpoint can send). This window can be increased by the peer sending a WINDOW_UPDATE frame.
    • Connection-level: A window that governs data sent across all streams on the connection.

    Managing Flow Control:

    • Inbound data: Use the FlowControl type to manage how much data you are willing to receive.
    • Outbound data: Use the SendStream type to manage how much data you are sending.
  7. Handle Server Push Promises

    master

    If server push is enabled, the client can receive PushPromise objects.

    To process them:

    1. Call response_future.push_promises() on the ResponseFuture returned by a request. This returns a PushPromises handle.
    2. Iterate over the PushPromises handle (which implements Stream) or use push_promise().await to get the next PushPromise.
    3. Each PushPromise contains the request (the promise itself) and a response (a PushedResponseFuture) which can be awaited to get the actual Response<RecvStream>.
  8. How the HTTP/2 handshake works

    master

    The h2 library does not manage the underlying transport (TCP or TLS). You must provide a connection that is already in a state ready for the HTTP/2 handshake. There are three common ways to prepare this connection:

    1. HTTP/1.1 Upgrade: Opening an HTTP/1.1 connection and performing an upgrade.
    2. TLS with ALPN: Opening a TLS connection and using Application-Layer Protocol Negotiation (ALPN) to negotiate HTTP/2.
    3. Prior Knowledge: Assuming the connection is immediately ready for HTTP/2.

    Once the connection is ready, you pass it to client::handshake or server::handshake. The library then performs the handshake, which includes sending the connection preface and SETTINGS frames.

  9. Manage HTTP/2 streams with Streams

    master

    The Streams struct is the primary interface for managing HTTP/2 connection and stream state. It handles stream concurrency, frame processing, and state transitions for both clients and servers.

    Key capabilities include:

    • Sending Requests: Use send_request to initiate a new stream with an HTTP request.
    • Receiving Streams: Use next_incoming to retrieve StreamRef objects for incoming streams.
    • Flow Control: Manage connection-level window sizes via set_target_connection_window_size.
    • Lifecycle Management: Handle connection-level errors, settings updates, and EOF signals.

    Note that Streams can be cloned; cloning increments an internal reference count to ensure the underlying connection state persists as long as any part of the application holds a reference.

  10. Use FramedWrite to encode and send frames

    master

    To send HTTP/2 frames using FramedWrite, follow this pattern:

    1. Check readiness: Call poll_ready to ensure the codec has enough capacity to accept a new frame. This may trigger a flush of existing buffered data.
    2. Buffer the frame: Call buffer(item) with a Frame<B> to encode the frame into the internal buffer.
    3. Flush: Call flush to write the buffered frames to the underlying AsyncWrite stream.

    Note: buffer will return Err(UserError::PayloadTooBig) if a Frame::Data payload exceeds the current max_frame_size.

  11. Initialize a FramedWrite codec

    master

    Use FramedWrite::new(inner) to wrap an AsyncWrite type (like a TCP stream) into an HTTP/2 frame encoder. The codec automatically determines a chain_threshold for payload chaining based on whether the underlying writer supports vectored I/O.

    By default, the internal write buffer is initialized with a capacity of 16KB (DEFAULT_BUFFER_CAPACITY).

  12. Perform an HTTP/2 Handshake

    master

    To establish an HTTP/2 connection, you must perform a handshake on an existing I/O stream (implementing AsyncRead and AsyncWrite).

    There are two ways to do this:

    1. Using h2::client::handshake(io): A convenience function that uses a Builder with default settings. Returns a Result<(SendRequest<Bytes>, Connection<T, Bytes>), Error>.
    2. Using Builder::handshake(io): Allows you to apply custom configurations via the Builder before initiating the handshake. This returns a future that resolves to the (SendRequest, Connection) tuple.

    Note: The handshake future resolves once the connection preface and initial settings are sent, but does not necessarily wait for the server's initial settings frame.

    // Basic usage with default settings
    # use tokio::io::{AsyncRead, AsyncWrite};
    # use h2::client;
    # use bytes::Bytes;
    # async fn doc<T: AsyncRead + AsyncWrite + Unpin>(my_io: T) -> Result<(), h2::Error> {
    let (send_request, connection) = client::handshake(my_io).await?;
    // Start polling `connection` and use `send_request` to send requests
    # Ok(())
    # }