diff --git a/Cargo.toml b/Cargo.toml index 07640a5e82..0c4b8de00a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,3 @@ - [package] name = "hyper" version = "0.14.0-dev" # don't forget to update html_root_url @@ -76,11 +75,11 @@ default = [ "runtime", "stream", - #"http1", + "http1", "http2", ] full = [ - #"http1", + "http1", "http2", "stream", "runtime", @@ -97,7 +96,7 @@ tcp = [ ] # HTTP versions -#http1 = [] +http1 = [] http2 = ["h2"] # `impl Stream` for things diff --git a/src/body/body.rs b/src/body/body.rs index 05c309298c..5583ba9839 100644 --- a/src/body/body.rs +++ b/src/body/body.rs @@ -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>; @@ -67,14 +71,17 @@ struct Extra { on_upgrade: OnUpgrade, } +#[cfg(any(feature = "http1", feature = "http2"))] type DelayEofUntil = oneshot::Receiver; 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), } @@ -203,6 +210,7 @@ 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(); @@ -210,6 +218,7 @@ impl Body { 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)); } @@ -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 { @@ -231,6 +241,7 @@ impl Body { fn poll_eof(&mut self, cx: &mut task::Context<'_>) -> Poll>> { 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)); @@ -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 => { @@ -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), } } @@ -300,6 +314,7 @@ impl Body { } } + #[cfg(feature = "http1")] pub(super) fn take_full_data(&mut self) -> Option { if let Kind::Once(ref mut chunk) = self.kind { chunk.take() @@ -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)); } diff --git a/src/body/length.rs b/src/body/length.rs new file mode 100644 index 0000000000..aa9cf3dcd5 --- /dev/null +++ b/src/body/length.rs @@ -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 representing a Known or Unknown length. + pub(crate) fn into_opt(self) -> Option { + 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 { + 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); + } +} diff --git a/src/body/mod.rs b/src/body/mod.rs index ce704f5bd2..e9a5352c13 100644 --- a/src/body/mod.rs +++ b/src/body/mod.rs @@ -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(body: &mut T) -> Option { use std::any::{Any, TypeId}; diff --git a/src/cfg.rs b/src/cfg.rs index c2f1e4fef0..86d211d067 100644 --- a/src/cfg.rs +++ b/src/cfg.rs @@ -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 + )* + } + } +} diff --git a/src/client/conn.rs b/src/client/conn.rs index 02cfc4d486..1420781e6d 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -10,7 +10,8 @@ use std::error::Error as StdError; use std::fmt; -use std::mem; +#[cfg(feature = "http2")] +use std::marker::PhantomData; use std::sync::Arc; #[cfg(feature = "runtime")] #[cfg(feature = "http2")] @@ -24,11 +25,14 @@ use tower_service::Service; use super::dispatch; use crate::body::HttpBody; -use crate::common::{task, BoxSendFuture, Exec, Executor, Future, Pin, Poll}; +use crate::common::{task, BoxSendFuture, Exec, Future, Pin, Poll}; use crate::proto; +use crate::rt::Executor; +#[cfg(feature = "http1")] use crate::upgrade::Upgraded; use crate::{Body, Request, Response}; +#[cfg(feature = "http1")] type Http1Dispatcher = proto::dispatch::Dispatcher, B, T, R>; #[pin_project(project = ProtoClientProj)] @@ -36,9 +40,10 @@ enum ProtoClient where B: HttpBody, { + #[cfg(feature = "http1")] H1(#[pin] Http1Dispatcher), #[cfg(feature = "http2")] - H2(#[pin] proto::h2::ClientTask), + H2(#[pin] proto::h2::ClientTask, PhantomData), } /// Returns a handshake future over some IO. @@ -88,6 +93,7 @@ pub struct Builder { #[derive(Clone, Debug)] enum Proto { + #[cfg(feature = "http1")] Http1, #[cfg(feature = "http2")] Http2, @@ -353,18 +359,20 @@ where /// /// Only works for HTTP/1 connections. HTTP/2 connections will panic. pub fn into_parts(self) -> Parts { - let (io, read_buf, _) = match self.inner.expect("already upgraded") { - ProtoClient::H1(h1) => h1.into_inner(), + match self.inner.expect("already upgraded") { + #[cfg(feature = "http1")] + ProtoClient::H1(h1) => { + let (io, read_buf, _) = h1.into_inner(); + Parts { + io, + read_buf, + _inner: (), + } + } #[cfg(feature = "http2")] - ProtoClient::H2(_h2) => { + ProtoClient::H2(..) => { panic!("http2 cannot into_inner"); } - }; - - Parts { - io, - read_buf, - _inner: (), } } @@ -381,9 +389,10 @@ where /// to work with this function; or use the `without_shutdown` wrapper. pub fn poll_without_shutdown(&mut self, cx: &mut task::Context<'_>) -> Poll> { match *self.inner.as_mut().expect("already upgraded") { + #[cfg(feature = "http1")] 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(|_| ()), + ProtoClient::H2(ref mut h2, _) => Pin::new(h2).poll(cx).map_ok(|_| ()), } } @@ -410,16 +419,18 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { match ready!(Pin::new(self.inner.as_mut().unwrap()).poll(cx))? { proto::Dispatched::Shutdown => Poll::Ready(Ok(())), - proto::Dispatched::Upgrade(pending) => { - let h1 = match mem::replace(&mut self.inner, None) { - Some(ProtoClient::H1(h1)) => h1, - _ => unreachable!("Upgrade expects h1"), - }; - - let (io, buf, _) = h1.into_inner(); - pending.fulfill(Upgraded::new(io, buf)); - Poll::Ready(Ok(())) - } + #[cfg(feature = "http1")] + proto::Dispatched::Upgrade(pending) => match self.inner.take() { + Some(ProtoClient::H1(h1)) => { + let (io, buf, _) = h1.into_inner(); + pending.fulfill(Upgraded::new(io, buf)); + Poll::Ready(Ok(())) + } + _ => { + drop(pending); + unreachable!("Upgrade expects h1"); + } + }, } } } @@ -448,7 +459,10 @@ impl Builder { h1_max_buf_size: None, #[cfg(feature = "http2")] h2_builder: Default::default(), + #[cfg(feature = "http1")] version: Proto::Http1, + #[cfg(not(feature = "http1"))] + version: Proto::Http2, } } @@ -477,6 +491,7 @@ impl Builder { self } + #[cfg(feature = "http1")] pub(super) fn h1_max_buf_size(&mut self, max: usize) -> &mut Self { assert!( max >= proto::h1::MINIMUM_MAX_BUFFER_SIZE, @@ -494,7 +509,9 @@ impl Builder { #[cfg(feature = "http2")] #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_only(&mut self, enabled: bool) -> &mut Builder { - self.version = if enabled { Proto::Http2 } else { Proto::Http1 }; + if enabled { + self.version = Proto::Http2 + } self } @@ -643,6 +660,7 @@ impl Builder { let (tx, rx) = dispatch::channel(); let proto = match opts.version { + #[cfg(feature = "http1")] Proto::Http1 => { let mut conn = proto::Conn::new(io); if let Some(writev) = opts.h1_writev { @@ -670,7 +688,7 @@ impl Builder { let h2 = proto::h2::client::handshake(io, rx, &opts.h2_builder, opts.exec.clone()) .await?; - ProtoClient::H2(h2) + ProtoClient::H2(h2, PhantomData) } }; @@ -723,9 +741,10 @@ where fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { match self.project() { + #[cfg(feature = "http1")] ProtoClientProj::H1(c) => c.poll(cx), #[cfg(feature = "http2")] - ProtoClientProj::H2(c) => c.poll(cx), + ProtoClientProj::H2(c, _) => c.poll(cx), } } } diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index 4397ded215..08c1e01f42 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -162,11 +162,13 @@ impl Receiver { } } + #[cfg(feature = "http1")] pub(crate) fn close(&mut self) { self.taker.cancel(); self.inner.close(); } + #[cfg(feature = "http1")] pub(crate) fn try_recv(&mut self) -> Option<(T, Callback)> { match self.inner.try_recv() { Ok(mut env) => env.0.take(), diff --git a/src/client/mod.rs b/src/client/mod.rs index a9067065d8..a227b44683 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -62,7 +62,8 @@ use http::{Method, Request, Response, Uri, Version}; use self::connect::{sealed::Connect, Alpn, Connected, Connection}; use self::pool::{Key as PoolKey, Pool, Poolable, Pooled, Reservation}; use crate::body::{Body, HttpBody}; -use crate::common::{lazy as hyper_lazy, task, BoxSendFuture, Executor, Future, Lazy, Pin, Poll}; +use crate::common::{lazy as hyper_lazy, task, BoxSendFuture, Future, Lazy, Pin, Poll}; +use crate::rt::Executor; #[cfg(feature = "tcp")] pub use self::connect::HttpConnector; @@ -1022,6 +1023,8 @@ impl Builder { /// # 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 http1_max_buf_size(&mut self, max: usize) -> &mut Self { self.conn_builder.h1_max_buf_size(max); self diff --git a/src/common/buf.rs b/src/common/buf.rs index 8f71b7bbad..a882cc0f42 100644 --- a/src/common/buf.rs +++ b/src/common/buf.rs @@ -21,6 +21,7 @@ impl BufList { } #[inline] + #[cfg(feature = "http1")] pub(crate) fn bufs_cnt(&self) -> usize { self.bufs.len() } diff --git a/src/proto/h1/date.rs b/src/common/date.rs similarity index 98% rename from src/proto/h1/date.rs rename to src/common/date.rs index 7cc680bc0b..e8f9f7026b 100644 --- a/src/proto/h1/date.rs +++ b/src/common/date.rs @@ -10,12 +10,14 @@ use httpdate::HttpDate; // "Sun, 06 Nov 1994 08:49:37 GMT".len() pub const DATE_VALUE_LENGTH: usize = 29; +#[cfg(feature = "http1")] pub fn extend(dst: &mut Vec) { CACHED.with(|cache| { dst.extend_from_slice(cache.borrow().buffer()); }) } +#[cfg(feature = "http1")] pub fn update() { CACHED.with(|cache| { cache.borrow_mut().check(); diff --git a/src/common/exec.rs b/src/common/exec.rs index 57a93d0659..a6324834c8 100644 --- a/src/common/exec.rs +++ b/src/common/exec.rs @@ -6,15 +6,10 @@ use std::sync::Arc; use crate::body::{Body, HttpBody}; #[cfg(feature = "http2")] use crate::proto::h2::server::H2Stream; +use crate::rt::Executor; use crate::server::conn::spawn_all::{NewSvcTask, Watcher}; use crate::service::HttpService; -/// An executor of futures. -pub trait Executor { - /// Place the future into the executor to be run. - fn execute(&self, fut: Fut); -} - pub trait ConnStreamExec: Clone { fn execute_h2stream(&mut self, fut: H2Stream); } diff --git a/src/common/io/rewind.rs b/src/common/io/rewind.rs index a69c73208b..6897b334e2 100644 --- a/src/common/io/rewind.rs +++ b/src/common/io/rewind.rs @@ -29,7 +29,7 @@ impl Rewind { } } - #[cfg(any(feature = "http2", test))] + #[cfg(any(all(feature = "http1", feature = "http2"), test))] pub(crate) fn rewind(&mut self, bs: Bytes) { debug_assert!(self.pre.is_none()); self.pre = Some(bs); diff --git a/src/common/mod.rs b/src/common/mod.rs index edd61acf87..e1ac726185 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -8,9 +8,14 @@ macro_rules! ready { } pub(crate) mod buf; +#[cfg(any(feature = "http1", feature = "http2"))] +pub(crate) mod date; +#[cfg(any(feature = "http1", feature = "http2"))] pub(crate) mod drain; +#[cfg(any(feature = "http1", feature = "http2"))] pub(crate) mod exec; pub(crate) mod io; +#[cfg(any(feature = "http1", feature = "http2"))] mod lazy; mod never; #[cfg(feature = "stream")] @@ -18,11 +23,14 @@ pub(crate) mod sync_wrapper; pub(crate) mod task; pub(crate) mod watch; -pub use self::exec::Executor; +#[cfg(any(feature = "http1", feature = "http2"))] pub(crate) use self::exec::{BoxSendFuture, Exec}; +#[cfg(any(feature = "http1", feature = "http2"))] pub(crate) use self::lazy::{lazy, Started as Lazy}; pub use self::never::Never; pub(crate) use self::task::Poll; // group up types normally needed for `Future` -pub(crate) use std::{future::Future, marker::Unpin, pin::Pin}; +#[cfg(any(feature = "http1", feature = "http2"))] +pub(crate) use std::marker::Unpin; +pub(crate) use std::{future::Future, pin::Pin}; diff --git a/src/common/task.rs b/src/common/task.rs index bfccfe3bfe..ec70c957d6 100644 --- a/src/common/task.rs +++ b/src/common/task.rs @@ -1,9 +1,11 @@ +#[cfg(feature = "http1")] use super::Never; pub(crate) use std::task::{Context, Poll}; /// A function to help "yield" a future, such that it is re-scheduled immediately. /// /// Useful for spin counts, so a future doesn't hog too much time. +#[cfg(feature = "http1")] pub(crate) fn yield_now(cx: &mut Context<'_>) -> Poll { cx.waker().wake_by_ref(); Poll::Pending diff --git a/src/error.rs b/src/error.rs index 005888d8b8..3bcf2eac2a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,6 @@ //! Error and Result module. use std::error::Error as StdError; use std::fmt; -use std::io; /// Result type often returned from methods that can have hyper `Error`s. pub type Result = std::result::Result; @@ -25,27 +24,33 @@ pub(crate) enum Kind { /// A message reached EOF, but is not complete. IncompleteMessage, /// A connection received a message (or bytes) when not waiting for one. + #[cfg(feature = "http1")] UnexpectedMessage, /// A pending item was dropped before ever being processed. Canceled, /// Indicates a channel (client or body sender) is closed. ChannelClosed, /// An `io::Error` that occurred while trying to read or write to a network stream. + #[cfg(any(feature = "http1", feature = "http2"))] Io, /// Error occurred while connecting. Connect, /// Error creating a TcpListener. - #[cfg(feature = "tcp")] + #[cfg(all(any(feature = "http1", feature = "http2"), feature = "tcp"))] Listen, /// Error accepting on an Incoming stream. + #[cfg(any(feature = "http1", feature = "http2"))] Accept, /// Error while reading a body from connection. + #[cfg(any(feature = "http1", feature = "http2"))] Body, /// Error while writing a body to connection. + #[cfg(any(feature = "http1", feature = "http2"))] BodyWrite, /// The body write was aborted. BodyWriteAborted, /// Error calling AsyncWrite::shutdown() + #[cfg(feature = "http1")] Shutdown, /// A general error from h2. @@ -57,6 +62,7 @@ pub(crate) enum Kind { pub(crate) enum Parse { Method, Version, + #[cfg(feature = "http1")] VersionH2, Uri, Header, @@ -67,28 +73,37 @@ pub(crate) enum Parse { #[derive(Debug, PartialEq)] pub(crate) enum User { /// Error calling user's HttpBody::poll_data(). + #[cfg(any(feature = "http1", feature = "http2"))] Body, /// Error calling user's MakeService. + #[cfg(any(feature = "http1", feature = "http2"))] MakeService, /// Error from future of user's Service. + #[cfg(any(feature = "http1", feature = "http2"))] Service, /// User tried to send a certain header in an unexpected context. /// /// For example, sending both `content-length` and `transfer-encoding`. + #[cfg(feature = "http1")] UnexpectedHeader, /// User tried to create a Request with bad version. + #[cfg(any(feature = "http1", feature = "http2"))] UnsupportedVersion, /// User tried to create a CONNECT Request with the Client. + #[cfg(any(feature = "http1", feature = "http2"))] UnsupportedRequestMethod, /// User tried to respond with a 1xx (not 101) response code. + #[cfg(feature = "http1")] UnsupportedStatusCode, /// User tried to send a Request with Client with non-absolute URI. + #[cfg(any(feature = "http1", feature = "http2"))] AbsoluteUriRequired, /// User tried polling for an upgrade that doesn't exist. NoUpgrade, /// User polled for an upgrade, but low-level API is not using upgrades. + #[cfg(feature = "http1")] ManualUpgrade, } @@ -159,6 +174,7 @@ impl Error { self } + #[cfg(feature = "http1")] pub(crate) fn kind(&self) -> &Kind { &self.inner.kind } @@ -189,35 +205,42 @@ impl Error { Error::new(Kind::Canceled) } + #[cfg(feature = "http1")] pub(crate) fn new_incomplete() -> Error { Error::new(Kind::IncompleteMessage) } + #[cfg(feature = "http1")] pub(crate) fn new_too_large() -> Error { Error::new(Kind::Parse(Parse::TooLarge)) } + #[cfg(feature = "http1")] pub(crate) fn new_version_h2() -> Error { Error::new(Kind::Parse(Parse::VersionH2)) } + #[cfg(feature = "http1")] pub(crate) fn new_unexpected_message() -> Error { Error::new(Kind::UnexpectedMessage) } - pub(crate) fn new_io(cause: io::Error) -> Error { + #[cfg(feature = "http1")] + pub(crate) fn new_io(cause: std::io::Error) -> Error { Error::new(Kind::Io).with(cause) } - #[cfg(feature = "tcp")] + #[cfg(all(any(feature = "http1", feature = "http2"), feature = "tcp"))] pub(crate) fn new_listen>(cause: E) -> Error { Error::new(Kind::Listen).with(cause) } + #[cfg(any(feature = "http1", feature = "http2"))] pub(crate) fn new_accept>(cause: E) -> Error { Error::new(Kind::Accept).with(cause) } + #[cfg(any(feature = "http1", feature = "http2"))] pub(crate) fn new_connect>(cause: E) -> Error { Error::new(Kind::Connect).with(cause) } @@ -226,10 +249,12 @@ impl Error { Error::new(Kind::ChannelClosed) } + #[cfg(any(feature = "http1", feature = "http2"))] pub(crate) fn new_body>(cause: E) -> Error { Error::new(Kind::Body).with(cause) } + #[cfg(any(feature = "http1", feature = "http2"))] pub(crate) fn new_body_write>(cause: E) -> Error { Error::new(Kind::BodyWrite).with(cause) } @@ -242,22 +267,27 @@ impl Error { Error::new(Kind::User(user)) } + #[cfg(feature = "http1")] pub(crate) fn new_user_header() -> Error { Error::new_user(User::UnexpectedHeader) } + #[cfg(any(feature = "http1", feature = "http2"))] pub(crate) fn new_user_unsupported_version() -> Error { Error::new_user(User::UnsupportedVersion) } + #[cfg(any(feature = "http1", feature = "http2"))] pub(crate) fn new_user_unsupported_request_method() -> Error { Error::new_user(User::UnsupportedRequestMethod) } + #[cfg(feature = "http1")] pub(crate) fn new_user_unsupported_status_code() -> Error { Error::new_user(User::UnsupportedStatusCode) } + #[cfg(feature = "http1")] pub(crate) fn new_user_absolute_uri_required() -> Error { Error::new_user(User::AbsoluteUriRequired) } @@ -266,23 +296,28 @@ impl Error { Error::new_user(User::NoUpgrade) } + #[cfg(feature = "http1")] pub(crate) fn new_user_manual_upgrade() -> Error { Error::new_user(User::ManualUpgrade) } + #[cfg(any(feature = "http1", feature = "http2"))] pub(crate) fn new_user_make_service>(cause: E) -> Error { Error::new_user(User::MakeService).with(cause) } + #[cfg(any(feature = "http1", feature = "http2"))] pub(crate) fn new_user_service>(cause: E) -> Error { Error::new_user(User::Service).with(cause) } + #[cfg(any(feature = "http1", feature = "http2"))] pub(crate) fn new_user_body>(cause: E) -> Error { Error::new_user(User::Body).with(cause) } - pub(crate) fn new_shutdown(cause: io::Error) -> Error { + #[cfg(feature = "http1")] + pub(crate) fn new_shutdown(cause: std::io::Error) -> Error { Error::new(Kind::Shutdown).with(cause) } @@ -299,38 +334,54 @@ impl Error { match self.inner.kind { Kind::Parse(Parse::Method) => "invalid HTTP method parsed", Kind::Parse(Parse::Version) => "invalid HTTP version parsed", + #[cfg(feature = "http1")] Kind::Parse(Parse::VersionH2) => "invalid HTTP version parsed (found HTTP2 preface)", Kind::Parse(Parse::Uri) => "invalid URI", Kind::Parse(Parse::Header) => "invalid HTTP header parsed", Kind::Parse(Parse::TooLarge) => "message head is too large", Kind::Parse(Parse::Status) => "invalid HTTP status-code parsed", Kind::IncompleteMessage => "connection closed before message completed", + #[cfg(feature = "http1")] Kind::UnexpectedMessage => "received unexpected message from connection", Kind::ChannelClosed => "channel closed", Kind::Connect => "error trying to connect", Kind::Canceled => "operation was canceled", - #[cfg(feature = "tcp")] + #[cfg(all(any(feature = "http1", feature = "http2"), feature = "tcp"))] Kind::Listen => "error creating server listener", + #[cfg(any(feature = "http1", feature = "http2"))] Kind::Accept => "error accepting connection", + #[cfg(any(feature = "http1", feature = "http2"))] Kind::Body => "error reading a body from connection", + #[cfg(any(feature = "http1", feature = "http2"))] Kind::BodyWrite => "error writing a body to connection", Kind::BodyWriteAborted => "body write aborted", + #[cfg(feature = "http1")] Kind::Shutdown => "error shutting down connection", #[cfg(feature = "http2")] Kind::Http2 => "http2 error", + #[cfg(any(feature = "http1", feature = "http2"))] Kind::Io => "connection error", + #[cfg(any(feature = "http1", feature = "http2"))] Kind::User(User::Body) => "error from user's HttpBody stream", + #[cfg(any(feature = "http1", feature = "http2"))] Kind::User(User::MakeService) => "error from user's MakeService", + #[cfg(any(feature = "http1", feature = "http2"))] Kind::User(User::Service) => "error from user's Service", + #[cfg(feature = "http1")] Kind::User(User::UnexpectedHeader) => "user sent unexpected header", + #[cfg(any(feature = "http1", feature = "http2"))] Kind::User(User::UnsupportedVersion) => "request has unsupported HTTP version", + #[cfg(any(feature = "http1", feature = "http2"))] Kind::User(User::UnsupportedRequestMethod) => "request has unsupported HTTP method", + #[cfg(feature = "http1")] Kind::User(User::UnsupportedStatusCode) => { "response has 1xx status code, not supported by server" } + #[cfg(any(feature = "http1", feature = "http2"))] Kind::User(User::AbsoluteUriRequired) => "client requires absolute-form URIs", Kind::User(User::NoUpgrade) => "no upgrade available", + #[cfg(feature = "http1")] Kind::User(User::ManualUpgrade) => "upgrade expected but low level API in use", } } diff --git a/src/headers.rs b/src/headers.rs index 743dd434a3..f1663b972a 100644 --- a/src/headers.rs +++ b/src/headers.rs @@ -1,18 +1,22 @@ +#[cfg(feature = "http1")] use bytes::BytesMut; -use http::header::{HeaderValue, OccupiedEntry, ValueIter}; -use http::header::{CONTENT_LENGTH, TRANSFER_ENCODING}; +use http::header::CONTENT_LENGTH; +use http::header::{HeaderValue, ValueIter}; #[cfg(feature = "http2")] use http::method::Method; use http::HeaderMap; +#[cfg(feature = "http1")] pub fn connection_keep_alive(value: &HeaderValue) -> bool { connection_has(value, "keep-alive") } +#[cfg(feature = "http1")] pub fn connection_close(value: &HeaderValue) -> bool { connection_has(value, "close") } +#[cfg(feature = "http1")] fn connection_has(value: &HeaderValue, needle: &str) -> bool { if let Ok(s) = value.to_str() { for val in s.split(',') { @@ -24,6 +28,7 @@ fn connection_has(value: &HeaderValue, needle: &str) -> bool { false } +#[cfg(feature = "http1")] pub fn content_length_parse(value: &HeaderValue) -> Option { value.to_str().ok().and_then(|s| s.parse().ok()) } @@ -74,10 +79,12 @@ pub fn set_content_length_if_missing(headers: &mut HeaderMap, len: u64) { .or_insert_with(|| HeaderValue::from(len)); } +#[cfg(feature = "http1")] pub fn transfer_encoding_is_chunked(headers: &HeaderMap) -> bool { - is_chunked(headers.get_all(TRANSFER_ENCODING).into_iter()) + is_chunked(headers.get_all(http::header::TRANSFER_ENCODING).into_iter()) } +#[cfg(feature = "http1")] pub fn is_chunked(mut encodings: ValueIter<'_, HeaderValue>) -> bool { // chunked must always be the last encoding, according to spec if let Some(line) = encodings.next_back() { @@ -87,6 +94,7 @@ pub fn is_chunked(mut encodings: ValueIter<'_, HeaderValue>) -> bool { false } +#[cfg(feature = "http1")] pub fn is_chunked_(value: &HeaderValue) -> bool { // chunked must always be the last encoding, according to spec if let Ok(s) = value.to_str() { @@ -98,7 +106,8 @@ pub fn is_chunked_(value: &HeaderValue) -> bool { false } -pub fn add_chunked(mut entry: OccupiedEntry<'_, HeaderValue>) { +#[cfg(feature = "http1")] +pub fn add_chunked(mut entry: http::header::OccupiedEntry<'_, HeaderValue>) { const CHUNKED: &str = "chunked"; if let Some(line) = entry.iter_mut().next_back() { diff --git a/src/lib.rs b/src/lib.rs index a9fa5c9ea5..35c04bf12f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ #[doc(hidden)] pub use http; +#[cfg(any(feature = "http1", feature = "http2"))] #[macro_use] extern crate tracing; @@ -51,23 +52,28 @@ extern crate test; pub use http::{header, HeaderMap, Method, Request, Response, StatusCode, Uri, Version}; pub use crate::body::Body; -pub use crate::client::Client; pub use crate::error::{Error, Result}; -pub use crate::server::Server; #[macro_use] mod cfg; #[macro_use] mod common; pub mod body; -pub mod client; #[doc(hidden)] // Mistakenly public... pub mod error; -mod headers; #[cfg(test)] mod mock; -mod proto; +#[cfg(any(feature = "http1", feature = "http2",))] pub mod rt; -pub mod server; pub mod service; pub mod upgrade; + +cfg_any_http! { + mod headers; + mod proto; + pub mod client; + pub mod server; + + pub use crate::client::Client; + pub use crate::server::Server; +} diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index c80b971a35..f24aa0a507 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -9,10 +9,10 @@ use tokio::io::{AsyncRead, AsyncWrite}; use super::io::Buffered; use super::{Decoder, Encode, EncodedBuf, Encoder, Http1Transaction, ParseContext, Wants}; +use crate::body::DecodedLength; use crate::common::{task, Pin, Poll, Unpin}; use crate::headers::connection_keep_alive; -use crate::proto::{BodyLength, DecodedLength, MessageHead}; -use crate::Result; +use crate::proto::{BodyLength, MessageHead}; const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; @@ -589,7 +589,7 @@ where self.state.writing = state; } - pub fn end_body(&mut self) -> Result<()> { + pub fn end_body(&mut self) -> crate::Result<()> { debug_assert!(self.can_write_body()); let mut res = Ok(()); diff --git a/src/proto/h1/dispatch.rs b/src/proto/h1/dispatch.rs index 9bef39c6de..2f85ce7fa4 100644 --- a/src/proto/h1/dispatch.rs +++ b/src/proto/h1/dispatch.rs @@ -5,10 +5,10 @@ use http::{Request, Response, StatusCode}; use tokio::io::{AsyncRead, AsyncWrite}; use super::{Http1Transaction, Wants}; -use crate::body::{Body, HttpBody}; +use crate::body::{Body, DecodedLength, HttpBody}; use crate::common::{task, Future, Never, Pin, Poll, Unpin}; use crate::proto::{ - BodyLength, Conn, DecodedLength, Dispatched, MessageHead, RequestHead, RequestLine, + BodyLength, Conn, Dispatched, MessageHead, RequestHead, RequestLine, ResponseHead, }; use crate::service::HttpService; diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 067ed67343..d3d84d0a87 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -2,13 +2,14 @@ use std::cell::Cell; use std::cmp; use std::fmt; use std::io::{self, IoSlice}; +use std::marker::Unpin; use bytes::{Buf, BufMut, Bytes, BytesMut}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use super::{Http1Transaction, ParseContext, ParsedMessage}; use crate::common::buf::BufList; -use crate::common::{task, Pin, Poll, Unpin}; +use crate::common::{task, Pin, Poll}; /// The initial buffer size allocated before trying to read from IO. pub(crate) const INIT_BUFFER_SIZE: usize = 8192; diff --git a/src/proto/h1/mod.rs b/src/proto/h1/mod.rs index 2d0bf39bc9..5653d950b1 100644 --- a/src/proto/h1/mod.rs +++ b/src/proto/h1/mod.rs @@ -1,7 +1,8 @@ use bytes::BytesMut; use http::{HeaderMap, Method}; -use crate::proto::{BodyLength, DecodedLength, MessageHead}; +use crate::body::DecodedLength; +use crate::proto::{BodyLength, MessageHead}; pub(crate) use self::conn::Conn; pub use self::decode::Decoder; @@ -11,7 +12,6 @@ pub use self::io::Cursor; //TODO: move out of h1::io pub use self::io::MINIMUM_MAX_BUFFER_SIZE; mod conn; -pub(super) mod date; mod decode; pub(crate) mod dispatch; mod encode; diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index f188efa5a5..bedd61aaa4 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -9,12 +9,14 @@ use bytes::BytesMut; use http::header::{self, Entry, HeaderName, HeaderValue}; use http::{HeaderMap, Method, StatusCode, Version}; +use crate::body::DecodedLength; +use crate::common::date; use crate::error::Parse; use crate::headers; use crate::proto::h1::{ - date, Encode, Encoder, Http1Transaction, ParseContext, ParseResult, ParsedMessage, + Encode, Encoder, Http1Transaction, ParseContext, ParseResult, ParsedMessage, }; -use crate::proto::{BodyLength, DecodedLength, MessageHead, RequestHead, RequestLine}; +use crate::proto::{BodyLength, MessageHead, RequestHead, RequestLine}; const MAX_HEADERS: usize = 100; const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific diff --git a/src/proto/h2/mod.rs b/src/proto/h2/mod.rs index 9fb8435edb..bc1b128400 100644 --- a/src/proto/h2/mod.rs +++ b/src/proto/h2/mod.rs @@ -9,8 +9,7 @@ use pin_project::pin_project; use std::error::Error as StdError; use std::io::IoSlice; -use super::DecodedLength; -use crate::body::HttpBody; +use crate::body::{DecodedLength, HttpBody}; use crate::common::{task, Future, Pin, Poll}; use crate::headers::content_length_parse_all; diff --git a/src/proto/h2/server.rs b/src/proto/h2/server.rs index 7284e898a6..200ad9f8a5 100644 --- a/src/proto/h2/server.rs +++ b/src/proto/h2/server.rs @@ -11,7 +11,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; use super::{decode_content_length, ping, PipeToSendStream, SendBuf}; use crate::body::HttpBody; use crate::common::exec::ConnStreamExec; -use crate::common::{task, Future, Pin, Poll}; +use crate::common::{date, task, Future, Pin, Poll}; use crate::headers; use crate::proto::Dispatched; use crate::service::HttpService; @@ -400,7 +400,7 @@ where // set Date header if it isn't already set... res.headers_mut() .entry(::http::header::DATE) - .or_insert_with(crate::proto::h1::date::update_and_header_value); + .or_insert_with(date::update_and_header_value); // automatically set Content-Length from body... if let Some(len) = body.size_hint().exact() { diff --git a/src/proto/mod.rs b/src/proto/mod.rs index e13307fd20..df55baee22 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -1,10 +1,11 @@ //! Pieces pertaining to the HTTP message protocol. -use http::{HeaderMap, Method, StatusCode, Uri, Version}; -pub(crate) use self::body_length::DecodedLength; -pub(crate) use self::h1::{dispatch, Conn, ServerTransaction}; +cfg_http1! { + pub(crate) mod h1; + + pub(crate) use self::h1::{dispatch, Conn, ServerTransaction}; +} -pub(crate) mod h1; cfg_http2! { pub(crate) mod h2; } @@ -13,23 +14,27 @@ cfg_http2! { #[derive(Clone, Debug, Default, PartialEq)] pub struct MessageHead { /// HTTP version of the message. - pub version: Version, + pub version: http::Version, /// Subject (request line or status line) of Incoming message. pub subject: S, /// Headers of the Incoming message. - pub headers: HeaderMap, + pub headers: http::HeaderMap, } /// An incoming request message. +#[cfg(feature = "http1")] pub type RequestHead = MessageHead; #[derive(Debug, Default, PartialEq)] -pub struct RequestLine(pub Method, pub Uri); +#[cfg(feature = "http1")] +pub struct RequestLine(pub http::Method, pub http::Uri); /// An incoming response message. -pub type ResponseHead = MessageHead; +#[cfg(feature = "http1")] +pub type ResponseHead = MessageHead; #[derive(Debug)] +#[cfg(feature = "http1")] pub enum BodyLength { /// Content-Length Known(u64), @@ -42,106 +47,6 @@ pub(crate) enum Dispatched { /// Dispatcher completely shutdown connection. Shutdown, /// Dispatcher has pending upgrade, and so did not shutdown. + #[cfg(feature = "http1")] Upgrade(crate::upgrade::Pending), } - -/// A separate module to encapsulate the invariants of the DecodedLength type. -mod body_length { - use std::fmt; - - #[derive(Clone, Copy, PartialEq, Eq)] - pub(crate) struct DecodedLength(u64); - - 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] - pub(crate) fn danger_len(self) -> u64 { - debug_assert!(self.0 < Self::CHUNKED.0); - self.0 - } - - /// Converts to an Option representing a Known or Unknown length. - pub(crate) fn into_opt(self) -> Option { - match self { - DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => None, - DecodedLength(known) => Some(known), - } - } - - /// Checks the `u64` is within the maximum allowed for content-length. - pub(crate) fn checked_new(len: u64) -> Result { - 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); - } - } -} diff --git a/src/rt.rs b/src/rt.rs index 4e60139a87..2614b59112 100644 --- a/src/rt.rs +++ b/src/rt.rs @@ -5,4 +5,8 @@ //! If the `runtime` feature is disabled, the types in this module can be used //! to plug in other runtimes. -pub use crate::common::Executor; +/// An executor of futures. +pub trait Executor { + /// Place the future into the executor to be run. + fn execute(&self, fut: Fut); +} diff --git a/src/server/conn.rs b/src/server/conn.rs index 08bd54137a..c54b8751e7 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -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 { #[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, { pub(super) conn: Option>, + #[cfg(feature = "http1")] #[cfg(feature = "http2")] fallback: Fallback, } @@ -167,6 +172,7 @@ where S: HttpService, B: HttpBody, { + #[cfg(feature = "http1")] H1( #[pin] proto::h1::Dispatcher< @@ -181,6 +187,7 @@ where H2(#[pin] proto::h2::Server, S, B, E>), } +#[cfg(feature = "http1")] #[cfg(feature = "http2")] #[derive(Clone, Debug)] enum Fallback { @@ -188,6 +195,7 @@ enum Fallback { Http1Only, } +#[cfg(feature = "http1")] #[cfg(feature = "http2")] impl Fallback { fn to_h2(&self) -> bool { @@ -198,6 +206,7 @@ impl Fallback { } } +#[cfg(feature = "http1")] #[cfg(feature = "http2")] impl Unpin for Fallback {} @@ -247,6 +256,8 @@ impl Http { /// 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 Http { /// 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 Http { /// 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 Http { /// 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 Http { if val { self.mode = ConnectionMode::H2Only; } else { - self.mode = ConnectionMode::Fallback; + #[cfg(feature = "http1")] + { + self.mode = ConnectionMode::Fallback; + } } self } @@ -446,6 +459,8 @@ impl Http { /// # 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 Http { I: AsyncRead + AsyncWrite + Unpin, E: ConnStreamExec, { + #[cfg(feature = "http1")] macro_rules! h1 { () => {{ let mut conn = proto::Conn::new(io); @@ -545,9 +561,11 @@ impl Http { } 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 Http { 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> { 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 { 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() => { diff --git a/src/server/mod.rs b/src/server/mod.rs index c850133289..0541fe3dda 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -241,6 +241,8 @@ impl Builder { /// 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 Builder { /// 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 Builder { /// 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 Builder { // 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 Builder { /// 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 Builder { /// 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 diff --git a/src/service/mod.rs b/src/service/mod.rs index 6310a10fa3..137aecea52 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -39,11 +39,14 @@ pub use tower_service::Service; mod http; mod make; +#[cfg(any(feature = "http1", feature = "http2"))] mod oneshot; mod util; pub(crate) use self::http::HttpService; +#[cfg(any(feature = "http1", feature = "http2"))] pub(crate) use self::make::{MakeConnection, MakeServiceRef}; +#[cfg(any(feature = "http1", feature = "http2"))] pub(crate) use self::oneshot::{oneshot, Oneshot}; pub use self::make::make_service_fn; diff --git a/src/upgrade.rs b/src/upgrade.rs index f2bfe346cc..7e51bf22ce 100644 --- a/src/upgrade.rs +++ b/src/upgrade.rs @@ -57,6 +57,7 @@ pub struct Parts { _inner: (), } +#[cfg(feature = "http1")] pub(crate) struct Pending { tx: oneshot::Sender>, } @@ -68,6 +69,7 @@ pub(crate) struct Pending { #[derive(Debug)] struct UpgradeExpected(()); +#[cfg(feature = "http1")] pub(crate) fn pending() -> (Pending, OnUpgrade) { let (tx, rx) = oneshot::channel(); (Pending { tx }, OnUpgrade { rx: Some(rx) }) @@ -76,6 +78,7 @@ pub(crate) fn pending() -> (Pending, OnUpgrade) { // ===== impl Upgraded ===== impl Upgraded { + #[cfg(any(feature = "http1", test))] pub(crate) fn new(io: T, read_buf: Bytes) -> Self where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -145,6 +148,7 @@ impl OnUpgrade { OnUpgrade { rx: None } } + #[cfg(feature = "http1")] pub(crate) fn is_none(&self) -> bool { self.rx.is_none() } @@ -175,6 +179,7 @@ impl fmt::Debug for OnUpgrade { // ===== impl Pending ===== +#[cfg(feature = "http1")] impl Pending { pub(crate) fn fulfill(self, upgraded: Upgraded) { trace!("pending upgrade fulfill"); diff --git a/tests/server.rs b/tests/server.rs index 0b7bd46a41..5ecd21a826 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -1078,6 +1078,7 @@ async fn nonempty_parse_eof_returns_error() { .expect_err("partial parse eof is error"); } +#[cfg(feature = "http1")] #[tokio::test] async fn http1_allow_half_close() { let _ = pretty_env_logger::try_init(); @@ -1111,6 +1112,7 @@ async fn http1_allow_half_close() { t1.join().expect("client thread"); } +#[cfg(feature = "http1")] #[tokio::test] async fn disconnect_after_reading_request_before_responding() { let _ = pretty_env_logger::try_init(); @@ -1524,18 +1526,22 @@ async fn illegal_request_length_returns_400_response() { .expect_err("illegal Content-Length should error"); } +#[cfg(feature = "http1")] #[test] #[should_panic] fn max_buf_size_panic_too_small() { const MAX: usize = 8191; Http::new().max_buf_size(MAX); } + +#[cfg(feature = "http1")] #[test] fn max_buf_size_no_panic() { const MAX: usize = 8193; Http::new().max_buf_size(MAX); } +#[cfg(feature = "http1")] #[tokio::test] async fn max_buf_size() { let _ = pretty_env_logger::try_init(); @@ -2277,7 +2283,7 @@ impl ServeOptions { fn serve(self) -> Serve { let _ = pretty_env_logger::try_init(); - let options = self; + let _options = self; let (addr_tx, addr_rx) = mpsc::channel(); let (msg_tx, msg_rx) = mpsc::channel(); @@ -2306,11 +2312,15 @@ impl ServeOptions { }) }); - let server = Server::bind(&addr) - .http1_only(options.http1_only) - .http1_keepalive(options.keep_alive) - .http1_pipeline_flush(options.pipeline) - .serve(service); + let builder = Server::bind(&addr); + + #[cfg(feature = "http1")] + let builder = builder + .http1_only(_options.http1_only) + .http1_keepalive(_options.keep_alive) + .http1_pipeline_flush(_options.pipeline); + + let server = builder.serve(service); addr_tx.send(server.local_addr()).expect("server addr tx");