Fix HTTP/2 support: use ALPN-based protocol detection and fallback to URI host
Two changes to properly support HTTP/2 clients: 1. server.rs: Detect ALPN protocol after TLS handshake and use hyper::server::conn::http2::Builder for H2 connections instead of the auto::Builder which failed to detect HTTP/2 over TLS. The auto::Builder's ReadVersion mechanism doesn't work reliably with tokio-rustls TlsStreams. For H1 connections, continue using auto::Builder with upgrade support. 2. handler.rs: Fallback to URI host when Host header is missing. In HTTP/2, the host is conveyed via :authority pseudo-header which hyper represents as the URI host, not a Host header.
This commit is contained in:
@@ -39,11 +39,14 @@ async fn proxy_handler(
|
||||
let host = req
|
||||
.headers()
|
||||
.get(axum::http::header::HOST)
|
||||
.and_then(|v| v.to_str().ok());
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.or_else(|| req.uri().host())
|
||||
.unwrap_or_default();
|
||||
|
||||
let host = match host {
|
||||
Some(h) => h,
|
||||
None => return ProxyError::MissingHost.into_response(),
|
||||
let host = if host.is_empty() {
|
||||
return ProxyError::MissingHost.into_response();
|
||||
} else {
|
||||
host
|
||||
};
|
||||
|
||||
let config = state.config.load();
|
||||
|
||||
@@ -6,6 +6,7 @@ use axum::extract::ConnectInfo;
|
||||
use axum::http::Request;
|
||||
use axum::response::Response;
|
||||
use axum::Router;
|
||||
use hyper::body::Incoming;
|
||||
use hyper_util::rt::TokioExecutor;
|
||||
use hyper_util::service::TowerToHyperService;
|
||||
use tokio::net::TcpListener;
|
||||
@@ -80,15 +81,30 @@ pub async fn serve_https_listener(
|
||||
}
|
||||
};
|
||||
|
||||
let alpn = tls_stream.get_ref().1.alpn_protocol();
|
||||
let is_h2 = alpn == Some(b"h2");
|
||||
|
||||
let svc = ConnectInfoService {
|
||||
inner: router.into_service::<hyper::body::Incoming>(),
|
||||
inner: router.into_service::<Incoming>(),
|
||||
remote_addr,
|
||||
};
|
||||
|
||||
let svc = TowerToHyperService::new(svc);
|
||||
|
||||
let io = hyper_util::rt::TokioIo::new(tls_stream);
|
||||
|
||||
if let Err(e) = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new())
|
||||
if is_h2 {
|
||||
let mut builder = hyper::server::conn::http2::Builder::new(TokioExecutor::new());
|
||||
if let Err(e) = builder
|
||||
.enable_connect_protocol()
|
||||
.serve_connection(io, svc)
|
||||
.await
|
||||
{
|
||||
error!(error = %e, "HTTPS/2 connection error");
|
||||
}
|
||||
} else {
|
||||
let mut builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new());
|
||||
builder.http2().enable_connect_protocol();
|
||||
if let Err(e) = builder
|
||||
.serve_connection_with_upgrades(io, svc)
|
||||
.await
|
||||
{
|
||||
@@ -99,6 +115,7 @@ pub async fn serve_https_listener(
|
||||
}
|
||||
error!(error = %e, "HTTPS connection error");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
_ = shutdown_rx.changed() => {
|
||||
@@ -135,11 +152,10 @@ struct ConnectInfoService<S> {
|
||||
remote_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl<S, B> Service<Request<B>> for ConnectInfoService<S>
|
||||
impl<S> Service<Request<Incoming>> for ConnectInfoService<S>
|
||||
where
|
||||
S: Service<Request<B>, Response = Response> + Clone + Send + 'static,
|
||||
S: Service<Request<Incoming>, Response = Response> + Clone + Send + 'static,
|
||||
S::Future: Send + 'static,
|
||||
B: Send + 'static,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
@@ -152,7 +168,7 @@ where
|
||||
self.inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: Request<B>) -> Self::Future {
|
||||
fn call(&mut self, mut req: Request<Incoming>) -> Self::Future {
|
||||
req.extensions_mut().insert(ConnectInfo(self.remote_addr));
|
||||
self.inner.call(req)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user