HTTP2 client with hyper

Hi all,

I'm struggling to make sense how to create a HTTP2 connection to downstream server and make a request using Hyper.
The hyper::client::conn::http1::handshake and hyper::client::conn::http2::handshake are very simmilar, but the HTTP2 handlshake() needs an executor passed, along with the IO.

I didn't find any examples that would indicate how to do this with hyper, and I'm not sure if I want to go with the reqwest route as well.

This would be part of the web server to add support to proxy to the HTTP2 endpoints that I'd like to add.

Thank you for your help and advice!

Their 'single threaded' example hyper/examples/single_threaded.rs at master · hyperium/hyper · GitHub shows http2 / tokio integration in a single-threaded way. You may want to adapt it to use tokio's multi-threaded executor though.

Reqwest, or something else built on top of hyper, would be the "batteries-included" way to do this. If you want to be gluing things together manually with hyper you should make sure you understand tokio and the general concepts of rust's async io ecosystem.

2 Likes

That is brilliant, I was looking for a way, the LocalExec example there explains a lot.
Thank you!

1 Like

Here's a working example that might help someone in the future.

Updated to use hyper_util::rt::tokio::TokioExecutor


use core::panic;
use http_body_util::{combinators::BoxBody, BodyExt, Empty};
use hyper::body::Bytes;
use hyper::Request;
use hyper_util::rt::TokioIo;
use log::debug;
use std::{net::ToSocketAddrs, sync::Arc};
use tokio::net::TcpStream;
use tokio_rustls::{rustls, TlsConnector};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Set log level to TRACE to see detailed information
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .init();

    let domain = "www.google.com";
    let uri = format!("https://{}/", domain);
    let target_host = match format!("{}:443", domain).to_socket_addrs() {
        Ok(socket_ip) => socket_ip.into_iter().next().unwrap(),
        Err(e) => {
            panic!("DNS resolution error: {}", e);
        }
    };

    let mut root_cert_store = rustls::RootCertStore::empty();
    root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());

    let mut config = rustls::ClientConfig::builder()
        .with_root_certificates(root_cert_store)
        .with_no_client_auth();
    config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()];

    let connector = TlsConnector::from(Arc::new(config));
      let tcp_stream = TcpStream::connect(target_host).await?;
    let tls_domain = rustls_pki_types::ServerName::try_from(domain)
        .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid dnsname"))?
        .to_owned();

    let stream = connector.connect(tls_domain, tcp_stream).await?;
    let io = TokioIo::new(stream);
    let executor = hyper_util::rt::tokio::TokioExecutor::new();

    let (mut sender, conn) = hyper::client::conn::http2::handshake(executor, io).await?;

    tokio::task::spawn(async move {
        if let Err(e) = conn.await {
            println!("Error: {:?}", e);
        }
    });

    let upstream_request = Request::builder()
        .uri(uri)
        .header("user-agent", "hyper-client-http2")
        .version(hyper::Version::HTTP_2)
        .body(Empty::<Bytes>::new())?;

    debug!("Request: {:#?}", upstream_request);
    let res = sender.send_request(upstream_request).await?;

    debug!("Response: {:#?}", res);

    let body = res.collect().await?.to_bytes();

    println!("{}", String::from_utf8_lossy(&body));
    Ok(())
}

Why not just use hyper_util::rt::tokio::TokioExecutor?

1 Like

Valid point, hyper_util::rt::tokio::TokioExecutor would be a better solution.
Thank you!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.