feat(http2): Make HTTP/2 support an optional feature

cc #2251

BREAKING CHANGE: This puts all HTTP/2 methods and support behind an
  `http2` cargo feature, which will not be enabled by default. To use
  HTTP/2, add `features = ["http2"]` to the hyper dependency in your
  `Cargo.toml`.
This commit is contained in:
Sean McArthur
2020-11-09 16:11:04 -08:00
parent 5438e9b7bf
commit b819b428d3
19 changed files with 395 additions and 112 deletions

View File

@@ -13,6 +13,7 @@ use std::fmt;
use std::mem;
use std::sync::Arc;
#[cfg(feature = "runtime")]
#[cfg(feature = "http2")]
use std::time::Duration;
use bytes::Bytes;
@@ -36,6 +37,7 @@ where
B: HttpBody,
{
H1(#[pin] Http1Dispatcher<T, B, proto::h1::ClientTransaction>),
#[cfg(feature = "http2")]
H2(#[pin] proto::h2::ClientTask<B>),
}
@@ -79,8 +81,16 @@ pub struct Builder {
h1_title_case_headers: bool,
h1_read_buf_exact_size: Option<usize>,
h1_max_buf_size: Option<usize>,
http2: bool,
#[cfg(feature = "http2")]
h2_builder: proto::h2::client::Config,
version: Proto,
}
#[derive(Clone, Debug)]
enum Proto {
Http1,
#[cfg(feature = "http2")]
Http2,
}
/// A future returned by `SendRequest::send_request`.
@@ -122,6 +132,7 @@ pub struct Parts<T> {
// A `SendRequest` that can be cloned to send HTTP2 requests.
// private for now, probably not a great idea of a type...
#[must_use = "futures do nothing unless polled"]
#[cfg(feature = "http2")]
pub(super) struct Http2SendRequest<B> {
dispatch: dispatch::UnboundedSender<Request<B>, Response<Body>>,
}
@@ -152,6 +163,7 @@ impl<B> SendRequest<B> {
self.dispatch.is_closed()
}
#[cfg(feature = "http2")]
pub(super) fn into_http2(self) -> Http2SendRequest<B> {
Http2SendRequest {
dispatch: self.dispatch.unbound(),
@@ -269,6 +281,7 @@ impl<B> fmt::Debug for SendRequest<B> {
// ===== impl Http2SendRequest
#[cfg(feature = "http2")]
impl<B> Http2SendRequest<B> {
pub(super) fn is_ready(&self) -> bool {
self.dispatch.is_ready()
@@ -279,6 +292,7 @@ impl<B> Http2SendRequest<B> {
}
}
#[cfg(feature = "http2")]
impl<B> Http2SendRequest<B>
where
B: HttpBody + 'static,
@@ -310,12 +324,14 @@ where
}
}
#[cfg(feature = "http2")]
impl<B> fmt::Debug for Http2SendRequest<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Http2SendRequest").finish()
}
}
#[cfg(feature = "http2")]
impl<B> Clone for Http2SendRequest<B> {
fn clone(&self) -> Self {
Http2SendRequest {
@@ -339,6 +355,7 @@ where
pub fn into_parts(self) -> Parts<T> {
let (io, read_buf, _) = match self.inner.expect("already upgraded") {
ProtoClient::H1(h1) => h1.into_inner(),
#[cfg(feature = "http2")]
ProtoClient::H2(_h2) => {
panic!("http2 cannot into_inner");
}
@@ -365,6 +382,7 @@ where
pub fn poll_without_shutdown(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
match *self.inner.as_mut().expect("already upgraded") {
ProtoClient::H1(ref mut h1) => h1.poll_without_shutdown(cx),
#[cfg(feature = "http2")]
ProtoClient::H2(ref mut h2) => Pin::new(h2).poll(cx).map_ok(|_| ()),
}
}
@@ -428,8 +446,9 @@ impl Builder {
h1_read_buf_exact_size: None,
h1_title_case_headers: false,
h1_max_buf_size: None,
http2: false,
#[cfg(feature = "http2")]
h2_builder: Default::default(),
version: Proto::Http1,
}
}
@@ -472,8 +491,10 @@ impl Builder {
/// Sets whether HTTP2 is required.
///
/// Default is false.
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
pub fn http2_only(&mut self, enabled: bool) -> &mut Builder {
self.http2 = enabled;
self.version = if enabled { Proto::Http2 } else { Proto::Http1 };
self
}
@@ -485,6 +506,8 @@ impl Builder {
/// If not set, hyper will use a default.
///
/// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
pub fn http2_initial_stream_window_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
if let Some(sz) = sz.into() {
self.h2_builder.adaptive_window = false;
@@ -498,6 +521,8 @@ impl Builder {
/// Passing `None` will do nothing.
///
/// If not set, hyper will use a default.
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
pub fn http2_initial_connection_window_size(
&mut self,
sz: impl Into<Option<u32>>,
@@ -514,6 +539,8 @@ impl Builder {
/// Enabling this will override the limits set in
/// `http2_initial_stream_window_size` and
/// `http2_initial_connection_window_size`.
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
pub fn http2_adaptive_window(&mut self, enabled: bool) -> &mut Self {
use proto::h2::SPEC_WINDOW_SIZE;
@@ -530,6 +557,8 @@ impl Builder {
/// Passing `None` will do nothing.
///
/// If not set, hyper will use a default.
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
pub fn http2_max_frame_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
if let Some(sz) = sz.into() {
self.h2_builder.max_frame_size = sz;
@@ -548,6 +577,8 @@ impl Builder {
///
/// Requires the `runtime` cargo feature to be enabled.
#[cfg(feature = "runtime")]
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
pub fn http2_keep_alive_interval(
&mut self,
interval: impl Into<Option<Duration>>,
@@ -567,6 +598,8 @@ impl Builder {
///
/// Requires the `runtime` cargo feature to be enabled.
#[cfg(feature = "runtime")]
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
pub fn http2_keep_alive_timeout(&mut self, timeout: Duration) -> &mut Self {
self.h2_builder.keep_alive_timeout = timeout;
self
@@ -585,6 +618,8 @@ impl Builder {
///
/// Requires the `runtime` cargo feature to be enabled.
#[cfg(feature = "runtime")]
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
pub fn http2_keep_alive_while_idle(&mut self, enabled: bool) -> &mut Self {
self.h2_builder.keep_alive_while_idle = enabled;
self
@@ -604,34 +639,39 @@ impl Builder {
let opts = self.clone();
async move {
trace!("client handshake HTTP/{}", if opts.http2 { 2 } else { 1 });
trace!("client handshake {:?}", opts.version);
let (tx, rx) = dispatch::channel();
let proto = if !opts.http2 {
let mut conn = proto::Conn::new(io);
if let Some(writev) = opts.h1_writev {
if writev {
conn.set_write_strategy_queue();
} else {
conn.set_write_strategy_flatten();
let proto = match opts.version {
Proto::Http1 => {
let mut conn = proto::Conn::new(io);
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();
}
if let Some(sz) = opts.h1_read_buf_exact_size {
conn.set_read_buf_exact_size(sz);
}
if let Some(max) = opts.h1_max_buf_size {
conn.set_max_buf_size(max);
}
let cd = proto::h1::dispatch::Client::new(rx);
let dispatch = proto::h1::Dispatcher::new(cd, conn);
ProtoClient::H1(dispatch)
}
if opts.h1_title_case_headers {
conn.set_title_case_headers();
#[cfg(feature = "http2")]
Proto::Http2 => {
let h2 =
proto::h2::client::handshake(io, rx, &opts.h2_builder, opts.exec.clone())
.await?;
ProtoClient::H2(h2)
}
if let Some(sz) = opts.h1_read_buf_exact_size {
conn.set_read_buf_exact_size(sz);
}
if let Some(max) = opts.h1_max_buf_size {
conn.set_max_buf_size(max);
}
let cd = proto::h1::dispatch::Client::new(rx);
let dispatch = proto::h1::Dispatcher::new(cd, conn);
ProtoClient::H1(dispatch)
} else {
let h2 = proto::h2::client::handshake(io, rx, &opts.h2_builder, opts.exec.clone())
.await?;
ProtoClient::H2(h2)
};
Ok((
@@ -684,6 +724,7 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
match self.project() {
ProtoClientProj::H1(c) => c.poll(cx),
#[cfg(feature = "http2")]
ProtoClientProj::H2(c) => c.poll(cx),
}
}