Skip to content

Commit

Permalink
feat(http1): Make HTTP/1 support an optional feature
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
seanmonstar committed Nov 17, 2020
1 parent 2f2ceb2 commit 2a19ab7
Show file tree
Hide file tree
Showing 31 changed files with 459 additions and 239 deletions.
7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

[package]
name = "hyper"
version = "0.14.0-dev" # don't forget to update html_root_url
Expand Down Expand Up @@ -76,11 +75,11 @@ default = [
"runtime",
"stream",

#"http1",
"http1",
"http2",
]
full = [
#"http1",
"http1",
"http2",
"stream",
"runtime",
Expand All @@ -97,7 +96,7 @@ tcp = [
]

# HTTP versions
#http1 = []
http1 = []
http2 = ["h2"]

# `impl Stream` for things
Expand Down
22 changes: 19 additions & 3 deletions src/body/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ use std::error::Error as StdError;
use std::fmt;

use bytes::Bytes;
use futures_channel::{mpsc, oneshot};
use futures_channel::mpsc;
#[cfg(any(feature = "http1", feature = "http2"))]
use futures_channel::oneshot;
use futures_core::Stream; // for mpsc::Receiver
#[cfg(feature = "stream")]
use futures_util::TryStreamExt;
use http::HeaderMap;
use http_body::{Body as HttpBody, SizeHint};

use super::DecodedLength;
#[cfg(feature = "stream")]
use crate::common::sync_wrapper::SyncWrapper;
use crate::common::{task, watch, Future, Never, Pin, Poll};
use crate::common::{task, watch, Pin, Poll};
#[cfg(any(feature = "http1", feature = "http2"))]
use crate::common::{Future, Never};
#[cfg(feature = "http2")]
use crate::proto::h2::ping;
use crate::proto::DecodedLength;
use crate::upgrade::OnUpgrade;

type BodySender = mpsc::Sender<Result<Bytes, crate::Error>>;
Expand Down Expand Up @@ -67,14 +71,17 @@ struct Extra {
on_upgrade: OnUpgrade,
}

#[cfg(any(feature = "http1", feature = "http2"))]
type DelayEofUntil = oneshot::Receiver<Never>;

enum DelayEof {
/// Initial state, stream hasn't seen EOF yet.
#[cfg(any(feature = "http1", feature = "http2"))]
NotEof(DelayEofUntil),
/// Transitions to this state once we've seen `poll` try to
/// return EOF (`None`). This future is then polled, and
/// when it completes, the Body finally returns EOF (`None`).
#[cfg(any(feature = "http1", feature = "http2"))]
Eof(DelayEofUntil),
}

Expand Down Expand Up @@ -203,13 +210,15 @@ impl Body {
body
}

#[cfg(feature = "http1")]
pub(crate) fn set_on_upgrade(&mut self, upgrade: OnUpgrade) {
debug_assert!(!upgrade.is_none(), "set_on_upgrade with empty upgrade");
let extra = self.extra_mut();
debug_assert!(extra.on_upgrade.is_none(), "set_on_upgrade twice");
extra.on_upgrade = upgrade;
}

#[cfg(any(feature = "http1", feature = "http2"))]
pub(crate) fn delayed_eof(&mut self, fut: DelayEofUntil) {
self.extra_mut().delayed_eof = Some(DelayEof::NotEof(fut));
}
Expand All @@ -220,6 +229,7 @@ impl Body {
.and_then(|extra| extra.delayed_eof.take())
}

#[cfg(any(feature = "http1", feature = "http2"))]
fn extra_mut(&mut self) -> &mut Extra {
self.extra.get_or_insert_with(|| {
Box::new(Extra {
Expand All @@ -231,6 +241,7 @@ impl Body {

fn poll_eof(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<crate::Result<Bytes>>> {
match self.take_delayed_eof() {
#[cfg(any(feature = "http1", feature = "http2"))]
Some(DelayEof::NotEof(mut delay)) => match self.poll_inner(cx) {
ok @ Poll::Ready(Some(Ok(..))) | ok @ Poll::Pending => {
self.extra_mut().delayed_eof = Some(DelayEof::NotEof(delay));
Expand All @@ -246,6 +257,7 @@ impl Body {
},
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
},
#[cfg(any(feature = "http1", feature = "http2"))]
Some(DelayEof::Eof(mut delay)) => match Pin::new(&mut delay).poll(cx) {
Poll::Ready(Ok(never)) => match never {},
Poll::Pending => {
Expand All @@ -254,6 +266,8 @@ impl Body {
}
Poll::Ready(Err(_done)) => Poll::Ready(None),
},
#[cfg(not(any(feature = "http1", feature = "http2")))]
Some(delay_eof) => match delay_eof {},
None => self.poll_inner(cx),
}
}
Expand Down Expand Up @@ -300,6 +314,7 @@ impl Body {
}
}

#[cfg(feature = "http1")]
pub(super) fn take_full_data(&mut self) -> Option<Bytes> {
if let Kind::Once(ref mut chunk) = self.kind {
chunk.take()
Expand Down Expand Up @@ -549,6 +564,7 @@ impl Sender {
.try_send(Err(crate::Error::new_body_write_aborted()));
}

#[cfg(feature = "http1")]
pub(crate) fn send_error(&mut self, err: crate::Error) {
let _ = self.tx.try_send(Err(err));
}
Expand Down
100 changes: 100 additions & 0 deletions src/body/length.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use std::fmt;

#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) struct DecodedLength(u64);

#[cfg(any(feature = "http1", feature = "http2", test))]
const MAX_LEN: u64 = std::u64::MAX - 2;

impl DecodedLength {
pub(crate) const CLOSE_DELIMITED: DecodedLength = DecodedLength(::std::u64::MAX);
pub(crate) const CHUNKED: DecodedLength = DecodedLength(::std::u64::MAX - 1);
pub(crate) const ZERO: DecodedLength = DecodedLength(0);

#[cfg(test)]
pub(crate) fn new(len: u64) -> Self {
debug_assert!(len <= MAX_LEN);
DecodedLength(len)
}

/// Takes the length as a content-length without other checks.
///
/// Should only be called if previously confirmed this isn't
/// CLOSE_DELIMITED or CHUNKED.
#[inline]
#[cfg(feature = "http1")]
pub(crate) fn danger_len(self) -> u64 {
debug_assert!(self.0 < Self::CHUNKED.0);
self.0
}

/// Converts to an Option<u64> representing a Known or Unknown length.
pub(crate) fn into_opt(self) -> Option<u64> {
match self {
DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => None,
DecodedLength(known) => Some(known),
}
}

/// Checks the `u64` is within the maximum allowed for content-length.
#[cfg(any(feature = "http1", feature = "http2"))]
pub(crate) fn checked_new(len: u64) -> Result<Self, crate::error::Parse> {
if len <= MAX_LEN {
Ok(DecodedLength(len))
} else {
warn!("content-length bigger than maximum: {} > {}", len, MAX_LEN);
Err(crate::error::Parse::TooLarge)
}
}

pub(crate) fn sub_if(&mut self, amt: u64) {
match *self {
DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => (),
DecodedLength(ref mut known) => {
*known -= amt;
}
}
}
}

impl fmt::Debug for DecodedLength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
DecodedLength::CLOSE_DELIMITED => f.write_str("CLOSE_DELIMITED"),
DecodedLength::CHUNKED => f.write_str("CHUNKED"),
DecodedLength(n) => f.debug_tuple("DecodedLength").field(&n).finish(),
}
}
}

impl fmt::Display for DecodedLength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
DecodedLength::CLOSE_DELIMITED => f.write_str("close-delimited"),
DecodedLength::CHUNKED => f.write_str("chunked encoding"),
DecodedLength::ZERO => f.write_str("empty"),
DecodedLength(n) => write!(f, "content-length ({} bytes)", n),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn sub_if_known() {
let mut len = DecodedLength::new(30);
len.sub_if(20);

assert_eq!(len.0, 10);
}

#[test]
fn sub_if_chunked() {
let mut len = DecodedLength::CHUNKED;
len.sub_if(20);

assert_eq!(len, DecodedLength::CHUNKED);
}
}
3 changes: 3 additions & 0 deletions src/body/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@ pub use http_body::Body as HttpBody;

pub use self::aggregate::aggregate;
pub use self::body::{Body, Sender};
pub(crate) use self::length::DecodedLength;
pub use self::to_bytes::to_bytes;

mod aggregate;
mod body;
mod length;
mod to_bytes;

/// An optimization to try to take a full body if immediately available.
///
/// This is currently limited to *only* `hyper::Body`s.
#[cfg(feature = "http1")]
pub(crate) fn take_full_data<T: HttpBody + 'static>(body: &mut T) -> Option<T::Data> {
use std::any::{Any, TypeId};

Expand Down
34 changes: 31 additions & 3 deletions src/cfg.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
macro_rules! cfg_http2 {
macro_rules! cfg_any_http {
($($item:item)*) => {
$(
#[cfg(feature = "http2")]
//#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
#[cfg(any(
feature = "http1",
feature = "http2",
))]
#[cfg_attr(docsrs, doc(cfg(any(
feature = "http1",
feature = "http2",
))))]
$item
)*
}
}

cfg_any_http! {
macro_rules! cfg_http1 {
($($item:item)*) => {
$(
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
$item
)*
}
}

macro_rules! cfg_http2 {
($($item:item)*) => {
$(
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
$item
)*
}
}
}
Loading

0 comments on commit 2a19ab7

Please sign in to comment.