v0.34.0_10

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-18 09:24:03 +02:00
1 parent 8c4cbc583f
commit dd2502dd11
13 files changed
+389 -113

No files matched your search

+108 -45
View File
@@ -28,6 +28,8 @@ pub struct Context {
pub original_url: String,
values: HeaderMap,
}
include!("forwarding/peer_formatting.rs");
impl Settings {
pub fn compile(&self) -> Result<Compiled> {
ensure!(
@@ -231,24 +233,58 @@ impl Compiled {
uri: &Uri,
include_original_url: bool,
) -> Result<Context> {
let peer_ip = canonical(peer.ip());
self.context_with_peer(
incoming, peer, local, tls, authority, uri, include_original_url,
None, false,
)
}
/// Only the host/path validation and formatting data from the same request
/// and accepted connection are reused. Dynamic trust and XFF are evaluated
/// on every request, including after an in-place configuration reload.
#[allow(clippy::too_many_arguments)]
pub(crate) fn context_with_peer(
&self,
incoming: &HeaderMap,
peer: SocketAddr,
local: SocketAddr,
tls: bool,
authority: &HeaderValue,
uri: &Uri,
include_original_url: bool,
cached: Option<&PeerFormatting>,
validated_host: bool,
) -> Result<Context> {
let cached = cached.filter(|cached| cached.peer == peer && cached.local == local);
let peer_ip = cached.map_or_else(|| canonical(peer.ip()), |cached| cached.peer_ip);
let trusted_peer = self.trusted(peer_ip);
if !trusted_peer {
let scheme = if tls { "https" } else { "http" };
let authority_text = authority.to_str()?;
crate::config::request_host(authority_text)?;
if !validated_host { crate::config::request_host(authority_text)?; }
let target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
let original_url = if include_original_url {
format!("{scheme}://{authority_text}{target}")
} else {
String::new()
};
let peer_text = peer_ip.to_string();
let peer_header = HeaderValue::from_str(&peer_text)?;
let peer_node = if peer_ip.is_ipv4() { None } else { Some(node(peer_ip)) };
let peer_text = cached.is_none().then(|| peer_ip.to_string());
let ipv6_node = (cached.is_none() && peer_ip.is_ipv6()).then(|| node(peer_ip));
let peer_header = if let Some(cached) = cached {
cached.peer_header.clone()
} else {
HeaderValue::from_str(peer_text.as_deref().unwrap_or(""))?
};
let by_text = cached.is_none().then(|| node(canonical(local.ip())));
let peer_node = cached.map_or_else(
|| if peer_ip.is_ipv4() { peer_text.as_deref().unwrap_or("") }
else { ipv6_node.as_deref().unwrap_or("") },
|cached| cached.peer_node.as_str(),
);
let by_node = cached.map_or_else(|| by_text.as_deref().unwrap_or(""),
|cached| cached.by_node.as_str());
let forwarded = format!(
"for={};by={};proto={scheme};host=\"{authority_text}\"",
peer_node.as_deref().unwrap_or(&peer_text), node(canonical(local.ip()))
"for={peer_node};by={by_node};proto={scheme};host=\"{authority_text}\""
);
let mut values = HeaderMap::with_capacity(7);
values.insert("forwarded", forwarded.parse()?);
@@ -256,7 +292,11 @@ impl Compiled {
values.insert("x-real-ip", peer_header);
values.insert("x-forwarded-host", authority.clone());
values.insert("x-forwarded-proto", HeaderValue::from_static(scheme));
values.insert("x-forwarded-port", local.port().to_string().parse()?);
values.insert("x-forwarded-port", if let Some(cached) = cached {
cached.port_header.clone()
} else {
local.port().to_string().parse()?
});
values.insert("x-forwarded-uri", target.parse()?);
return Ok(Context {
client_ip: peer_ip,
@@ -287,29 +327,39 @@ impl Compiled {
}
let authority_text = authority.to_str()?;
// Validate before inserting authority into Forwarded's quoted host field.
crate::config::request_host(authority_text)?;
if !validated_host { crate::config::request_host(authority_text)?; }
let original_target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
let original_url = if include_original_url {
format!("{scheme}://{authority_text}{original_target}")
} else {
String::new()
};
let mut forwarded: Vec<_> = ips.iter().map(|ip| format!("for={}", node(*ip))).collect();
let last = forwarded.last_mut().expect("actual peer is always present");
last.push_str(&format!(
";by={};proto={scheme};host=\"{authority_text}\"",
node(canonical(local.ip()))
));
let mut values = HeaderMap::new();
values.insert("forwarded", forwarded.join(", ").parse()?);
values.insert(
"x-forwarded-for",
ips.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
.parse()?,
);
use std::fmt::Write as _;
let mut forwarded = String::with_capacity(ips.len() * 52 + 80 + authority_text.len());
let mut xff = String::with_capacity(ips.len() * 44);
for (index, ip) in ips.iter().enumerate() {
if index > 0 { forwarded.push_str(", "); xff.push_str(", "); }
forwarded.push_str("for=");
if index + 1 == ips.len() {
if let Some(cached) = cached { forwarded.push_str(&cached.peer_node); }
else { forwarded.push_str(&node(*ip)); }
} else {
forwarded.push_str(&node(*ip));
}
if index + 1 == ips.len() {
if let Some(cached) = cached { xff.push_str(cached.peer_header.to_str()?); }
else { write!(&mut xff, "{ip}")?; }
} else {
write!(&mut xff, "{ip}")?;
}
}
forwarded.push_str(";by=");
if let Some(cached) = cached { forwarded.push_str(&cached.by_node); }
else { forwarded.push_str(&node(canonical(local.ip()))); }
write!(&mut forwarded, ";proto={scheme};host=\"{authority_text}\"")?;
let mut values = HeaderMap::with_capacity(7);
values.insert("forwarded", forwarded.parse()?);
values.insert("x-forwarded-for", xff.parse()?);
values.insert("x-real-ip", client_ip.to_string().parse()?);
values.insert("x-forwarded-host", authority.clone());
values.insert("x-forwarded-proto", HeaderValue::from_static(scheme));
@@ -321,7 +371,11 @@ impl Compiled {
} else {
local.port()
};
values.insert("x-forwarded-port", port.to_string().parse()?);
values.insert("x-forwarded-port", if cached.is_some() && !(trusted_peer && self.trust_proto) {
cached.expect("tested above").port_header.clone()
} else {
port.to_string().parse()?
});
values.insert("x-forwarded-uri", original_target.parse()?);
Ok(Context {
client_ip,
@@ -371,21 +425,35 @@ impl Context {
Ok(())
}
pub fn apply(&self, headers: &mut HeaderMap) {
let remove: Vec<_> = headers
.keys()
.filter(|name| {
name.as_str().starts_with("x-forwarded-")
|| matches!(
name.as_str(),
"forwarded" | "x-real-ip" | "x-original-url" | "x-original-method"
)
})
.cloned()
.collect();
for name in remove {
headers.remove(name);
self.apply_clean(headers, true);
}
fn apply_clean(&self, headers: &mut HeaderMap, include_client_ip: bool) {
// HeaderMap::insert replaces all values for a known canonical name.
// Remove unknown/spoofed forwarding names while retaining the valid ones.
// The normal request has no extra spoofed header names. Allocate
// a removal list only for requests which actually need sanitization.
let mut remove = None;
for name in headers.keys() {
let text = name.as_str();
if matches!(text, "x-original-url" | "x-original-method")
|| (text.starts_with("x-forwarded-")
&& !self.values.contains_key(text))
{
remove.get_or_insert_with(Vec::new).push(name.clone());
}
}
if let Some(remove) = remove {
for name in remove { headers.remove(name); }
}
if !include_client_ip {
for name in ["forwarded", "x-forwarded-for", "x-real-ip"] {
headers.remove(name);
}
}
for (name, value) in &self.values {
if !include_client_ip && matches!(name.as_str(), "forwarded" | "x-forwarded-for" | "x-real-ip") {
continue;
}
headers.insert(name.clone(), value.clone());
}
}
@@ -395,12 +463,7 @@ impl Context {
/// the visitor address so frameworks such as ProxyFix see the RustProxy socket peer.
/// Authentication verifiers use `apply` directly and still receive the real client IP.
pub fn apply_upstream(&self, headers: &mut HeaderMap, forward_client_ip: bool) {
self.apply(headers);
if !forward_client_ip {
headers.remove("forwarded");
headers.remove("x-forwarded-for");
headers.remove("x-real-ip");
}
self.apply_clean(headers, forward_client_ip);
}
}
#[cfg(test)]
+29
View File
@@ -0,0 +1,29 @@
/// Immutable formatting shared by all HTTP/1 requests on one accepted socket.
/// No routing or trust decision is cached: those follow the current snapshot.
#[derive(Debug)]
pub(crate) struct PeerFormatting {
peer: SocketAddr,
local: SocketAddr,
peer_ip: IpAddr,
peer_header: HeaderValue,
peer_node: String,
by_node: String,
port_header: HeaderValue,
}
impl PeerFormatting {
pub(crate) fn new(peer: SocketAddr, local: SocketAddr) -> Self {
let peer_ip = canonical(peer.ip());
let peer_text = peer_ip.to_string();
Self {
peer,
local,
peer_ip,
peer_header: HeaderValue::from_str(&peer_text).expect("IP text is a valid header"),
peer_node: if peer_ip.is_ipv4() { peer_text } else { node(peer_ip) },
by_node: node(canonical(local.ip())),
port_header: HeaderValue::from_str(&local.port().to_string())
.expect("TCP port is a valid header"),
}
}
fn peer_header(&self) -> &HeaderValue { &self.peer_header }
}
+99
View File
@@ -14,6 +14,105 @@ mod tests {
)
.unwrap()
}
#[test]
fn socket_cached_forwarding_matches_uncached_for_ipv4_ipv6_and_trusted_hops() {
for (peer, local, networks) in [
("192.0.2.51:32000", "127.0.0.1:8080", Vec::<String>::new()),
("[2001:db8::8]:32100", "[2001:db8::1]:8443", Vec::new()),
("10.2.3.4:4123", "10.0.0.1:8443", vec!["10.0.0.0/8".to_owned()]),
] {
let peer: SocketAddr = peer.parse().unwrap();
let local: SocketAddr = local.parse().unwrap();
let formatter = PeerFormatting::new(peer, local);
let compiled = Settings {
trusted_proxies: networks,
trust_forwarded_proto: true,
}.compile().unwrap();
let mut incoming = HeaderMap::new();
incoming.insert("x-forwarded-for", "198.51.100.8, 10.3.2.1".parse().unwrap());
incoming.insert("x-forwarded-proto", "https".parse().unwrap());
let authority = HeaderValue::from_static("app.example:8443");
let uri: Uri = "/some%20file?q=4".parse().unwrap();
let plain = compiled.context_with_original_url(
&incoming, peer, local, true, &authority, &uri, false
).unwrap();
let cached = compiled.context_with_peer(
&incoming, peer, local, true, &authority, &uri, false,
Some(&formatter), true,
).unwrap();
assert_eq!(cached.client_ip, plain.client_ip);
assert_eq!(cached.scheme, plain.scheme);
assert_eq!(cached.original_url, plain.original_url);
assert_eq!(cached.values, plain.values);
assert_eq!(formatter.peer_header().to_str().unwrap(),
crate::vhost::canonical_ip(peer.ip()).to_string());
for include_ip in [false, true] {
let mut a = incoming.clone();
let mut b = incoming.clone();
a.insert("x-forwarded-evil", "spoofed".parse().unwrap());
b.insert("x-forwarded-evil", "spoofed".parse().unwrap());
a.append("x-forwarded-for", "1.2.3.4".parse().unwrap());
b.append("x-forwarded-for", "1.2.3.4".parse().unwrap());
plain.apply_upstream(&mut a, include_ip);
cached.apply_upstream(&mut b, include_ip);
assert_eq!(a, b);
assert!(!b.contains_key("x-forwarded-evil"));
assert_eq!(b.contains_key("x-real-ip"), include_ip);
assert_eq!(b.contains_key("forwarded"), include_ip);
}
}
}
#[test]
fn canonical_forwarding_headers_replace_duplicate_untrusted_values() {
let compiled = Settings::default().compile().unwrap();
let peer: SocketAddr = "192.0.2.9:4000".parse().unwrap();
let local: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let value = compiled.context_with_peer(
&HeaderMap::new(), peer, local, false,
&HeaderValue::from_static("app.example"),
&"/".parse().unwrap(), false,
Some(&PeerFormatting::new(peer, local)), false,
).unwrap();
let mut headers = HeaderMap::new();
for (name, val) in [
("forwarded", "for=203.0.113.1"),
("forwarded", "for=203.0.113.2"),
("x-forwarded-for", "203.0.113.3"),
("x-forwarded-for", "203.0.113.4"),
("x-real-ip", "203.0.113.5"),
("x-forwarded-privilege", "admin"),
("x-original-method", "DELETE"),
] {
headers.append(name, val.parse().unwrap());
}
value.apply(&mut headers);
assert_eq!(headers.get_all("forwarded").iter().count(), 1);
assert_eq!(headers.get_all("x-forwarded-for").iter().count(), 1);
assert_eq!(headers["x-forwarded-for"], "192.0.2.9");
assert!(!headers.contains_key("x-forwarded-privilege"));
assert!(!headers.contains_key("x-original-method"));
}
#[test]
fn peer_cache_can_never_override_the_actual_socket() {
let compiled = Settings::default().compile().unwrap();
let different = PeerFormatting::new(
"192.0.2.1:9001".parse().unwrap(),
"192.0.2.2:8080".parse().unwrap(),
);
let actual: SocketAddr = "198.51.100.6:4000".parse().unwrap();
let local: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let authority = HeaderValue::from_static("app.example");
let uri: Uri = "/safe".parse().unwrap();
let value = compiled.context_with_peer(
&HeaderMap::new(), actual, local, false,
&authority, &uri, false, Some(&different), false,
).unwrap();
assert_eq!(value.client_ip, actual.ip());
assert_eq!(value.values["x-forwarded-for"], "198.51.100.6");
}
#[test]
fn untrusted_headers_are_rebuilt_not_appended() {
let mut headers = HeaderMap::new();
+28 -12
View File
@@ -157,27 +157,43 @@ fn encoded_origin_path(path: &str) -> Option<String> {
fn response_context(
snapshot: &crate::config::Snapshot,
request: &Request,
) -> (
Option<Arc<crate::config::CompiledFrontend>>,
Option<Arc<crate::config::CompiledRoute>>,
) {
) -> SelectedRouteContext {
let tls = request
.extensions()
.get::<Transport>()
.copied()
.unwrap_or_default()
.tls;
let context = (|| {
let selected = (|| {
let local = request.extensions().get::<LocalAddr>()?.0;
let authority = effective_host(request, tls).ok()?;
let host = request_host(authority.to_str().ok()?).ok()?;
let original_host = effective_host(request, tls).ok()?;
let host = request_host(original_host.to_str().ok()?).ok()?;
let frontend = snapshot.frontend(&host, local, tls).cloned();
let route = crate::acl::routing_path_view(request.uri().path())
.ok()
.and_then(|path| snapshot.select_on_arc(&host, path.as_ref(), local, tls).cloned());
Some((frontend, route))
let path = crate::acl::routing_path_view(request.uri().path()).ok();
let route = path.as_ref().and_then(|path| {
snapshot.select_on_arc(&host, path.as_ref(), local, tls).cloned()
});
// An ordinary path borrows the original URI in forward_request. Only
// a decoded path needs to be moved between middleware and forwarding.
let decoded_path = path.and_then(|path| match path {
std::borrow::Cow::Borrowed(_) => None,
std::borrow::Cow::Owned(path) => Some(path),
});
Some(SelectedRouteContext {
frontend,
route,
validated: Some(ValidatedRoutingIdentity {
original_host,
host,
decoded_path,
}),
})
})();
context.unwrap_or((None, None))
selected.unwrap_or(SelectedRouteContext {
frontend: None,
route: None,
validated: None,
})
}
/// Reserved administration-session cookies may cross the proxy boundary only
+19 -21
View File
@@ -27,20 +27,11 @@ async fn forward_request(
.copied()
.unwrap_or_default()
.tls;
let Ok(original_host) = effective_host(&request, tls) else {
return error(
StatusCode::BAD_REQUEST,
"RustProxy: invalid or conflicting request authority\n",
);
};
let Ok(host) = original_host
.to_str()
.ok()
.and_then(|v| request_host(v).ok())
.ok_or(())
else {
return error(StatusCode::BAD_REQUEST, "RustProxy: invalid hostname\n");
};
let (selected_context, original_host, host, decoded_path, host_already_validated) =
match take_routing_identity(&mut request, tls) {
Ok(identity) => identity,
Err(response) => return response,
};
let handshake = if request.method() == Method::CONNECT {
let protocol = match request.version() {
Version::HTTP_2 => websocket::h2_protocol(&request),
@@ -106,13 +97,17 @@ async fn forward_request(
// forwarding operation, even after the request URI/headers are rewritten.
// Encoded/slash-folded paths still allocate via the strict decoder.
let routing_uri = request.uri().clone();
let Ok(path) = crate::acl::routing_path_view(routing_uri.path()) else {
return error(
StatusCode::BAD_REQUEST,
"RustProxy: ambiguous request path\n",
);
let path = if let Some(path) = decoded_path.as_deref() {
std::borrow::Cow::Borrowed(path)
} else {
let Ok(path) = crate::acl::routing_path_view(routing_uri.path()) else {
return error(
StatusCode::BAD_REQUEST,
"RustProxy: ambiguous request path\n",
);
};
path
};
let selected_context = request.extensions().get::<SelectedRouteContext>().cloned();
let frontend = if let Some(selection) = &selected_context {
selection.frontend.as_ref()
} else {
@@ -193,7 +188,7 @@ async fn forward_request(
}
}
}
let mut context = match snapshot.forwarding.context_with_original_url(
let mut context = match snapshot.forwarding.context_with_peer(
request.headers(),
peer.0,
local.0,
@@ -201,6 +196,9 @@ async fn forward_request(
&original_host,
request.uri(),
frontend.is_some_and(|frontend| frontend.tinyauth.is_some()),
request.extensions().get::<Arc<crate::forwarding::PeerFormatting>>()
.map(Arc::as_ref),
host_already_validated,
) {
Ok(context) => context,
Err(_) => {
+43
View File
@@ -0,0 +1,43 @@
// Observation has already validated routing identity. Transfer it to the
// forward handler instead of repeating hostname parsing and percent decoding.
#[derive(Clone)]
struct ValidatedRoutingIdentity {
original_host: HeaderValue,
host: String,
decoded_path: Option<String>,
}
#[derive(Clone)]
struct SelectedRouteContext {
frontend: Option<Arc<crate::config::CompiledFrontend>>,
route: Option<Arc<crate::config::CompiledRoute>>,
validated: Option<ValidatedRoutingIdentity>,
}
/// Transfer middleware-validated identity without cloning strings. Internal
/// fallback dispatches still use the complete authority validation path.
fn take_routing_identity(
request: &mut Request,
tls: bool,
) -> Result<(Option<SelectedRouteContext>, HeaderValue, String, Option<String>, bool), Response> {
let mut selected_context = request.extensions_mut().remove::<SelectedRouteContext>();
let validated = selected_context.as_mut().and_then(|context| context.validated.take());
let host_already_validated = validated.is_some();
let (original_host, host, decoded_path) = match validated {
Some(validated) => (
validated.original_host,
validated.host,
validated.decoded_path,
),
None => {
let original_host = effective_host(request, tls).map_err(|_| error(
StatusCode::BAD_REQUEST,
"RustProxy: invalid or conflicting request authority\n",
))?;
let host = original_host.to_str().ok()
.and_then(|v| request_host(v).ok())
.ok_or_else(|| error(StatusCode::BAD_REQUEST,
"RustProxy: invalid hostname\n"))?;
(original_host, host, None)
}
};
Ok((selected_context, original_host, host, decoded_path, host_already_validated))
}
+1
View File
@@ -1,4 +1,5 @@
include!("types.rs");
include!("identity.rs");
include!("error_fallback.rs");
include!("cache_helpers.rs");
mod cache_plan;
+26
View File
@@ -11,6 +11,32 @@ mod authority_tests {
}
}
#[test]
fn observation_transfers_validated_host_and_decoded_path() {
let config = crate::config::demo_config("127.0.0.1:9000".parse().unwrap());
let snapshot = crate::config::compile(config, 1, &validation_policy()).unwrap();
for (raw, normalized) in [
("/assets/app.js?rev=5", None),
("/assets/%61pp.js?rev=5", Some("/assets/app.js")),
("/assets//app.js", Some("/assets/app.js")),
] {
let mut request = Request::builder().method(Method::GET).uri(raw)
.header(HOST, "localhost").body(Body::empty()).unwrap();
request.extensions_mut().insert(LocalAddr("127.0.0.1:8080".parse().unwrap()));
let context = response_context(&snapshot, &request);
let validated = context.validated.unwrap();
assert_eq!(validated.host, "localhost");
assert_eq!(validated.original_host, "localhost");
assert_eq!(validated.decoded_path.as_deref(), normalized);
}
let mut invalid = Request::builder().uri("/ok")
.header(HOST, "localhost").header(HOST, "another.example")
.body(Body::empty()).unwrap();
invalid.extensions_mut().insert(LocalAddr("127.0.0.1:8080".parse().unwrap()));
assert!(response_context(&snapshot, &invalid).validated.is_none());
assert!(crate::acl::routing_path_view("/encoded%2fslash").is_err());
}
#[test]
fn retry_is_limited_to_safe_methods() {
for method in [Method::GET, Method::HEAD, Method::OPTIONS] {
+1 -7
View File
@@ -21,13 +21,7 @@ use http::{
};
use http_body::Body as HttpBody;
use http_body_util::BodyExt;
/// A route resolved against the immutable snapshot in the observation layer.
/// The internal error-fallback dispatcher must resolve its target independently.
#[derive(Clone)]
struct SelectedRouteContext {
frontend: Option<Arc<crate::config::CompiledFrontend>>,
route: Option<Arc<crate::config::CompiledRoute>>,
}
/// Mark responses whose configured outbound policies were already applied.
#[derive(Clone)]
struct ResponseRulesApplied;
#[derive(Clone)]
+6 -7
View File
@@ -37,13 +37,12 @@ fn should_replace_error_page(response: &Response, minimal_welcome: bool) -> bool
async fn observe(State(state): State<Arc<App>>, mut request: Request, next: Next) -> Response {
let snapshot = state.config.load_full();
let (frontend_context, route_context) = response_context(&snapshot, &request);
// The same snapshot is inserted into the request. No second host/regex
// selection or expensive full CompiledRoute clone is necessary in forward().
request.extensions_mut().insert(SelectedRouteContext {
frontend: frontend_context.clone(),
route: route_context.clone(),
});
let selected = response_context(&snapshot, &request);
let frontend_context = selected.frontend.clone();
let route_context = selected.route.clone();
// The same validated request identity and snapshot are transferred to
// forward(); no second hostname parsing or encoded-path decoding.
request.extensions_mut().insert(selected);
let local = request.extensions().get::<LocalAddr>().map(|v| v.0);
let transport_tls = request
.extensions()
+16 -10
View File
@@ -39,25 +39,29 @@ async fn connection<I>(
}
let request_stop = stop.clone();
let service_frontend_state = frontend_state.clone();
let formatted_peer = Arc::new(crate::forwarding::PeerFormatting::new(peer, local));
let service = service_fn(move |mut request: Request<Incoming>| {
let router = router.clone();
let mut router = router.clone();
let request_stop = request_stop.clone();
let request_lease = lease.clone();
let upgrade_state = service_frontend_state.clone();
let formatted_peer = formatted_peer.clone();
#[cfg(target_os = "linux")]
let sendfile_slots = sendfile_slots.clone();
async move {
request.extensions_mut().insert(PeerAddr(peer));
request.extensions_mut().insert(LocalAddr(local));
request.extensions_mut().insert(transport);
request.extensions_mut().insert(request_lease.clone());
// Reuse immutable per-socket formatting and insert extensions in
// one pass. Configuration and trust policies are still per-request.
let extensions = request.extensions_mut();
extensions.insert(PeerAddr(peer));
extensions.insert(LocalAddr(local));
extensions.insert(transport);
extensions.insert(formatted_peer);
extensions.insert(request_lease.clone());
#[cfg(target_os = "linux")]
if let Some(slots) = sendfile_slots {
request.extensions_mut().insert(slots);
extensions.insert(slots);
}
request
.extensions_mut()
.insert(ServerShutdown(request_stop.clone()));
extensions.insert(ServerShutdown(request_stop.clone()));
if h2c_attempt(&request) {
if !h2c_upgrade {
@@ -175,7 +179,9 @@ async fn connection<I>(
);
}
router.oneshot(request.map(Body::new)).await
// Axum Router is always ready; avoid the per-request Oneshot
// readiness adapter while retaining the same middleware stack.
router.call(request.map(Body::new)).await
}
});
let mut builder = http1::Builder::new();
+1 -1
View File
@@ -30,7 +30,7 @@ use tokio::{
task::JoinSet,
};
use tokio_util::sync::CancellationToken;
use tower::ServiceExt;
use tower::{Service, ServiceExt};
const HTTP2_PRIOR_KNOWLEDGE_PREFACE: &[u8; 24] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
const H2_FRAME_HEADER_LEN: usize = 9;