From b0aa6497258c20354ae0fe36d668e0c2361b3151 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Tue, 6 Feb 2018 14:53:21 -0800 Subject: [PATCH] feat(client): add `http1_writev` configuration option Setting this to false will force HTTP/1 connections to always flatten all buffers (headers and body) before writing to the transport. The default is true. --- src/client/mod.rs | 32 +++++++++++++++++++++++++++----- src/proto/h1/conn.rs | 4 ++++ src/proto/h1/io.rs | 7 +++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index b7c665d258..899cf9670d 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -42,6 +42,7 @@ pub mod compat; pub struct Client { connector: C, executor: Exec, + h1_writev: bool, pool: Pool>, } @@ -96,6 +97,7 @@ impl Client { Client { connector: config.connector, executor: exec, + h1_writev: config.h1_writev, pool: Pool::new(config.keep_alive, config.keep_alive_timeout) } } @@ -197,6 +199,7 @@ where C: Connect, let executor = self.executor.clone(); let pool = self.pool.clone(); let pool_key = Rc::new(domain.to_string()); + let h1_writev = self.h1_writev; self.connector.connect(url) .and_then(move |io| { let (tx, rx) = dispatch::channel(); @@ -205,7 +208,10 @@ where C: Connect, should_close: Cell::new(true), }; let pooled = pool.pooled(pool_key, tx); - let conn = proto::Conn::<_, _, proto::ClientTransaction, _>::new(io, pooled.clone()); + let mut conn = proto::Conn::<_, _, proto::ClientTransaction, _>::new(io, pooled.clone()); + if !h1_writev { + conn.set_write_strategy_flatten(); + } let dispatch = proto::dispatch::Dispatcher::new(proto::dispatch::Client::new(rx), conn); executor.execute(dispatch.map_err(|e| debug!("client connection error: {}", e)))?; Ok(pooled) @@ -256,6 +262,7 @@ impl Clone for Client { Client { connector: self.connector.clone(), executor: self.executor.clone(), + h1_writev: self.h1_writev, pool: self.pool.clone(), } } @@ -307,9 +314,9 @@ pub struct Config { connector: C, keep_alive: bool, keep_alive_timeout: Option, + h1_writev: bool, //TODO: make use of max_idle config max_idle: usize, - no_proto: bool, } /// Phantom type used to signal that `Config` should create a `HttpConnector`. @@ -324,8 +331,8 @@ impl Default for Config { connector: UseDefaultConnector(()), keep_alive: true, keep_alive_timeout: Some(Duration::from_secs(90)), + h1_writev: true, max_idle: 5, - no_proto: false, } } } @@ -348,8 +355,8 @@ impl Config { connector: self.connector, keep_alive: self.keep_alive, keep_alive_timeout: self.keep_alive_timeout, + h1_writev: self.h1_writev, max_idle: self.max_idle, - no_proto: self.no_proto, } } @@ -362,8 +369,8 @@ impl Config { connector: val, keep_alive: self.keep_alive, keep_alive_timeout: self.keep_alive_timeout, + h1_writev: self.h1_writev, max_idle: self.max_idle, - no_proto: self.no_proto, } } @@ -398,6 +405,20 @@ impl Config { } */ + /// 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. + /// + /// Default is true. + #[inline] + pub fn http1_writev(mut self, val: bool) -> Config { + self.h1_writev = val; + self + } + #[doc(hidden)] #[deprecated(since="0.11.11", note="no_proto is always enabled")] pub fn no_proto(self) -> Config { @@ -444,6 +465,7 @@ impl fmt::Debug for Config { f.debug_struct("Config") .field("keep_alive", &self.keep_alive) .field("keep_alive_timeout", &self.keep_alive_timeout) + .field("http1_writev", &self.h1_writev) .field("max_idle", &self.max_idle) .finish() } diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index a5e0e450a0..6cae1b2445 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -62,6 +62,10 @@ where I: AsyncRead + AsyncWrite, self.io.set_max_buf_size(max); } + pub fn set_write_strategy_flatten(&mut self) { + self.io.set_write_strategy_flatten(); + } + #[cfg(feature = "tokio-proto")] fn poll_incoming(&mut self) -> Poll, Chunk, ::Error>>, io::Error> { trace!("Conn::poll_incoming()"); diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 84c7646cb1..faa927d595 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -65,6 +65,13 @@ where self.write_buf.max_buf_size = max; } + pub 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.buf.bufs.is_empty()); + self.write_buf.set_strategy(Strategy::Flatten); + } + pub fn read_buf(&self) -> &[u8] { self.read_buf.as_ref() }