Skip to content

Commit

Permalink
feat(h1): Restore a way to force queue writing strategy (fixes #2676)
Browse files Browse the repository at this point in the history
  • Loading branch information
nox committed Oct 27, 2021
1 parent b5022f3 commit 4d3f7f4
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 5 deletions.
26 changes: 26 additions & 0 deletions src/client/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ pub struct Builder {
pub(super) exec: Exec,
h09_responses: bool,
h1_parser_config: ParserConfig,
h1_writev: Option<bool>,
h1_title_case_headers: bool,
h1_preserve_header_case: bool,
h1_read_buf_exact_size: Option<usize>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 h1_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.
///
Expand Down Expand Up @@ -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();
}
Expand Down
5 changes: 4 additions & 1 deletion src/proto/h1/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions src/proto/h1/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -520,7 +521,6 @@ impl<B> WriteBuf<B>
where
B: Buf,
{
#[cfg(feature = "server")]
fn set_strategy(&mut self, strategy: WriteStrategy) {
self.strategy = strategy;
}
Expand Down
30 changes: 30 additions & 0 deletions src/server/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub struct Http<E = Exec> {
h1_keep_alive: bool,
h1_title_case_headers: bool,
h1_preserve_header_case: bool,
h1_writev: Option<bool>,
#[cfg(feature = "http2")]
h2_builder: proto::h2::server::Config,
mode: ConnectionMode,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -363,6 +365,26 @@ impl<E> Http<E> {
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
Expand Down Expand Up @@ -538,6 +560,7 @@ impl<E> Http<E> {
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,
Expand Down Expand Up @@ -599,6 +622,13 @@ impl<E> Http<E> {
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);
Expand Down

0 comments on commit 4d3f7f4

Please sign in to comment.