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.
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.
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(())
}