Skip to content

Commit b0aa649

Browse files
committed
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.
1 parent d2fdf1f commit b0aa649

File tree

3 files changed

+38
-5
lines changed

3 files changed

+38
-5
lines changed

src/client/mod.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub mod compat;
4242
pub struct Client<C, B = proto::Body> {
4343
connector: C,
4444
executor: Exec,
45+
h1_writev: bool,
4546
pool: Pool<HyperClient<B>>,
4647
}
4748

@@ -96,6 +97,7 @@ impl<C, B> Client<C, B> {
9697
Client {
9798
connector: config.connector,
9899
executor: exec,
100+
h1_writev: config.h1_writev,
99101
pool: Pool::new(config.keep_alive, config.keep_alive_timeout)
100102
}
101103
}
@@ -197,6 +199,7 @@ where C: Connect,
197199
let executor = self.executor.clone();
198200
let pool = self.pool.clone();
199201
let pool_key = Rc::new(domain.to_string());
202+
let h1_writev = self.h1_writev;
200203
self.connector.connect(url)
201204
.and_then(move |io| {
202205
let (tx, rx) = dispatch::channel();
@@ -205,7 +208,10 @@ where C: Connect,
205208
should_close: Cell::new(true),
206209
};
207210
let pooled = pool.pooled(pool_key, tx);
208-
let conn = proto::Conn::<_, _, proto::ClientTransaction, _>::new(io, pooled.clone());
211+
let mut conn = proto::Conn::<_, _, proto::ClientTransaction, _>::new(io, pooled.clone());
212+
if !h1_writev {
213+
conn.set_write_strategy_flatten();
214+
}
209215
let dispatch = proto::dispatch::Dispatcher::new(proto::dispatch::Client::new(rx), conn);
210216
executor.execute(dispatch.map_err(|e| debug!("client connection error: {}", e)))?;
211217
Ok(pooled)
@@ -256,6 +262,7 @@ impl<C: Clone, B> Clone for Client<C, B> {
256262
Client {
257263
connector: self.connector.clone(),
258264
executor: self.executor.clone(),
265+
h1_writev: self.h1_writev,
259266
pool: self.pool.clone(),
260267
}
261268
}
@@ -307,9 +314,9 @@ pub struct Config<C, B> {
307314
connector: C,
308315
keep_alive: bool,
309316
keep_alive_timeout: Option<Duration>,
317+
h1_writev: bool,
310318
//TODO: make use of max_idle config
311319
max_idle: usize,
312-
no_proto: bool,
313320
}
314321

315322
/// Phantom type used to signal that `Config` should create a `HttpConnector`.
@@ -324,8 +331,8 @@ impl Default for Config<UseDefaultConnector, proto::Body> {
324331
connector: UseDefaultConnector(()),
325332
keep_alive: true,
326333
keep_alive_timeout: Some(Duration::from_secs(90)),
334+
h1_writev: true,
327335
max_idle: 5,
328-
no_proto: false,
329336
}
330337
}
331338
}
@@ -348,8 +355,8 @@ impl<C, B> Config<C, B> {
348355
connector: self.connector,
349356
keep_alive: self.keep_alive,
350357
keep_alive_timeout: self.keep_alive_timeout,
358+
h1_writev: self.h1_writev,
351359
max_idle: self.max_idle,
352-
no_proto: self.no_proto,
353360
}
354361
}
355362

@@ -362,8 +369,8 @@ impl<C, B> Config<C, B> {
362369
connector: val,
363370
keep_alive: self.keep_alive,
364371
keep_alive_timeout: self.keep_alive_timeout,
372+
h1_writev: self.h1_writev,
365373
max_idle: self.max_idle,
366-
no_proto: self.no_proto,
367374
}
368375
}
369376

@@ -398,6 +405,20 @@ impl<C, B> Config<C, B> {
398405
}
399406
*/
400407

408+
/// Set whether HTTP/1 connections should try to use vectored writes,
409+
/// or always flatten into a single buffer.
410+
///
411+
/// Note that setting this to false may mean more copies of body data,
412+
/// but may also improve performance when an IO transport doesn't
413+
/// support vectored writes well, such as most TLS implementations.
414+
///
415+
/// Default is true.
416+
#[inline]
417+
pub fn http1_writev(mut self, val: bool) -> Config<C, B> {
418+
self.h1_writev = val;
419+
self
420+
}
421+
401422
#[doc(hidden)]
402423
#[deprecated(since="0.11.11", note="no_proto is always enabled")]
403424
pub fn no_proto(self) -> Config<C, B> {
@@ -444,6 +465,7 @@ impl<C, B> fmt::Debug for Config<C, B> {
444465
f.debug_struct("Config")
445466
.field("keep_alive", &self.keep_alive)
446467
.field("keep_alive_timeout", &self.keep_alive_timeout)
468+
.field("http1_writev", &self.h1_writev)
447469
.field("max_idle", &self.max_idle)
448470
.finish()
449471
}

src/proto/h1/conn.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ where I: AsyncRead + AsyncWrite,
6262
self.io.set_max_buf_size(max);
6363
}
6464

65+
pub fn set_write_strategy_flatten(&mut self) {
66+
self.io.set_write_strategy_flatten();
67+
}
68+
6569
#[cfg(feature = "tokio-proto")]
6670
fn poll_incoming(&mut self) -> Poll<Option<Frame<MessageHead<T::Incoming>, Chunk, ::Error>>, io::Error> {
6771
trace!("Conn::poll_incoming()");

src/proto/h1/io.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ where
6565
self.write_buf.max_buf_size = max;
6666
}
6767

68+
pub fn set_write_strategy_flatten(&mut self) {
69+
// this should always be called only at construction time,
70+
// so this assert is here to catch myself
71+
debug_assert!(self.write_buf.buf.bufs.is_empty());
72+
self.write_buf.set_strategy(Strategy::Flatten);
73+
}
74+
6875
pub fn read_buf(&self) -> &[u8] {
6976
self.read_buf.as_ref()
7077
}

0 commit comments

Comments
 (0)