use http::{Request, Response};
use http_body::Body;
use tokio_util::sync::CancellationToken;
use crate::h2::connection::{Connection, ConnectionOptions};
use crate::h2::date::DateCache;
use crate::h2::options::Http2Options;
use crate::{HttpProtocol, Incoming};
pub mod codec;
pub mod connection;
mod date;
pub mod error;
pub mod hpack;
pub mod options;
mod stream;
pub(crate) use stream::H2Body;
pub(crate) const HTTP2_INVALID_HEADERS: [http::header::HeaderName; 5] = [
http::header::HeaderName::from_static("keep-alive"),
http::header::HeaderName::from_static("proxy-connection"),
http::header::CONNECTION,
http::header::TRANSFER_ENCODING,
http::header::UPGRADE,
];
#[inline]
pub(super) fn sanitize_response<ResB>(
response: &mut Response<ResB>,
send_date_header: bool,
date_cache: &DateCache,
) where
ResB: Body<Data = bytes::Bytes>,
{
let response_headers = response.headers_mut();
if send_date_header {
if let Some(http_date) = date_cache.get_date_header_value() {
response_headers
.entry(http::header::DATE)
.or_insert(http_date);
}
}
for header in &HTTP2_INVALID_HEADERS {
if let http::header::Entry::Occupied(entry) = response_headers.entry(header) {
entry.remove();
}
}
if response_headers
.get(http::header::TE)
.is_some_and(|v| v != "trailers")
{
response_headers.remove(http::header::TE);
}
}
pub struct Http2<Io> {
io_to_handshake: Option<Io>,
options: Http2Options,
cancel_token: Option<CancellationToken>,
}
impl<Io> Http2<Io>
where
Io: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static,
{
#[inline]
pub fn new(io: Io, options: Http2Options) -> Self {
Self {
io_to_handshake: Some(io),
options,
cancel_token: None,
}
}
#[inline]
pub fn graceful_shutdown_token(mut self, token: CancellationToken) -> Self {
self.cancel_token = Some(token);
self
}
}
impl<Io> HttpProtocol for Http2<Io>
where
Io: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static,
{
#[inline]
async fn handle<F, Fut, ResB, ResBE, ResE>(self, request_fn: F) -> Result<(), std::io::Error>
where
F: Fn(Request<Incoming>) -> Fut + 'static,
Fut: std::future::Future<Output = Result<Response<ResB>, ResE>> + 'static,
ResB: http_body::Body<Data = bytes::Bytes, Error = ResBE> + Unpin + 'static,
ResE: std::error::Error + 'static,
ResBE: std::error::Error + 'static,
{
let preface_timeout = self.options.handshake_timeout;
let options = ConnectionOptions {
send_continue_response: self.options.send_continue_response,
send_date_header: self.options.send_date_header,
max_concurrent_streams: self.options.max_concurrent_streams,
initial_stream_window_size: self.options.initial_stream_window_size,
initial_connection_window_size: self.options.initial_connection_window_size,
max_frame_size: self.options.max_frame_size,
max_header_list_size: self.options.max_header_list_size,
enable_connect_protocol: self.options.enable_connect_protocol,
idle_timeout: self.options.idle_timeout,
};
let shared = std::sync::Arc::new(request_fn);
let connection = Connection::new(
self.io_to_handshake
.ok_or_else(|| std::io::Error::other("no io to handshake"))?,
preface_timeout,
);
let connection = if let Some(token) = self.cancel_token {
connection.with_shutdown(token)
} else {
connection
};
connection.handle(shared, options).await
}
}