From 8474cf8145838dc92e0170eaf8364a18cb82439a Mon Sep 17 00:00:00 2001 From: jiahaoliang Date: Wed, 27 Aug 2025 11:28:31 +0800 Subject: [PATCH] add http10_disable_keep_alive() for server and client add http10_disable_keep_alive() for server and client to disable keep_alive connections for HTTP/1.0 requests and response --- src/client/client.rs | 15 +++++++++++++++ src/client/conn.rs | 18 ++++++++++++++++++ src/client/conn/http1.rs | 3 +++ src/proto/h1/conn.rs | 15 ++++++++++++++- src/proto/h1/io.rs | 2 ++ src/proto/h1/mod.rs | 1 + src/proto/h1/role.rs | 38 ++++++++++++++++++++++++++++++++++---- src/server/conn.rs | 25 +++++++++++++++++++++++++ src/server/conn/http1.rs | 18 ++++++++++++++++++ src/server/server.rs | 18 ++++++++++++++++++ 10 files changed, 148 insertions(+), 5 deletions(-) diff --git a/src/client/client.rs b/src/client/client.rs index 8195554bd7..f850e36775 100644 --- a/src/client/client.rs +++ b/src/client/client.rs @@ -1002,6 +1002,21 @@ impl Builder { self } + /// Set whether to disable keep alive for HTTP/1.0. + /// + /// Currently, keep alive for HTTP/1.0 is supported if Connection: keep-alive is set for + /// either `Request` or `Response`. If this is enabled, enforcing Connection: close for + /// HTTP/1.0 `Request` or `Response` to make sure HTTP/1.0 connection drops and will not be + /// put back to H1 connection pool. + /// + /// Note that this setting does not affect HTTP/2. + /// + /// Default is false. + pub fn http10_disable_keep_alive(&mut self, disable: bool) -> &mut Self { + self.conn_builder.http10_disable_keep_alive(disable); + self + } + // HTTP/1 options /// Sets the exact size of the read buffer to *always* use. diff --git a/src/client/conn.rs b/src/client/conn.rs index 8da457da64..5d8b6d2b01 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -194,6 +194,7 @@ pub struct Builder { h1_max_buf_size: Option, #[cfg(feature = "ffi")] h1_headers_raw: bool, + h10_disable_keep_alive: bool, #[cfg(feature = "http2")] h2_builder: proto::h2::client::Config, version: Proto, @@ -605,6 +606,7 @@ impl Builder { h1_max_buf_size: None, #[cfg(feature = "ffi")] h1_headers_raw: false, + h10_disable_keep_alive: false, #[cfg(feature = "http2")] h2_builder: Default::default(), #[cfg(feature = "http1")] @@ -817,6 +819,21 @@ impl Builder { self } + /// Set whether to disable keep alive for HTTP/1.0. + /// + /// Currently, keep alive for HTTP/1.0 is supported if Connection: keep-alive is set for + /// either `Request` or `Response`. If this is enabled, enforcing Connection: close for + /// HTTP/1.0 `Request` or `Response` to make sure HTTP/1.0 connection drops and will not be + /// put back to H1 connection pool. + /// + /// Note that this setting does not affect HTTP/2. + /// + /// Default is false. + pub fn http10_disable_keep_alive(&mut self, disable: bool) -> &mut Self { + self.h10_disable_keep_alive = disable; + self + } + /// Sets whether HTTP2 is required. /// /// Default is false. @@ -1019,6 +1036,7 @@ impl Builder { conn.set_write_strategy_flatten(); } } + conn.set_http10_disable_keep_alive(opts.h10_disable_keep_alive); if opts.h1_title_case_headers { conn.set_title_case_headers(); } diff --git a/src/client/conn/http1.rs b/src/client/conn/http1.rs index 37eda04067..701aa635f3 100644 --- a/src/client/conn/http1.rs +++ b/src/client/conn/http1.rs @@ -119,6 +119,7 @@ pub struct Builder { h1_preserve_header_order: bool, h1_read_buf_exact_size: Option, h1_max_buf_size: Option, + h10_disable_keep_alive: bool, } /// Returns a handshake future over some IO. @@ -305,6 +306,7 @@ impl Builder { #[cfg(feature = "ffi")] h1_preserve_header_order: false, h1_max_buf_size: None, + h10_disable_keep_alive: false, } } @@ -508,6 +510,7 @@ impl Builder { conn.set_write_strategy_flatten(); } } + conn.set_http10_disable_keep_alive(opts.h10_disable_keep_alive); if opts.h1_title_case_headers { conn.set_title_case_headers(); } diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 5ab72f264e..9c8eb69773 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -51,6 +51,7 @@ where cached_headers: None, error: None, keep_alive: KA::Busy, + h10_disable_keep_alive: false, method: None, h1_parser_config: ParserConfig::default(), #[cfg(all(feature = "server", feature = "runtime"))] @@ -130,6 +131,10 @@ where self.state.h1_header_read_timeout = Some(val); } + pub(crate) fn set_http10_disable_keep_alive(&mut self, disable: bool) { + self.state.h10_disable_keep_alive = disable; + } + #[cfg(feature = "server")] pub(crate) fn set_allow_half_close(&mut self) { self.state.allow_half_close = true; @@ -208,6 +213,7 @@ where #[cfg(feature = "ffi")] preserve_header_order: self.state.preserve_header_order, h09_responses: self.state.h09_responses, + h10_disable_keep_alive: self.state.h10_disable_keep_alive, #[cfg(feature = "ffi")] on_informational: &mut self.state.on_informational, #[cfg(feature = "ffi")] @@ -614,7 +620,13 @@ where // If we know the remote speaks an older version, we try to fix up any messages // to work with our older peer. fn enforce_version(&mut self, head: &mut MessageHead) { - if let Version::HTTP_10 = self.state.version { + // if h10_disable_keep_alive is set, force http/1.0 connection header to close. + // this function is only called in encode_head. + if self.state.h10_disable_keep_alive && head.version == Version::HTTP_10 { + self.state.disable_keep_alive(); + head.headers + .insert(CONNECTION, HeaderValue::from_static("close")); + } else if let Version::HTTP_10 = self.state.version { // Fixes response or connection when keep-alive header is not present self.fix_keep_alive(head); // If the remote only knows HTTP/1.0, we should force ourselves @@ -816,6 +828,7 @@ struct State { error: Option, /// Current keep-alive status. keep_alive: KA, + h10_disable_keep_alive: bool, /// If mid-message, the HTTP Method that started it. /// /// This is used to know things such as if the message can include diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 88ce9dac5f..5427077e2c 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -201,6 +201,7 @@ where #[cfg(feature = "ffi")] preserve_header_order: parse_ctx.preserve_header_order, h09_responses: parse_ctx.h09_responses, + h10_disable_keep_alive: parse_ctx.h10_disable_keep_alive, #[cfg(feature = "ffi")] on_informational: parse_ctx.on_informational, #[cfg(feature = "ffi")] @@ -755,6 +756,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] diff --git a/src/proto/h1/mod.rs b/src/proto/h1/mod.rs index 5a2587a843..5708bfb086 100644 --- a/src/proto/h1/mod.rs +++ b/src/proto/h1/mod.rs @@ -86,6 +86,7 @@ pub(crate) struct ParseContext<'a> { #[cfg(feature = "ffi")] preserve_header_order: bool, h09_responses: bool, + h10_disable_keep_alive: bool, #[cfg(feature = "ffi")] on_informational: &'a mut Option, #[cfg(feature = "ffi")] diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 1c00d7445d..6fe30ef1cf 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -262,7 +262,7 @@ impl Http1Transaction for Server { // SAFETY: array is valid up to `headers_len` let header = unsafe { &*header.as_ptr() }; let name = header_name!(&slice[header.name.0..header.name.1]); - let value = header_value!(slice.slice(header.value.0..header.value.1)); + let mut value = header_value!(slice.slice(header.value.0..header.value.1)); match name { header::TRANSFER_ENCODING => { @@ -309,7 +309,14 @@ impl Http1Transaction for Server { keep_alive = !headers::connection_close(&value); } else { // HTTP/1.0 - keep_alive = headers::connection_keep_alive(&value); + if ctx.h10_disable_keep_alive { + // if h10_disable_keep_alive is set, disable keep_alive, + // read and parsed the connection header as close + keep_alive = false; + value = HeaderValue::from_static("close"); + } else { + keep_alive = headers::connection_keep_alive(&value); + } } } header::EXPECT => { @@ -1057,7 +1064,7 @@ impl Http1Transaction for Client { // SAFETY: array is valid up to `headers_len` let header = unsafe { &*header.as_ptr() }; let name = header_name!(&slice[header.name.0..header.name.1]); - let value = header_value!(slice.slice(header.value.0..header.value.1)); + let mut value = header_value!(slice.slice(header.value.0..header.value.1)); if let header::CONNECTION = name { // keep_alive was previously set to default for Version @@ -1066,7 +1073,14 @@ impl Http1Transaction for Client { keep_alive = !headers::connection_close(&value); } else { // HTTP/1.0 - keep_alive = headers::connection_keep_alive(&value); + if ctx.h10_disable_keep_alive { + // if h10_disable_keep_alive is set, disable keep_alive, + // read and parsed the connection header as close + keep_alive = false; + value = HeaderValue::from_static("close"); + } else { + keep_alive = headers::connection_keep_alive(&value); + } } } @@ -1565,6 +1579,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -1600,6 +1615,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -1630,6 +1646,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -1658,6 +1675,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: true, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -1688,6 +1706,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -1722,6 +1741,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -1753,6 +1773,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -1779,6 +1800,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -1826,6 +1848,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -1854,6 +1877,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -2091,6 +2115,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -2119,6 +2144,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -2147,6 +2173,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -2652,6 +2679,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -2766,6 +2794,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] @@ -2814,6 +2843,7 @@ mod tests { #[cfg(feature = "ffi")] preserve_header_order: false, h09_responses: false, + h10_disable_keep_alive: false, #[cfg(feature = "ffi")] on_informational: &mut None, #[cfg(feature = "ffi")] diff --git a/src/server/conn.rs b/src/server/conn.rs index 951c9ee5cd..f3f7f87be3 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -111,6 +111,7 @@ pub struct Http { pub(crate) exec: E, h1_half_close: bool, h1_keep_alive: bool, + h10_disable_keep_alive: bool, h1_title_case_headers: bool, h1_preserve_header_case: bool, #[cfg(all(feature = "http1", feature = "runtime"))] @@ -257,6 +258,7 @@ impl Http { exec: Exec::Default, h1_half_close: false, h1_keep_alive: true, + h10_disable_keep_alive: false, h1_title_case_headers: false, h1_preserve_header_case: false, #[cfg(all(feature = "http1", feature = "runtime"))] @@ -316,6 +318,23 @@ impl Http { self } + /// Set whether to disable keep alive for HTTP/1.0. + /// + /// Currently, keep alive for HTTP/1.0 is supported if Connection: keep-alive is set for + /// either `Request` or `Response`. If this is enabled, enforcing Connection: close for + /// HTTP/1.0 `Request` or `Response` to make sure HTTP/1.0 connection drops and will not be + /// put back to H1 connection pool. + /// + /// Note that this setting does not affect HTTP/2. + /// + /// Default is false. + #[cfg(feature = "http1")] + #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] + pub fn http10_disable_keep_alive(&mut self, disable: bool) -> &mut Self { + self.h10_disable_keep_alive = disable; + self + } + /// Set whether HTTP/1 connections will write header names as title case at /// the socket level. /// @@ -621,6 +640,7 @@ impl Http { exec, h1_half_close: self.h1_half_close, h1_keep_alive: self.h1_keep_alive, + h10_disable_keep_alive: false, h1_title_case_headers: self.h1_title_case_headers, h1_preserve_header_case: self.h1_preserve_header_case, #[cfg(all(feature = "http1", feature = "runtime"))] @@ -678,6 +698,7 @@ impl Http { if !self.h1_keep_alive { conn.disable_keep_alive(); } + conn.set_http10_disable_keep_alive(self.h10_disable_keep_alive); if self.h1_half_close { conn.set_allow_half_close(); } @@ -765,10 +786,14 @@ where match self.conn { #[cfg(feature = "http1")] Some(ProtoServer::H1 { ref mut h1, .. }) => { + #[cfg(feature = "cloudsop_hiro_log_adapt")] + log::info!("h1.disable_keep_alive"); h1.disable_keep_alive(); } #[cfg(feature = "http2")] Some(ProtoServer::H2 { ref mut h2 }) => { + #[cfg(feature = "cloudsop_hiro_log_adapt")] + log::info!("h2.graceful_shutdown()"); h2.graceful_shutdown(); } None => (), diff --git a/src/server/conn/http1.rs b/src/server/conn/http1.rs index ab833b938b..a0bce67472 100644 --- a/src/server/conn/http1.rs +++ b/src/server/conn/http1.rs @@ -40,6 +40,7 @@ pin_project_lite::pin_project! { pub struct Builder { h1_half_close: bool, h1_keep_alive: bool, + h10_disable_keep_alive: bool, h1_title_case_headers: bool, h1_preserve_header_case: bool, h1_header_read_timeout: Option, @@ -206,6 +207,7 @@ impl Builder { Self { h1_half_close: false, h1_keep_alive: true, + h10_disable_keep_alive: false, h1_title_case_headers: false, h1_preserve_header_case: false, h1_header_read_timeout: None, @@ -235,6 +237,21 @@ impl Builder { self } + /// Set whether to disable keep alive for HTTP/1.0. + /// + /// Currently, keep alive for HTTP/1.0 is supported if Connection: keep-alive is set for + /// either `Request` or `Response`. If this is enabled, enforcing Connection: close for + /// HTTP/1.0 `Request` or `Response` to make sure HTTP/1.0 connection drops and will not be + /// put back to H1 connection pool. + /// + /// Note that this setting does not affect HTTP/2. + /// + /// Default is false. + pub fn http10_disable_keep_alive(&mut self, disable: bool) -> &mut Self { + self.h10_disable_keep_alive = disable; + self + } + /// Set whether HTTP/1 connections will write header names as title case at /// the socket level. /// @@ -361,6 +378,7 @@ impl Builder { if !self.h1_keep_alive { conn.disable_keep_alive(); } + conn.set_http10_disable_keep_alive(self.h10_disable_keep_alive); if self.h1_half_close { conn.set_allow_half_close(); } diff --git a/src/server/server.rs b/src/server/server.rs index 4cccedd98a..5df3b719df 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -257,6 +257,24 @@ impl Builder { self } + /// Set whether to disable keep alive for HTTP/1.0. + /// + /// Currently, keep alive for HTTP/1.0 is supported if Connection: keep-alive is set for + /// either `Request` or `Response`. If this is enabled, enforcing Connection: close for + /// HTTP/1.0 `Request` or `Response` to make sure HTTP/1.0 connection drops and will not be + /// put back to H1 connection pool. + /// + /// Note that this setting does not affect HTTP/2. + /// + /// Default is false. + #[cfg(feature = "http1")] + #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] + pub fn http10_disable_keep_alive(mut self, disable: bool) -> Self { + self.protocol.http10_disable_keep_alive(disable); + self + } + + /// Set whether HTTP/1 connections should support half-closures. /// /// Clients can chose to shutdown their write-side while waiting