Module h2::client

source ·
Expand description

Client implementation of the HTTP/2 protocol.

§Getting started

Running an HTTP/2 client requires the caller to establish the underlying connection as well as get the connection to a state that is ready to begin the HTTP/2 handshake. See here for more details.

This could be as basic as using Tokio’s TcpStream to connect to a remote host, but usually it means using either ALPN or HTTP/1.1 protocol upgrades.

Once a connection is obtained, it is passed to handshake, which will begin the HTTP/2 handshake. This returns a future that completes once the handshake process is performed and HTTP/2 streams may be initialized.

handshake uses default configuration values. There are a number of settings that can be changed by using Builder instead.

Once the handshake future completes, the caller is provided with a Connection instance and a SendRequest instance. The Connection instance is used to drive the connection (see Managing the connection). The SendRequest instance is used to initialize new streams (see Making requests).

§Making requests

Requests are made using the SendRequest handle provided by the handshake future. Once a request is submitted, an HTTP/2 stream is initialized and the request is sent to the server.

A request body and request trailers are sent using SendRequest and the server’s response is returned once the ResponseFuture future completes. Both the SendStream and ResponseFuture instances are returned by SendRequest::send_request and are tied to the HTTP/2 stream initialized by the sent request.

The SendRequest::poll_ready function returns Ready when a new HTTP/2 stream can be created, i.e. as long as the current number of active streams is below MAX_CONCURRENT_STREAMS. If a new stream cannot be created, the caller will be notified once an existing stream closes, freeing capacity for the caller. The caller should use SendRequest::poll_ready to check for capacity before sending a request to the server.

SendRequest enforces the MAX_CONCURRENT_STREAMS setting. The user must not send a request if poll_ready does not return Ready. Attempting to do so will result in an Error being returned.

§Managing the connection

The Connection instance is used to manage connection state. The caller is required to call Connection::poll in order to advance state. SendRequest::send_request and other functions have no effect unless Connection::poll is called.

The Connection instance should only be dropped once Connection::poll returns Ready. At this point, the underlying socket has been closed and no further work needs to be done.

The easiest way to ensure that the Connection instance gets polled is to submit the Connection instance to an executor. The executor will then manage polling the connection until the connection is complete. Alternatively, the caller can call poll manually.

§Example


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?;
    tokio::spawn(async move {
        connection.await.unwrap();
    });

    let mut h2 = h2.ready().await?;
    // Prepare the HTTP request to send to the server.
    let request = Request::builder()
                    .method(Method::GET)
                    .uri("https://www.example.com/")
                    .body(())
                    .unwrap();

    // Send the request. The second tuple item allows the caller
    // to stream a request body.
    let (response, _) = h2.send_request(request, true).unwrap();

    let (head, mut body) = response.await?.into_parts();

    println!("Received response: {:?}", head);

    // The `flow_control` handle allows the caller to manage
    // flow control.
    //
    // Whenever data is received, the caller is responsible for
    // releasing capacity back to the server once it has freed
    // the data from memory.
    let mut flow_control = body.flow_control().clone();

    while let Some(chunk) = body.data().await {
        let chunk = chunk?;
        println!("RX: {:?}", chunk);

        // Let the server send more data.
        let _ = flow_control.release_capacity(chunk.len());
    }

    Ok(())
}

Structs§

  • Builds client connections with custom configuration values.
  • Manages all state associated with an HTTP/2 client connection.
  • Peer 🔒
  • A pushed response and corresponding request headers
  • A stream of pushed responses and corresponding promised requests
  • A future of a pushed HTTP response.
  • Returns a SendRequest instance once it is ready to send at least one request.
  • A future of an HTTP response.
  • Initializes new HTTP/2 streams on a connection by sending a request.

Functions§