feat: enhance HTTP/2 support by ensuring Host header is set and adding multiplexed request tests
This commit is contained in:
@@ -244,7 +244,10 @@ impl HttpProxyService {
|
||||
.map(|h| {
|
||||
// Strip port from host header
|
||||
h.split(':').next().unwrap_or(h).to_string()
|
||||
});
|
||||
})
|
||||
// HTTP/2 uses :authority pseudo-header instead of Host;
|
||||
// hyper maps it to the URI authority component
|
||||
.or_else(|| req.uri().host().map(|h| h.to_string()));
|
||||
|
||||
let path = req.uri().path().to_string();
|
||||
let method = req.method().clone();
|
||||
@@ -397,11 +400,18 @@ impl HttpProxyService {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure Host header is set (HTTP/2 requests don't have Host; need it for h1 backends)
|
||||
if !upstream_headers.contains_key("host") {
|
||||
if let Some(ref h) = host {
|
||||
if let Ok(val) = hyper::header::HeaderValue::from_str(h) {
|
||||
upstream_headers.insert(hyper::header::HOST, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add standard reverse-proxy headers (X-Forwarded-*)
|
||||
{
|
||||
let original_host = parts.headers.get("host")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.unwrap_or("");
|
||||
let original_host = host.as_deref().unwrap_or("");
|
||||
let forwarded_proto = if route_match.route.action.tls.as_ref()
|
||||
.map(|t| matches!(t.mode,
|
||||
rustproxy_config::TlsMode::Terminate
|
||||
@@ -574,10 +584,11 @@ impl HttpProxyService {
|
||||
source_ip: &str,
|
||||
pool_key: &crate::connection_pool::PoolKey,
|
||||
) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> {
|
||||
// Always use HTTP/1.1 for h1 backend connections (h2 incoming requests have version HTTP/2.0)
|
||||
let mut upstream_req = Request::builder()
|
||||
.method(parts.method)
|
||||
.uri(upstream_path)
|
||||
.version(parts.version);
|
||||
.version(hyper::Version::HTTP_11);
|
||||
|
||||
if let Some(headers) = upstream_req.headers_mut() {
|
||||
*headers = upstream_headers;
|
||||
@@ -848,6 +859,7 @@ impl HttpProxyService {
|
||||
|
||||
// Copy all original headers (preserving the client's Host header).
|
||||
// Skip X-Forwarded-* since we set them ourselves below.
|
||||
let mut has_host_header = false;
|
||||
for (name, value) in parts.headers.iter() {
|
||||
let name_str = name.as_str();
|
||||
if name_str == "x-forwarded-for"
|
||||
@@ -856,13 +868,25 @@ impl HttpProxyService {
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if name_str == "host" {
|
||||
has_host_header = true;
|
||||
}
|
||||
raw_request.push_str(&format!("{}: {}\r\n", name, value.to_str().unwrap_or("")));
|
||||
}
|
||||
|
||||
// HTTP/2 requests don't have Host header; add one from URI authority for h1 backends
|
||||
let ws_host = parts.uri.host().map(|h| h.to_string());
|
||||
if !has_host_header {
|
||||
if let Some(ref h) = ws_host {
|
||||
raw_request.push_str(&format!("host: {}\r\n", h));
|
||||
}
|
||||
}
|
||||
|
||||
// Add standard reverse-proxy headers (X-Forwarded-*)
|
||||
{
|
||||
let original_host = parts.headers.get("host")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.or(ws_host.as_deref())
|
||||
.unwrap_or("");
|
||||
let forwarded_proto = if route.action.tls.as_ref()
|
||||
.map(|t| matches!(t.mode,
|
||||
|
||||
@@ -196,6 +196,7 @@ pub fn is_http(data: &[u8]) -> bool {
|
||||
b"PATC",
|
||||
b"OPTI",
|
||||
b"CONN",
|
||||
b"PRI ", // HTTP/2 connection preface
|
||||
];
|
||||
starts.iter().any(|s| data.starts_with(s))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user