feat(http1): Make HTTP/1 support an optional feature

cc #2251

BREAKING CHANGE: This puts all HTTP/1 methods and support behind an
  `http1` cargo feature, which will not be enabled by default. To use
  HTTP/1, add `features = ["http1"]` to the hyper dependency in your
  `Cargo.toml`.
This commit is contained in:
Sean McArthur
2020-11-16 15:39:10 -08:00
parent 2f2ceb2426
commit 2a19ab74ed
31 changed files with 459 additions and 239 deletions

View File

@@ -11,7 +11,7 @@
//! ## Example
//! A simple example that uses the `Http` struct to talk HTTP over a Tokio TCP stream
//! ```no_run
//! # #[cfg(feature = "runtime")]
//! # #[cfg(all(feature = "http1", feature = "runtime"))]
//! # mod rt {
//! use http::{Request, Response, StatusCode};
//! use hyper::{server::conn::Http, service::service_fn, Body};
@@ -28,7 +28,7 @@
//! tokio::task::spawn(async move {
//! if let Err(http_err) = Http::new()
//! .http1_only(true)
//! .keep_alive(true)
//! .http1_keep_alive(true)
//! .serve_connection(tcp_stream, service_fn(hello))
//! .await {
//! eprintln!("Error while serving HTTP connection: {}", http_err);
@@ -45,8 +45,8 @@
use std::error::Error as StdError;
use std::fmt;
#[cfg(feature = "http1")]
use std::marker::PhantomData;
use std::mem;
#[cfg(feature = "tcp")]
use std::net::SocketAddr;
#[cfg(feature = "runtime")]
@@ -63,10 +63,12 @@ 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 = "http1")]
#[cfg(feature = "http2")]
use crate::error::{Kind, Parse};
use crate::proto;
use crate::service::{HttpService, MakeServiceRef};
#[cfg(feature = "http1")]
use crate::upgrade::Upgraded;
use self::spawn_all::NewSvcTask;
@@ -100,11 +102,13 @@ pub struct Http<E = Exec> {
#[derive(Clone, Debug, PartialEq)]
enum ConnectionMode {
/// Always use HTTP/1 and do not upgrade when a parse error occurs.
#[cfg(feature = "http1")]
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 = "http1")]
#[cfg(feature = "http2")]
Fallback,
}
@@ -157,6 +161,7 @@ where
S: HttpService<Body>,
{
pub(super) conn: Option<ProtoServer<T, S::ResBody, S, E>>,
#[cfg(feature = "http1")]
#[cfg(feature = "http2")]
fallback: Fallback<E>,
}
@@ -167,6 +172,7 @@ where
S: HttpService<Body>,
B: HttpBody,
{
#[cfg(feature = "http1")]
H1(
#[pin]
proto::h1::Dispatcher<
@@ -181,6 +187,7 @@ where
H2(#[pin] proto::h2::Server<Rewind<T>, S, B, E>),
}
#[cfg(feature = "http1")]
#[cfg(feature = "http2")]
#[derive(Clone, Debug)]
enum Fallback<E> {
@@ -188,6 +195,7 @@ enum Fallback<E> {
Http1Only,
}
#[cfg(feature = "http1")]
#[cfg(feature = "http2")]
impl<E> Fallback<E> {
fn to_h2(&self) -> bool {
@@ -198,6 +206,7 @@ impl<E> Fallback<E> {
}
}
#[cfg(feature = "http1")]
#[cfg(feature = "http2")]
impl<E> Unpin for Fallback<E> {}
@@ -247,6 +256,8 @@ impl<E> Http<E> {
/// Sets whether HTTP1 is required.
///
/// Default is false
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn http1_only(&mut self, val: bool) -> &mut Self {
if val {
self.mode = ConnectionMode::H1Only;
@@ -267,6 +278,8 @@ impl<E> Http<E> {
/// detects an EOF in the middle of a request.
///
/// Default is `false`.
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn http1_half_close(&mut self, val: bool) -> &mut Self {
self.h1_half_close = val;
self
@@ -275,18 +288,13 @@ impl<E> Http<E> {
/// Enables or disables HTTP/1 keep-alive.
///
/// Default is true.
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn http1_keep_alive(&mut self, val: bool) -> &mut Self {
self.h1_keep_alive = val;
self
}
// renamed due different semantics of http2 keep alive
#[doc(hidden)]
#[deprecated(note = "renamed to `http1_keep_alive`")]
pub fn keep_alive(&mut self, val: bool) -> &mut Self {
self.http1_keep_alive(val)
}
/// Set whether HTTP/1 connections should try to use vectored writes,
/// or always flatten into a single buffer.
///
@@ -300,6 +308,8 @@ impl<E> Http<E> {
/// 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
@@ -314,7 +324,10 @@ impl<E> Http<E> {
if val {
self.mode = ConnectionMode::H2Only;
} else {
self.mode = ConnectionMode::Fallback;
#[cfg(feature = "http1")]
{
self.mode = ConnectionMode::Fallback;
}
}
self
}
@@ -446,6 +459,8 @@ impl<E> Http<E> {
/// # Panics
///
/// The minimum value allowed is 8192. This method panics if the passed `max` is less than the minimum.
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn max_buf_size(&mut self, max: usize) -> &mut Self {
assert!(
max >= proto::h1::MINIMUM_MAX_BUFFER_SIZE,
@@ -519,6 +534,7 @@ impl<E> Http<E> {
I: AsyncRead + AsyncWrite + Unpin,
E: ConnStreamExec<S::Future, Bd>,
{
#[cfg(feature = "http1")]
macro_rules! h1 {
() => {{
let mut conn = proto::Conn::new(io);
@@ -545,9 +561,11 @@ impl<E> Http<E> {
}
let proto = match self.mode {
#[cfg(feature = "http1")]
#[cfg(not(feature = "http2"))]
ConnectionMode::H1Only => h1!(),
#[cfg(feature = "http2")]
#[cfg(feature = "http1")]
ConnectionMode::H1Only | ConnectionMode::Fallback => h1!(),
#[cfg(feature = "http2")]
ConnectionMode::H2Only => {
@@ -560,6 +578,7 @@ impl<E> Http<E> {
Connection {
conn: Some(proto),
#[cfg(feature = "http1")]
#[cfg(feature = "http2")]
fallback: if self.mode == ConnectionMode::Fallback {
Fallback::ToHttp2(self.h2_builder.clone(), self.exec.clone())
@@ -610,6 +629,7 @@ where
/// nothing.
pub fn graceful_shutdown(self: Pin<&mut Self>) {
match self.project().conn {
#[cfg(feature = "http1")]
Some(ProtoServer::H1(ref mut h1, _)) => {
h1.disable_keep_alive();
}
@@ -640,6 +660,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() {
#[cfg(feature = "http1")]
ProtoServer::H1(h1, _) => {
let (io, read_buf, dispatch) = h1.into_inner();
Some(Parts {
@@ -672,26 +693,26 @@ where
B: Unpin,
{
loop {
let polled = match *self.conn.as_mut().unwrap() {
ProtoServer::H1(ref mut h1, _) => h1.poll_without_shutdown(cx),
match *self.conn.as_mut().unwrap() {
#[cfg(feature = "http1")]
ProtoServer::H1(ref mut h1, _) => match ready!(h1.poll_without_shutdown(cx)) {
Ok(()) => return Poll::Ready(Ok(())),
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));
}
},
#[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) => {
#[cfg(feature = "http2")]
match *e.kind() {
Kind::Parse(Parse::VersionH2) if self.fallback.to_h2() => {
self.upgrade_h2();
continue;
}
_ => (),
}
return Poll::Ready(Err(e));
}
}
}
}
@@ -710,6 +731,7 @@ where
})
}
#[cfg(feature = "http1")]
#[cfg(feature = "http2")]
fn upgrade_h2(&mut self) {
trace!("Trying to upgrade connection to h2");
@@ -759,16 +781,21 @@ where
loop {
match ready!(Pin::new(self.conn.as_mut().unwrap()).poll(cx)) {
Ok(done) => {
if let proto::Dispatched::Upgrade(pending) = done {
// With no `Send` bound on `I`, we can't try to do
// upgrades here. In case a user was trying to use
// `Body::on_upgrade` with this API, send a special
// error letting them know about that.
pending.manual();
}
match done {
proto::Dispatched::Shutdown => {}
#[cfg(feature = "http1")]
proto::Dispatched::Upgrade(pending) => {
// With no `Send` bound on `I`, we can't try to do
// upgrades here. In case a user was trying to use
// `Body::on_upgrade` with this API, send a special
// error letting them know about that.
pending.manual();
}
};
return Poll::Ready(Ok(()));
}
Err(e) => {
#[cfg(feature = "http1")]
#[cfg(feature = "http2")]
match *e.kind() {
Kind::Parse(Parse::VersionH2) if self.fallback.to_h2() => {
@@ -796,18 +823,21 @@ where
// ===== impl ConnectionMode =====
impl ConnectionMode {}
impl Default for ConnectionMode {
#[cfg(feature = "http2")]
#[cfg(all(feature = "http1", feature = "http2"))]
fn default() -> ConnectionMode {
ConnectionMode::Fallback
}
#[cfg(not(feature = "http2"))]
#[cfg(all(feature = "http1", not(feature = "http2")))]
fn default() -> ConnectionMode {
ConnectionMode::H1Only
}
#[cfg(all(not(feature = "http1"), feature = "http2"))]
fn default() -> ConnectionMode {
ConnectionMode::H2Only
}
}
// ===== impl Serve =====
@@ -955,6 +985,7 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
match self.project() {
#[cfg(feature = "http1")]
ProtoServerProj::H1(s, _) => s.poll(cx),
#[cfg(feature = "http2")]
ProtoServerProj::H2(s) => s.poll(cx),
@@ -1137,17 +1168,22 @@ mod upgrades {
loop {
match ready!(Pin::new(self.inner.conn.as_mut().unwrap()).poll(cx)) {
Ok(proto::Dispatched::Shutdown) => return Poll::Ready(Ok(())),
#[cfg(feature = "http1")]
Ok(proto::Dispatched::Upgrade(pending)) => {
let h1 = match mem::replace(&mut self.inner.conn, None) {
Some(ProtoServer::H1(h1, _)) => h1,
_ => unreachable!("Upgrade expects h1"),
match self.inner.conn.take() {
Some(ProtoServer::H1(h1, _)) => {
let (io, buf, _) = h1.into_inner();
pending.fulfill(Upgraded::new(io, buf));
return Poll::Ready(Ok(()));
}
_ => {
drop(pending);
unreachable!("Upgrade expects h1")
}
};
let (io, buf, _) = h1.into_inner();
pending.fulfill(Upgraded::new(io, buf));
return Poll::Ready(Ok(()));
}
Err(e) => {
#[cfg(feature = "http1")]
#[cfg(feature = "http2")]
match *e.kind() {
Kind::Parse(Parse::VersionH2) if self.inner.fallback.to_h2() => {

View File

@@ -241,6 +241,8 @@ impl<I, E> Builder<I, E> {
/// Sets whether to use keep-alive for HTTP/1 connections.
///
/// Default is `true`.
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn http1_keepalive(mut self, val: bool) -> Self {
self.protocol.http1_keep_alive(val);
self
@@ -254,6 +256,8 @@ impl<I, E> Builder<I, E> {
/// detects an EOF in the middle of a request.
///
/// Default is `false`.
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn http1_half_close(mut self, val: bool) -> Self {
self.protocol.http1_half_close(val);
self
@@ -262,6 +266,8 @@ impl<I, E> Builder<I, E> {
/// Set the maximum buffer size.
///
/// Default is ~ 400kb.
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn http1_max_buf_size(mut self, val: usize) -> Self {
self.protocol.max_buf_size(val);
self
@@ -272,6 +278,7 @@ impl<I, E> Builder<I, E> {
// This isn't really desirable in most cases, only really being useful in
// silly pipeline benchmarks.
#[doc(hidden)]
#[cfg(feature = "http1")]
pub fn http1_pipeline_flush(mut self, val: bool) -> Self {
self.protocol.pipeline_flush(val);
self
@@ -290,7 +297,9 @@ impl<I, E> Builder<I, E> {
/// which may eliminate unnecessary cloning on some TLS backends
///
/// Default is `auto`. In this mode hyper will try to guess which
/// mode to use
/// mode to use.
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn http1_writev(mut self, val: bool) -> Self {
self.protocol.http1_writev(val);
self
@@ -299,6 +308,8 @@ impl<I, E> Builder<I, E> {
/// Sets whether HTTP/1 is required.
///
/// Default is `false`.
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn http1_only(mut self, val: bool) -> Self {
self.protocol.http1_only(val);
self