From 80627141ede3cdaf37b833ee91d08be0af0f5c84 Mon Sep 17 00:00:00 2001 From: Anthony Ramine <123095+nox@users.noreply.github.com> Date: Thu, 4 Nov 2021 23:29:43 +0100 Subject: [PATCH] feat(http1): Add `http1_writev(bool)` to client and server Builders Restore a way to force queue writing strategy. Closes #2676 --- src/client/client.rs | 17 +++++++++++++++++ src/client/conn.rs | 26 ++++++++++++++++++++++++++ src/proto/h1/conn.rs | 5 ++++- src/proto/h1/io.rs | 8 ++++---- src/server/conn.rs | 30 ++++++++++++++++++++++++++++++ src/server/server.rs | 18 ++++++++++++++++++ 6 files changed, 99 insertions(+), 5 deletions(-) diff --git a/src/client/client.rs b/src/client/client.rs index 72a78ab149..9125ff2ad7 100644 --- a/src/client/client.rs +++ b/src/client/client.rs @@ -1022,6 +1022,23 @@ impl Builder { self } + /// Set whether HTTP/1 connections should try to use vectored writes, + /// or always flatten into a single buffer. + /// + /// Note that setting this to false may mean more copies of body data, + /// but may also improve performance when an IO transport doesn't + /// support vectored writes well, such as most TLS implementations. + /// + /// Setting this to true will force hyper to use queued strategy + /// which may eliminate unnecessary cloning on some TLS backends + /// + /// Default is `auto`. In this mode hyper will try to guess which + /// mode to use + pub fn http1_writev(&mut self, enabled: bool) -> &mut Builder { + self.conn_builder.http1_writev(enabled); + self + } + /// Set whether HTTP/1 connections will write header names as title case at /// the socket level. /// diff --git a/src/client/conn.rs b/src/client/conn.rs index 1b7b661994..2aa09a7ffb 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -153,6 +153,7 @@ pub struct Builder { pub(super) exec: Exec, h09_responses: bool, h1_parser_config: ParserConfig, + h1_writev: Option, h1_title_case_headers: bool, h1_preserve_header_case: bool, h1_read_buf_exact_size: Option, @@ -535,6 +536,7 @@ impl Builder { Builder { exec: Exec::Default, h09_responses: false, + h1_writev: None, h1_read_buf_exact_size: None, h1_parser_config: Default::default(), h1_title_case_headers: false, @@ -596,6 +598,23 @@ impl Builder { self } + /// Set whether HTTP/1 connections should try to use vectored writes, + /// or always flatten into a single buffer. + /// + /// Note that setting this to false may mean more copies of body data, + /// but may also improve performance when an IO transport doesn't + /// support vectored writes well, such as most TLS implementations. + /// + /// Setting this to true will force hyper to use queued strategy + /// which may eliminate unnecessary cloning on some TLS backends + /// + /// Default is `auto`. In this mode hyper will try to guess which + /// mode to use + pub fn http1_writev(&mut self, enabled: bool) -> &mut Builder { + self.h1_writev = Some(enabled); + self + } + /// Set whether HTTP/1 connections will write header names as title case at /// the socket level. /// @@ -837,6 +856,13 @@ impl Builder { Proto::Http1 => { let mut conn = proto::Conn::new(io); conn.set_h1_parser_config(opts.h1_parser_config); + if let Some(writev) = opts.h1_writev { + if writev { + conn.set_write_strategy_queue(); + } else { + conn.set_write_strategy_flatten(); + } + } 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 887dee48e5..eba765226e 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -71,7 +71,6 @@ where self.io.set_flush_pipeline(enabled); } - #[cfg(test)] pub(crate) fn set_write_strategy_queue(&mut self) { self.io.set_write_strategy_queue(); } @@ -85,6 +84,10 @@ where self.io.set_read_buf_exact_size(sz); } + pub(crate) fn set_write_strategy_flatten(&mut self) { + self.io.set_write_strategy_flatten(); + } + #[cfg(feature = "client")] pub(crate) fn set_h1_parser_config(&mut self, parser_config: ParserConfig) { self.state.h1_parser_config = parser_config; diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 9fa6899001..712aad44d7 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -97,16 +97,17 @@ where self.read_buf_strategy = ReadStrategy::Exact(sz); } - #[cfg(feature = "server")] - fn set_write_strategy_flatten(&mut self) { + pub(crate) fn set_write_strategy_flatten(&mut self) { // this should always be called only at construction time, // so this assert is here to catch myself debug_assert!(self.write_buf.queue.bufs_cnt() == 0); self.write_buf.set_strategy(WriteStrategy::Flatten); } - #[cfg(test)] pub(crate) fn set_write_strategy_queue(&mut self) { + // this should always be called only at construction time, + // so this assert is here to catch myself + debug_assert!(self.write_buf.queue.bufs_cnt() == 0); self.write_buf.set_strategy(WriteStrategy::Queue); } @@ -520,7 +521,6 @@ impl WriteBuf where B: Buf, { - #[cfg(feature = "server")] fn set_strategy(&mut self, strategy: WriteStrategy) { self.strategy = strategy; } diff --git a/src/server/conn.rs b/src/server/conn.rs index b488f8ba7e..1bb8e8d676 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -103,6 +103,7 @@ pub struct Http { h1_keep_alive: bool, h1_title_case_headers: bool, h1_preserve_header_case: bool, + h1_writev: Option, #[cfg(feature = "http2")] h2_builder: proto::h2::server::Config, mode: ConnectionMode, @@ -284,6 +285,7 @@ impl Http { h1_keep_alive: true, h1_title_case_headers: false, h1_preserve_header_case: false, + h1_writev: None, #[cfg(feature = "http2")] h2_builder: Default::default(), mode: ConnectionMode::default(), @@ -363,6 +365,26 @@ impl Http { self } + /// Set whether HTTP/1 connections should try to use vectored writes, + /// or always flatten into a single buffer. + /// + /// Note that setting this to false may mean more copies of body data, + /// but may also improve performance when an IO transport doesn't + /// support vectored writes well, such as most TLS implementations. + /// + /// Setting this to true will force hyper to use queued strategy + /// which may eliminate unnecessary cloning on some TLS backends + /// + /// Default is `auto`. In this mode hyper will try to guess which + /// mode to use + #[inline] + #[cfg(feature = "http1")] + #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] + pub fn http1_writev(&mut self, val: bool) -> &mut Self { + self.h1_writev = Some(val); + self + } + /// Sets whether HTTP2 is required. /// /// Default is false @@ -538,6 +560,7 @@ impl Http { h1_keep_alive: self.h1_keep_alive, h1_title_case_headers: self.h1_title_case_headers, h1_preserve_header_case: self.h1_preserve_header_case, + h1_writev: self.h1_writev, #[cfg(feature = "http2")] h2_builder: self.h2_builder, mode: self.mode, @@ -599,6 +622,13 @@ impl Http { if self.h1_preserve_header_case { conn.set_preserve_header_case(); } + if let Some(writev) = self.h1_writev { + if writev { + conn.set_write_strategy_queue(); + } else { + conn.set_write_strategy_flatten(); + } + } conn.set_flush_pipeline(self.pipeline_flush); if let Some(max) = self.max_buf_size { conn.set_max_buf_size(max); diff --git a/src/server/server.rs b/src/server/server.rs index 0d559c579d..4b09dd7c9f 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -258,6 +258,24 @@ impl Builder { self } + /// Set whether HTTP/1 connections should try to use vectored writes, + /// or always flatten into a single buffer. + /// + /// Note that setting this to false may mean more copies of body data, + /// but may also improve performance when an IO transport doesn't + /// support vectored writes well, such as most TLS implementations. + /// + /// Setting this to true will force hyper to use queued strategy + /// which may eliminate unnecessary cloning on some TLS backends + /// + /// Default is `auto`. In this mode hyper will try to guess which + /// mode to use + #[cfg(feature = "http1")] + pub fn http1_writev(mut self, enabled: bool) -> Self { + self.protocol.http1_writev(enabled); + self + } + /// Set whether HTTP/1 connections will write header names as title case at /// the socket level. ///