From 12ee9e0f21c322e9d85b5c2413f6d6237b739216 Mon Sep 17 00:00:00 2001 From: akasakariko <148060537+akasakariko@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:30:59 +0900 Subject: [PATCH] fix(http1): avoid adding close to switching protocols responses Do not append Connection: close to an HTTP/1.1 101 response when HTTP keep-alive is disabled The connection is handed off to the upgraded protocol rather than closed Extend the existing upgrade test to cover a request with Connection: close, Upgrade Verify the response headers while retaining the buffered-byte and upgraded-I/O checks Fixes #4244 --- src/proto/h1/conn.rs | 4 +++- src/proto/h1/mod.rs | 4 ++++ src/proto/h1/role.rs | 4 ++++ tests/server.rs | 12 +++++++++--- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 8a5a734940..502e6c4922 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -707,7 +707,9 @@ where // to do only speak HTTP/1.0 as well. head.version = Version::HTTP_10; } - Version::HTTP_11 => { + // A 101 hands off the connection to another protocol, so disabled + // HTTP keep-alive must not add a close option to the upgrade response + Version::HTTP_11 if !T::is_switching_protocols(head) => { if let KA::Disabled = self.state.keep_alive.status() { match head.headers.entry(CONNECTION) { Entry::Occupied(mut entry) => { diff --git a/src/proto/h1/mod.rs b/src/proto/h1/mod.rs index 344d5a8a4f..4587440bec 100644 --- a/src/proto/h1/mod.rs +++ b/src/proto/h1/mod.rs @@ -37,6 +37,10 @@ pub(crate) trait Http1Transaction { fn on_error(err: &crate::Error) -> Option>; + fn is_switching_protocols(_head: &MessageHead) -> bool { + false + } + fn is_client() -> bool { !Self::is_server() } diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index fce1df78af..169919b102 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -510,6 +510,10 @@ impl Http1Transaction for Server { Some(msg) } + fn is_switching_protocols(head: &MessageHead) -> bool { + head.subject == StatusCode::SWITCHING_PROTOCOLS + } + fn is_server() -> bool { true } diff --git a/tests/server.rs b/tests/server.rs index 094558cb61..261aa0d35c 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -1819,18 +1819,23 @@ async fn upgrades_new() { b"\ GET / HTTP/1.1\r\n\ Upgrade: foobar\r\n\ - Connection: upgrade\r\n\ + Connection: close, Upgrade\r\n\ \r\n\ eagerly optimistic\ ", ) .expect("write 1"); let mut buf = [0; 256]; - tcp.read(&mut buf).expect("read 1"); + let response = read_until(&mut tcp, |buf| buf.ends_with(b"\r\n\r\n")).expect("read 1"); - let response = s(&buf); + let response = s(&response); assert!(response.starts_with("HTTP/1.1 101 Switching Protocols\r\n")); assert!(!has_header(response, "content-length")); + assert!( + response.contains("\r\nconnection: upgrade\r\n"), + "{response}" + ); + assert!(!response.contains("close"), "{response}"); let _ = read_101_tx.send(()); let n = tcp.read(&mut buf).expect("read 2"); @@ -1846,6 +1851,7 @@ async fn upgrades_new() { Response::builder() .status(101) .header("upgrade", "foobar") + .header("connection", "upgrade") .body(Empty::::new()) .unwrap(), )