0.34.6_repo_cleanup
This commit is contained in:
1 parent
ce1a90397c
commit
133d2f0f91
273 files changed
+5969
-6926
No files matched your search
@@ -34,8 +34,6 @@ where
|
||||
|
||||
async fn wait_for_h2_idle(state: Arc<H2ConnectionState>, idle: Duration) {
|
||||
loop {
|
||||
// Register for a notification before inspecting the state. This avoids
|
||||
// losing the final stream-drop notification between the load and await.
|
||||
let notified = state.notify.notified();
|
||||
tokio::pin!(notified);
|
||||
if state.active_streams.load(Ordering::Acquire) > 0 {
|
||||
@@ -166,8 +164,6 @@ enum TlsMode {
|
||||
Frontend(Arc<App>, bool),
|
||||
Fixed(Arc<rustls::ServerConfig>, Option<Router>),
|
||||
}
|
||||
/// Upgraded tunnels hold this lease after Hyper finishes its HTTP connection.
|
||||
/// This prevents WebSockets from escaping the public connection limit/counter.
|
||||
pub struct ConnectionLease {
|
||||
_permit: OwnedSemaphorePermit,
|
||||
metrics: Option<Arc<Metrics>>,
|
||||
@@ -253,9 +249,6 @@ pub async fn serve_frontend(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Start one compiled frontend listener with its immutable per-listener H2
|
||||
/// lifecycle policy. The public `serve_frontend` wrapper retains the legacy
|
||||
/// global fallback for callers that do not provide a compiled listener plan.
|
||||
pub async fn serve_frontend_with_http2(
|
||||
listener: TcpListener,
|
||||
router: Router,
|
||||
@@ -304,9 +297,6 @@ pub async fn serve_tls(
|
||||
)
|
||||
.await
|
||||
}
|
||||
/// Serve an HTTPS socket while turning accidental/plain HTTP requests on the
|
||||
/// same port into redirects. No administration handler is exposed through the
|
||||
/// plaintext router.
|
||||
pub async fn serve_tls_with_plain_redirect(
|
||||
listener: TcpListener,
|
||||
router: Router,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// HTTP/1 dispatcher and upgrade flow; split from TLS listener runtime.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn connection<I>(
|
||||
stream: I,
|
||||
@@ -49,8 +48,6 @@ async fn connection<I>(
|
||||
#[cfg(target_os = "linux")]
|
||||
let sendfile_slots = sendfile_slots.clone();
|
||||
async move {
|
||||
// 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));
|
||||
@@ -179,8 +176,6 @@ async fn connection<I>(
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
});
|
||||
|
||||
+3
-13
@@ -83,13 +83,11 @@ fn hpack_integer(out: &mut Vec<u8>, value: usize, prefix_bits: u8, first: u8) {
|
||||
}
|
||||
|
||||
fn hpack_string(out: &mut Vec<u8>, value: &[u8]) {
|
||||
// Huffman bit stays clear; the bootstrap encoder is deliberately simple.
|
||||
hpack_integer(out, value.len(), 7, 0);
|
||||
out.extend_from_slice(value);
|
||||
}
|
||||
|
||||
fn hpack_literal(out: &mut Vec<u8>, name: &str, value: &str) {
|
||||
// Literal Header Field without Indexing, new name (RFC 7541 section 6.2.2).
|
||||
out.push(0);
|
||||
hpack_string(out, name.as_bytes());
|
||||
hpack_string(out, value.as_bytes());
|
||||
@@ -203,22 +201,17 @@ fn h2_headers_frame<B>(request: &Request<B>) -> std::result::Result<Bytes, &'sta
|
||||
((length >> 16) & 0xff) as u8,
|
||||
((length >> 8) & 0xff) as u8,
|
||||
(length & 0xff) as u8,
|
||||
0x1, // HEADERS
|
||||
0x5, // END_STREAM | END_HEADERS
|
||||
0x1,
|
||||
0x5,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1, // stream 1 is the HTTP/1.1 upgrade request
|
||||
1,
|
||||
]);
|
||||
frame.extend_from_slice(&block);
|
||||
Ok(Bytes::from(frame))
|
||||
}
|
||||
|
||||
/// After a successful 101 response the client sends the normal HTTP/2 client
|
||||
/// connection preface. Hyper still needs to observe the upgrade request as
|
||||
/// stream 1, so this adapter injects one synthetic HEADERS frame immediately
|
||||
/// after the client's mandatory initial SETTINGS frame. All later streams are
|
||||
/// handled by Hyper's normal HTTP/2 state machine.
|
||||
struct H2cUpgradeIo<I> {
|
||||
inner: I,
|
||||
injected_headers: Bytes,
|
||||
@@ -344,9 +337,6 @@ impl<I: AsyncWrite + Unpin> AsyncWrite for H2cUpgradeIo<I> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared HTTP/2 activity state. Idle shutdown is allowed only when there are
|
||||
/// no active streams and no recent frame traffic. Lifetime shutdown ignores idle
|
||||
/// state but still uses the same GOAWAY -> drain -> TCP close sequence.
|
||||
struct H2ConnectionState {
|
||||
started: std::time::Instant,
|
||||
last_activity_ms: AtomicU64,
|
||||
|
||||
+1
-22
@@ -1,13 +1,3 @@
|
||||
//! Linux HTTP/1 kernel sendfile bridge.
|
||||
//!
|
||||
//! Hyper's Body uses Bytes, not file descriptors. This opt-in bridge conveys a
|
||||
//! uniquely tagged, exact-length placeholder through the ordinary metering
|
||||
//! pipeline; a TCP-only AsyncWrite adapter replaces those bytes with the
|
||||
//! original, already-validated file via Linux sendfile(2). An unmatched marker
|
||||
//! or short file is a hard I/O error, never silently returned as user content.
|
||||
//! Never register a placeholder for cached, compressed, transformed, TLS, H2,
|
||||
//! H3, ranged or non-GET responses.
|
||||
|
||||
use bytes::Bytes;
|
||||
use http_body::{Body as HttpBody, Frame, SizeHint};
|
||||
use std::{
|
||||
@@ -54,8 +44,6 @@ pub(crate) struct SendfileBody {
|
||||
web_metrics: Arc<crate::web_server::metrics::PhpMetrics>,
|
||||
}
|
||||
|
||||
// Web Server counters count emitted body frames before any downstream encoder.
|
||||
// Atomic reference is supplied by the Web Server that originally opened the file.
|
||||
impl SendfileSlots {
|
||||
pub fn create_body(
|
||||
&self,
|
||||
@@ -68,7 +56,7 @@ impl SendfileSlots {
|
||||
let mut registry = self.0.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if registry.slots.len() >= MAX_PENDING { return None; }
|
||||
let mut tag = [0u8; TAG_LEN];
|
||||
tag[0] = 0; // A valid HTTP/1 response header never contains NUL.
|
||||
tag[0] = 0;
|
||||
tag[1..].copy_from_slice(&rand::random::<[u8; TAG_LEN - 1]>());
|
||||
if registry.slots.iter().any(|slot| slot.tag == tag) { return None; }
|
||||
let mut data = vec![0u8; FRAME_LEN];
|
||||
@@ -83,8 +71,6 @@ impl SendfileSlots {
|
||||
|
||||
fn plan(&self, buf: &[u8]) -> WritePlan {
|
||||
let registry = self.0.lock().unwrap_or_else(|e| e.into_inner());
|
||||
// Critical for mixed reverse-proxy traffic: almost all socket writes
|
||||
// have no pending static file; do not inspect their payload at all.
|
||||
if registry.slots.is_empty() { return WritePlan::Plain(buf.len()); }
|
||||
if let Some(tag) = registry.active {
|
||||
if let Some(slot) = registry.slots.iter().find(|s| s.tag == tag) {
|
||||
@@ -92,9 +78,6 @@ impl SendfileSlots {
|
||||
}
|
||||
return WritePlan::Invalid;
|
||||
}
|
||||
// A marker is an opaque 32-byte random tag, searched only on a
|
||||
// connection that has a registered response. Headers preceding a
|
||||
// marker are written intact; each subsequent body byte is validated.
|
||||
for start in 0..buf.len() {
|
||||
if buf[start] != 0 { continue; }
|
||||
for slot in ®istry.slots {
|
||||
@@ -211,8 +194,6 @@ impl AsyncWrite for SendfileIo {
|
||||
}
|
||||
fn is_write_vectored(&self) -> bool { self.stream.is_write_vectored() }
|
||||
fn poll_write_vectored(mut self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>]) -> Poll<io::Result<usize>> {
|
||||
// Preserve zero-extra-syscall vectored I/O whenever no sendfile body
|
||||
// has been prepared on this connection.
|
||||
if self.slots.0.lock().unwrap_or_else(|e| e.into_inner()).slots.is_empty() {
|
||||
return Pin::new(&mut self.stream).poll_write_vectored(cx, bufs);
|
||||
}
|
||||
@@ -273,8 +254,6 @@ mod tests {
|
||||
file: Arc::new(file), length: expected.len() as u64,
|
||||
}, web.clone()).unwrap();
|
||||
let first = format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n", expected.len());
|
||||
// Combine header and first marker into one socket write: the
|
||||
// bridge must send the header before enabling kernel transfer.
|
||||
let mut body = body;
|
||||
let first_frame = http_body_util::BodyExt::frame(&mut body).await.unwrap().unwrap().into_data().unwrap();
|
||||
let mut joined = first.into_bytes();
|
||||
|
||||
@@ -79,8 +79,6 @@ async fn http2_connection<I>(
|
||||
_ = stop.cancelled() => break H2ShutdownCause::Process,
|
||||
_ = &mut lifetime => break H2ShutdownCause::Lifetime,
|
||||
_ = wait_for_h2_idle(activity.clone(), settings.idle_timeout()) => {
|
||||
// Re-check immediately before GOAWAY so activity racing the timer
|
||||
// cannot turn a busy connection into an idle shutdown.
|
||||
if activity.is_idle(settings.idle_timeout()) {
|
||||
break H2ShutdownCause::Idle;
|
||||
}
|
||||
@@ -88,8 +86,6 @@ async fn http2_connection<I>(
|
||||
}
|
||||
};
|
||||
|
||||
// Hyper's graceful shutdown sends GOAWAY and refuses newer streams while
|
||||
// continuing to poll streams that were already accepted.
|
||||
conn.as_mut().graceful_shutdown();
|
||||
let configured_drain = settings.drain_timeout();
|
||||
let drain = if matches!(cause, H2ShutdownCause::Process) {
|
||||
@@ -122,4 +118,4 @@ async fn http2_connection<I>(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
include!("serve/tests.rs");
|
||||
include!("tests.rs");
|
||||
@@ -162,7 +162,6 @@ mod h2c_tests {
|
||||
.write_all(HTTP2_PRIOR_KNOWLEDGE_PREFACE)
|
||||
.await
|
||||
.unwrap();
|
||||
// Client connection preface must begin with a non-ACK SETTINGS frame.
|
||||
client
|
||||
.write_all(&[0, 0, 0, 0x4, 0, 0, 0, 0, 0])
|
||||
.await
|
||||
|
||||
@@ -18,8 +18,6 @@ async fn serve_inner(
|
||||
_ = tasks.join_next(), if !tasks.is_empty() => {},
|
||||
accepted = listener.accept() => {
|
||||
let (stream, peer) = accepted.context("listener accept failed")?;
|
||||
// Never substitute the wildcard listener address or a forwarded
|
||||
// header for the destination IP of this accepted connection.
|
||||
let local = stream.local_addr().context("accepted local address unavailable")?;
|
||||
let Ok(permit) = permits.clone().try_acquire_owned() else {
|
||||
if let Some(m) = &metrics { m.rejected_connections.fetch_add(1, Ordering::Relaxed); }
|
||||
@@ -47,8 +45,6 @@ async fn serve_inner(
|
||||
tasks.spawn(async move {
|
||||
match tls {
|
||||
Some(TlsMode::Frontend(state, http2)) => {
|
||||
// Read ClientHello before committing to ALPN so a vhost can opt out
|
||||
// of HTTP/2 or narrow TLS policy while sharing one TLS listener.
|
||||
let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
|
||||
let acceptor = tokio_rustls::LazyConfigAcceptor::new(
|
||||
rustls::server::Acceptor::default(),
|
||||
@@ -245,9 +241,6 @@ async fn serve_inner(
|
||||
&& frontend_state
|
||||
.as_ref()
|
||||
.is_some_and(|state| state.config.load().websocket_rfc8441_on(local, false));
|
||||
// The existing Hyper transport remains untouched when the
|
||||
// option is disabled. Connections opened after a hot config
|
||||
// change acquire a new bridge automatically.
|
||||
#[cfg(target_os = "linux")]
|
||||
if !prior_h2 && frontend_state.as_ref().is_some_and(|state| {
|
||||
state.config.load().web_php_services.values()
|
||||
|
||||
Reference in new issue
Block a user