use std::net::SocketAddr;
use std::time::{Duration, Instant};
use super::http::{HttpResponse, bounded, remaining};
pub async fn h2_request(
addr: SocketAddr,
method: &str,
path: &str,
host: &str,
headers: &[(&str, &str)],
bound: Duration,
) -> HttpResponse {
let deadline = Instant::now() + bound;
let tcp = bounded(
tokio::net::TcpStream::connect(addr),
remaining(deadline),
"HTTP/2 connect",
)
.await
.expect("the HTTP/2 peer could not connect");
let (mut client, connection) = bounded(
h2::client::handshake(tcp),
remaining(deadline),
"HTTP/2 handshake",
)
.await
.expect("the HTTP/2 handshake did not complete");
let driver = tokio::spawn(connection);
let request = headers
.iter()
.fold(
::http::Request::builder()
.method(method)
.uri(format!("http://{host}{path}")),
|builder, (name, value)| builder.header(*name, *value),
)
.body(())
.expect("the HTTP/2 request head is representable");
let (response, _) = client
.send_request(request, true)
.expect("the HTTP/2 stream could not be opened");
let response = bounded(response, remaining(deadline), "HTTP/2 response head")
.await
.expect("no HTTP/2 response head");
let status = response.status().as_u16();
let headers = response
.headers()
.iter()
.map(|(name, value)| {
(
Box::from(name.as_str()),
Box::from(String::from_utf8_lossy(value.as_bytes()).as_ref()),
)
})
.collect();
let body = drain_h2_body(
response.into_body(),
"HTTP/2 response body frame",
remaining(deadline),
)
.await;
drop(client);
join_driver(driver).await;
HttpResponse::from_parts(status, headers, body)
}
pub async fn drain_h2_body(
mut body: h2::RecvStream,
operation: &str,
bound: Duration,
) -> Box<[u8]> {
let deadline = Instant::now() + bound;
let mut bytes = Vec::new();
while let Some(chunk) = bounded(body.data(), remaining(deadline), operation).await {
let chunk = chunk.expect("an HTTP/2 body frame failed");
body.flow_control()
.release_capacity(chunk.len())
.expect("the HTTP/2 reader could not release its flow-control capacity");
bytes.extend_from_slice(&chunk);
}
bytes.into_boxed_slice()
}
async fn join_driver(driver: tokio::task::JoinHandle<Result<(), h2::Error>>) {
driver.abort();
match driver.await {
Ok(Ok(())) => {}
Err(error) if error.is_cancelled() => {}
Ok(Err(error)) => panic!("HTTP/2 client driver failed: {error}"),
Err(error) => panic!("HTTP/2 client driver join failed: {error}"),
}
}