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:
@@ -45,10 +45,12 @@
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
#[cfg(feature = "tcp")]
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(feature = "runtime")]
|
||||
#[cfg(feature = "http2")]
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::Bytes;
|
||||
@@ -57,9 +59,11 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::Accept;
|
||||
use crate::body::{Body, HttpBody};
|
||||
use crate::common::exec::{Exec, H2Exec, NewSvcExec};
|
||||
use crate::common::exec::{ConnStreamExec, Exec, NewSvcExec};
|
||||
#[cfg(feature = "http2")]
|
||||
use crate::common::io::Rewind;
|
||||
use crate::common::{task, Future, Pin, Poll, Unpin};
|
||||
#[cfg(feature = "http2")]
|
||||
use crate::error::{Kind, Parse};
|
||||
use crate::proto;
|
||||
use crate::service::{HttpService, MakeServiceRef};
|
||||
@@ -85,6 +89,7 @@ pub struct Http<E = Exec> {
|
||||
h1_half_close: bool,
|
||||
h1_keep_alive: bool,
|
||||
h1_writev: Option<bool>,
|
||||
#[cfg(feature = "http2")]
|
||||
h2_builder: proto::h2::server::Config,
|
||||
mode: ConnectionMode,
|
||||
max_buf_size: Option<usize>,
|
||||
@@ -97,8 +102,10 @@ enum ConnectionMode {
|
||||
/// Always use HTTP/1 and do not upgrade when a parse error occurs.
|
||||
H1Only,
|
||||
/// Always use HTTP/2.
|
||||
#[cfg(feature = "http2")]
|
||||
H2Only,
|
||||
/// Use HTTP/1 and try to upgrade to h2 when a parse error occurs.
|
||||
#[cfg(feature = "http2")]
|
||||
Fallback,
|
||||
}
|
||||
|
||||
@@ -150,6 +157,7 @@ where
|
||||
S: HttpService<Body>,
|
||||
{
|
||||
pub(super) conn: Option<ProtoServer<T, S::ResBody, S, E>>,
|
||||
#[cfg(feature = "http2")]
|
||||
fallback: Fallback<E>,
|
||||
}
|
||||
|
||||
@@ -167,16 +175,20 @@ where
|
||||
T,
|
||||
proto::ServerTransaction,
|
||||
>,
|
||||
PhantomData<E>,
|
||||
),
|
||||
#[cfg(feature = "http2")]
|
||||
H2(#[pin] proto::h2::Server<Rewind<T>, S, B, E>),
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
#[derive(Clone, Debug)]
|
||||
enum Fallback<E> {
|
||||
ToHttp2(proto::h2::server::Config, E),
|
||||
Http1Only,
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
impl<E> Fallback<E> {
|
||||
fn to_h2(&self) -> bool {
|
||||
match *self {
|
||||
@@ -186,6 +198,7 @@ impl<E> Fallback<E> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
impl<E> Unpin for Fallback<E> {}
|
||||
|
||||
/// Deconstructed parts of a `Connection`.
|
||||
@@ -221,8 +234,9 @@ impl Http {
|
||||
h1_half_close: false,
|
||||
h1_keep_alive: true,
|
||||
h1_writev: None,
|
||||
#[cfg(feature = "http2")]
|
||||
h2_builder: Default::default(),
|
||||
mode: ConnectionMode::Fallback,
|
||||
mode: ConnectionMode::default(),
|
||||
max_buf_size: None,
|
||||
pipeline_flush: false,
|
||||
}
|
||||
@@ -237,7 +251,10 @@ impl<E> Http<E> {
|
||||
if val {
|
||||
self.mode = ConnectionMode::H1Only;
|
||||
} else {
|
||||
self.mode = ConnectionMode::Fallback;
|
||||
#[cfg(feature = "http2")]
|
||||
{
|
||||
self.mode = ConnectionMode::Fallback;
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -291,6 +308,8 @@ impl<E> Http<E> {
|
||||
/// Sets whether HTTP2 is required.
|
||||
///
|
||||
/// Default is false
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_only(&mut self, val: bool) -> &mut Self {
|
||||
if val {
|
||||
self.mode = ConnectionMode::H2Only;
|
||||
@@ -308,6 +327,8 @@ impl<E> Http<E> {
|
||||
/// 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;
|
||||
@@ -321,6 +342,8 @@ impl<E> Http<E> {
|
||||
/// 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>>,
|
||||
@@ -337,6 +360,8 @@ impl<E> Http<E> {
|
||||
/// 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;
|
||||
|
||||
@@ -353,6 +378,8 @@ impl<E> Http<E> {
|
||||
/// 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;
|
||||
@@ -366,6 +393,8 @@ impl<E> Http<E> {
|
||||
/// Default is no limit (`std::u32::MAX`). Passing `None` will do nothing.
|
||||
///
|
||||
/// [spec]: https://http2.github.io/http2-spec/#SETTINGS_MAX_CONCURRENT_STREAMS
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_concurrent_streams(&mut self, max: impl Into<Option<u32>>) -> &mut Self {
|
||||
self.h2_builder.max_concurrent_streams = max.into();
|
||||
self
|
||||
@@ -382,6 +411,8 @@ impl<E> Http<E> {
|
||||
///
|
||||
/// 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>>,
|
||||
@@ -401,6 +432,8 @@ impl<E> Http<E> {
|
||||
///
|
||||
/// 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
|
||||
@@ -441,6 +474,7 @@ impl<E> Http<E> {
|
||||
h1_half_close: self.h1_half_close,
|
||||
h1_keep_alive: self.h1_keep_alive,
|
||||
h1_writev: self.h1_writev,
|
||||
#[cfg(feature = "http2")]
|
||||
h2_builder: self.h2_builder,
|
||||
mode: self.mode,
|
||||
max_buf_size: self.max_buf_size,
|
||||
@@ -483,10 +517,10 @@ impl<E> Http<E> {
|
||||
Bd: HttpBody + 'static,
|
||||
Bd::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
I: AsyncRead + AsyncWrite + Unpin,
|
||||
E: H2Exec<S::Future, Bd>,
|
||||
E: ConnStreamExec<S::Future, Bd>,
|
||||
{
|
||||
let proto = match self.mode {
|
||||
ConnectionMode::H1Only | ConnectionMode::Fallback => {
|
||||
macro_rules! h1 {
|
||||
() => {{
|
||||
let mut conn = proto::Conn::new(io);
|
||||
if !self.h1_keep_alive {
|
||||
conn.disable_keep_alive();
|
||||
@@ -506,8 +540,16 @@ impl<E> Http<E> {
|
||||
conn.set_max_buf_size(max);
|
||||
}
|
||||
let sd = proto::h1::dispatch::Server::new(service);
|
||||
ProtoServer::H1(proto::h1::Dispatcher::new(sd, conn))
|
||||
}
|
||||
ProtoServer::H1(proto::h1::Dispatcher::new(sd, conn), PhantomData)
|
||||
}};
|
||||
}
|
||||
|
||||
let proto = match self.mode {
|
||||
#[cfg(not(feature = "http2"))]
|
||||
ConnectionMode::H1Only => h1!(),
|
||||
#[cfg(feature = "http2")]
|
||||
ConnectionMode::H1Only | ConnectionMode::Fallback => h1!(),
|
||||
#[cfg(feature = "http2")]
|
||||
ConnectionMode::H2Only => {
|
||||
let rewind_io = Rewind::new(io);
|
||||
let h2 =
|
||||
@@ -518,6 +560,7 @@ impl<E> Http<E> {
|
||||
|
||||
Connection {
|
||||
conn: Some(proto),
|
||||
#[cfg(feature = "http2")]
|
||||
fallback: if self.mode == ConnectionMode::Fallback {
|
||||
Fallback::ToHttp2(self.h2_builder.clone(), self.exec.clone())
|
||||
} else {
|
||||
@@ -534,7 +577,7 @@ impl<E> Http<E> {
|
||||
S: MakeServiceRef<IO, Body, ResBody = Bd>,
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
Bd: HttpBody,
|
||||
E: H2Exec<<S::Service as HttpService<Body>>::Future, Bd>,
|
||||
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, Bd>,
|
||||
{
|
||||
Serve {
|
||||
incoming,
|
||||
@@ -553,7 +596,7 @@ where
|
||||
I: AsyncRead + AsyncWrite + Unpin,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: H2Exec<S::Future, B>,
|
||||
E: ConnStreamExec<S::Future, B>,
|
||||
{
|
||||
/// Start a graceful shutdown process for this connection.
|
||||
///
|
||||
@@ -567,9 +610,10 @@ where
|
||||
/// nothing.
|
||||
pub fn graceful_shutdown(self: Pin<&mut Self>) {
|
||||
match self.project().conn {
|
||||
Some(ProtoServer::H1(ref mut h1)) => {
|
||||
Some(ProtoServer::H1(ref mut h1, _)) => {
|
||||
h1.disable_keep_alive();
|
||||
}
|
||||
#[cfg(feature = "http2")]
|
||||
Some(ProtoServer::H2(ref mut h2)) => {
|
||||
h2.graceful_shutdown();
|
||||
}
|
||||
@@ -596,7 +640,7 @@ where
|
||||
/// This method will return a `None` if this connection is using an h2 protocol.
|
||||
pub fn try_into_parts(self) -> Option<Parts<I, S>> {
|
||||
match self.conn.unwrap() {
|
||||
ProtoServer::H1(h1) => {
|
||||
ProtoServer::H1(h1, _) => {
|
||||
let (io, read_buf, dispatch) = h1.into_inner();
|
||||
Some(Parts {
|
||||
io,
|
||||
@@ -605,6 +649,7 @@ where
|
||||
_inner: (),
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "http2")]
|
||||
ProtoServer::H2(_h2) => None,
|
||||
}
|
||||
}
|
||||
@@ -628,18 +673,24 @@ where
|
||||
{
|
||||
loop {
|
||||
let polled = match *self.conn.as_mut().unwrap() {
|
||||
ProtoServer::H1(ref mut h1) => h1.poll_without_shutdown(cx),
|
||||
ProtoServer::H1(ref mut h1, _) => h1.poll_without_shutdown(cx),
|
||||
#[cfg(feature = "http2")]
|
||||
ProtoServer::H2(ref mut h2) => return Pin::new(h2).poll(cx).map_ok(|_| ()),
|
||||
};
|
||||
match ready!(polled) {
|
||||
Ok(()) => return Poll::Ready(Ok(())),
|
||||
Err(e) => match *e.kind() {
|
||||
Kind::Parse(Parse::VersionH2) if self.fallback.to_h2() => {
|
||||
self.upgrade_h2();
|
||||
continue;
|
||||
Err(e) => {
|
||||
#[cfg(feature = "http2")]
|
||||
match *e.kind() {
|
||||
Kind::Parse(Parse::VersionH2) if self.fallback.to_h2() => {
|
||||
self.upgrade_h2();
|
||||
continue;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => return Poll::Ready(Err(e)),
|
||||
},
|
||||
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -659,12 +710,13 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
fn upgrade_h2(&mut self) {
|
||||
trace!("Trying to upgrade connection to h2");
|
||||
let conn = self.conn.take();
|
||||
|
||||
let (io, read_buf, dispatch) = match conn.unwrap() {
|
||||
ProtoServer::H1(h1) => h1.into_inner(),
|
||||
ProtoServer::H1(h1, _) => h1.into_inner(),
|
||||
ProtoServer::H2(_h2) => {
|
||||
panic!("h2 cannot into_inner");
|
||||
}
|
||||
@@ -699,7 +751,7 @@ where
|
||||
I: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: H2Exec<S::Future, B>,
|
||||
E: ConnStreamExec<S::Future, B>,
|
||||
{
|
||||
type Output = crate::Result<()>;
|
||||
|
||||
@@ -716,13 +768,18 @@ where
|
||||
}
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
Err(e) => match *e.kind() {
|
||||
Kind::Parse(Parse::VersionH2) if self.fallback.to_h2() => {
|
||||
self.upgrade_h2();
|
||||
continue;
|
||||
Err(e) => {
|
||||
#[cfg(feature = "http2")]
|
||||
match *e.kind() {
|
||||
Kind::Parse(Parse::VersionH2) if self.fallback.to_h2() => {
|
||||
self.upgrade_h2();
|
||||
continue;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => return Poll::Ready(Err(e)),
|
||||
},
|
||||
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -736,6 +793,23 @@ where
|
||||
f.debug_struct("Connection").finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl ConnectionMode =====
|
||||
|
||||
impl ConnectionMode {}
|
||||
|
||||
impl Default for ConnectionMode {
|
||||
#[cfg(feature = "http2")]
|
||||
fn default() -> ConnectionMode {
|
||||
ConnectionMode::Fallback
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "http2"))]
|
||||
fn default() -> ConnectionMode {
|
||||
ConnectionMode::H1Only
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Serve =====
|
||||
|
||||
impl<I, S, E> Serve<I, S, E> {
|
||||
@@ -766,7 +840,7 @@ where
|
||||
IE: Into<Box<dyn StdError + Send + Sync>>,
|
||||
S: MakeServiceRef<IO, Body, ResBody = B>,
|
||||
B: HttpBody,
|
||||
E: H2Exec<<S::Service as HttpService<Body>>::Future, B>,
|
||||
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||
{
|
||||
fn poll_next_(
|
||||
self: Pin<&mut Self>,
|
||||
@@ -804,7 +878,7 @@ where
|
||||
S: HttpService<Body, ResBody = B>,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: H2Exec<S::Future, B>,
|
||||
E: ConnStreamExec<S::Future, B>,
|
||||
{
|
||||
type Output = Result<Connection<I, S, E>, FE>;
|
||||
|
||||
@@ -838,7 +912,7 @@ where
|
||||
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
S: MakeServiceRef<IO, Body, ResBody = B>,
|
||||
B: HttpBody,
|
||||
E: H2Exec<<S::Service as HttpService<Body>>::Future, B>,
|
||||
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||
{
|
||||
pub(super) fn poll_watch<W>(
|
||||
self: Pin<&mut Self>,
|
||||
@@ -875,13 +949,14 @@ where
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: H2Exec<S::Future, B>,
|
||||
E: ConnStreamExec<S::Future, B>,
|
||||
{
|
||||
type Output = crate::Result<proto::Dispatched>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
match self.project() {
|
||||
ProtoServerProj::H1(s) => s.poll(cx),
|
||||
ProtoServerProj::H1(s, _) => s.poll(cx),
|
||||
#[cfg(feature = "http2")]
|
||||
ProtoServerProj::H2(s) => s.poll(cx),
|
||||
}
|
||||
}
|
||||
@@ -893,7 +968,7 @@ pub(crate) mod spawn_all {
|
||||
|
||||
use super::{Connecting, UpgradeableConnection};
|
||||
use crate::body::{Body, HttpBody};
|
||||
use crate::common::exec::H2Exec;
|
||||
use crate::common::exec::ConnStreamExec;
|
||||
use crate::common::{task, Future, Pin, Poll, Unpin};
|
||||
use crate::service::HttpService;
|
||||
use pin_project::pin_project;
|
||||
@@ -920,7 +995,7 @@ pub(crate) mod spawn_all {
|
||||
where
|
||||
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
S: HttpService<Body>,
|
||||
E: H2Exec<S::Future, S::ResBody>,
|
||||
E: ConnStreamExec<S::Future, S::ResBody>,
|
||||
S::ResBody: 'static,
|
||||
<S::ResBody as HttpBody>::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
@@ -970,7 +1045,7 @@ pub(crate) mod spawn_all {
|
||||
S: HttpService<Body, ResBody = B>,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: H2Exec<S::Future, B>,
|
||||
E: ConnStreamExec<S::Future, B>,
|
||||
W: Watcher<I, S, E>,
|
||||
{
|
||||
type Output = ();
|
||||
@@ -1036,7 +1111,7 @@ mod upgrades {
|
||||
I: AsyncRead + AsyncWrite + Unpin,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: H2Exec<S::Future, B>,
|
||||
E: ConnStreamExec<S::Future, B>,
|
||||
{
|
||||
/// Start a graceful shutdown process for this connection.
|
||||
///
|
||||
@@ -1054,7 +1129,7 @@ mod upgrades {
|
||||
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: super::H2Exec<S::Future, B>,
|
||||
E: ConnStreamExec<S::Future, B>,
|
||||
{
|
||||
type Output = crate::Result<()>;
|
||||
|
||||
@@ -1064,7 +1139,7 @@ mod upgrades {
|
||||
Ok(proto::Dispatched::Shutdown) => return Poll::Ready(Ok(())),
|
||||
Ok(proto::Dispatched::Upgrade(pending)) => {
|
||||
let h1 = match mem::replace(&mut self.inner.conn, None) {
|
||||
Some(ProtoServer::H1(h1)) => h1,
|
||||
Some(ProtoServer::H1(h1, _)) => h1,
|
||||
_ => unreachable!("Upgrade expects h1"),
|
||||
};
|
||||
|
||||
@@ -1072,13 +1147,18 @@ mod upgrades {
|
||||
pending.fulfill(Upgraded::new(io, buf));
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
Err(e) => match *e.kind() {
|
||||
Kind::Parse(Parse::VersionH2) if self.inner.fallback.to_h2() => {
|
||||
self.inner.upgrade_h2();
|
||||
continue;
|
||||
Err(e) => {
|
||||
#[cfg(feature = "http2")]
|
||||
match *e.kind() {
|
||||
Kind::Parse(Parse::VersionH2) if self.inner.fallback.to_h2() => {
|
||||
self.inner.upgrade_h2();
|
||||
continue;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => return Poll::Ready(Err(e)),
|
||||
},
|
||||
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user