Update tokio and hyper alphas

This commit is contained in:
Sean McArthur
2019-09-25 11:11:03 -07:00
parent f71227d968
commit 6413a4349e
7 changed files with 240 additions and 80 deletions

View File

@@ -19,13 +19,13 @@ matrix:
# rustls-tls # rustls-tls
#- rust: stable #- rust: stable
- rust: nightly #- rust: nightly
env: FEATURES="--no-default-features --features rustls-tls" # env: FEATURES="--no-default-features --features rustls-tls"
# default-tls and rustls-tls # default-tls and rustls-tls
#- rust: stable #- rust: stable
- rust: nightly #- rust: nightly
env: FEATURES="--features rustls-tls" # env: FEATURES="--features rustls-tls"
# optional cookies # optional cookies
#- rust: stable #- rust: stable

View File

@@ -24,13 +24,14 @@ encoding_rs = "0.8"
futures-core-preview = { version = "=0.3.0-alpha.18" } futures-core-preview = { version = "=0.3.0-alpha.18" }
futures-util-preview = { version = "=0.3.0-alpha.18" } futures-util-preview = { version = "=0.3.0-alpha.18" }
http = "0.1.15" http = "0.1.15"
hyper = "=0.13.0-alpha.1" http-body = "=0.2.0-alpha.1"
hyper = "=0.13.0-alpha.2"
log = "0.4" log = "0.4"
mime = "0.3.7" mime = "0.3.7"
mime_guess = "2.0" mime_guess = "2.0"
percent-encoding = "2.1" percent-encoding = "2.1"
tokio = { version = "=0.2.0-alpha.4", default-features = false, features = ["rt-full", "tcp"] } tokio = { version = "=0.2.0-alpha.5", default-features = false, features = ["rt-full", "tcp"] }
tokio-executor = "=0.2.0-alpha.4" tokio-executor = "=0.2.0-alpha.5"
url = "2.1" url = "2.1"
uuid = { version = "0.7", features = ["v4"] } uuid = { version = "0.7", features = ["v4"] }
time = "0.1.42" time = "0.1.42"
@@ -45,15 +46,15 @@ serde_urlencoded = "0.6.1"
# Optional deps... # Optional deps...
## default-tls ## default-tls
hyper-tls = { version = "=0.4.0-alpha.1", optional = true } hyper-tls = { version = "=0.4.0-alpha.2", optional = true }
native-tls = { version = "0.2", optional = true } native-tls = { version = "0.2", optional = true }
tokio-tls = { version = "=0.3.0-alpha.4", optional = true } tokio-tls = { version = "=0.3.0-alpha.5", optional = true }
## rustls-tls ## rustls-tls
hyper-rustls = { version = "=0.18.0-alpha.1", optional = true } #hyper-rustls = { version = "=0.18.0-alpha.1", optional = true }
rustls = { version = "0.16", features = ["dangerous_configuration"], optional = true } #rustls = { version = "0.16", features = ["dangerous_configuration"], optional = true }
tokio-rustls = { version = "=0.12.0-alpha.2", optional = true } #tokio-rustls = { version = "=0.12.0-alpha.2", optional = true }
webpki-roots = { version = "0.17", optional = true } #webpki-roots = { version = "0.17", optional = true }
## blocking ## blocking
futures-channel-preview = { version = "=0.3.0-alpha.18", optional = true } futures-channel-preview = { version = "=0.3.0-alpha.18", optional = true }
@@ -73,11 +74,11 @@ async-compression = { version = "0.1.0-alpha.4", default-features = false, featu
[dev-dependencies] [dev-dependencies]
env_logger = "0.6" env_logger = "0.6"
hyper = { version = "=0.13.0-alpha.2", features = ["unstable-stream"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
libflate = "0.1" libflate = "0.1"
doc-comment = "0.3" doc-comment = "0.3"
bytes = "0.4" tokio-fs = { version = "=0.2.0-alpha.5" }
tokio-fs = { version = "=0.2.0-alpha.4" }
[features] [features]
default = ["default-tls"] default = ["default-tls"]
@@ -87,7 +88,8 @@ tls = []
default-tls = ["hyper-tls", "native-tls", "tls", "tokio-tls"] default-tls = ["hyper-tls", "native-tls", "tls", "tokio-tls"]
default-tls-vendored = ["default-tls", "native-tls/vendored"] default-tls-vendored = ["default-tls", "native-tls/vendored"]
rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "tls"] # re-enable CI also
#rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "tls"]
blocking = ["futures-channel-preview", "futures-util-preview/io"] blocking = ["futures-channel-preview", "futures-util-preview/io"]

View File

@@ -1,10 +1,12 @@
use bytes::Bytes;
use futures_core::Stream;
use hyper::body::Payload;
use std::fmt; use std::fmt;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use bytes::Bytes;
use futures_core::Stream;
use futures_util::TryStreamExt;
use http_body::Body as HttpBody;
use tokio::timer::Delay; use tokio::timer::Delay;
/// An asynchronous request body. /// An asynchronous request body.
@@ -17,12 +19,22 @@ pub(crate) struct ImplStream(Body);
enum Inner { enum Inner {
Reusable(Bytes), Reusable(Bytes),
Hyper { Streaming {
body: hyper::Body, body: Pin<
Box<
dyn HttpBody<Data = hyper::Chunk, Error = Box<dyn std::error::Error + Send + Sync>>
+ Send
+ Sync,
>,
>,
timeout: Option<Delay>, timeout: Option<Delay>,
}, },
} }
struct WrapStream<S>(S);
struct WrapHyper(hyper::Body);
impl Body { impl Body {
/// Wrap a futures `Stream` in a box inside `Body`. /// Wrap a futures `Stream` in a box inside `Body`.
/// ///
@@ -49,27 +61,38 @@ impl Body {
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>, S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
hyper::Chunk: From<S::Ok>, hyper::Chunk: From<S::Ok>,
{ {
Body::wrap(hyper::body::Body::wrap_stream(stream)) let body = Box::pin(WrapStream(
} stream.map_ok(hyper::Chunk::from).map_err(Into::into),
));
pub(crate) fn response(body: hyper::Body, timeout: Option<Delay>) -> Body {
Body { Body {
inner: Inner::Hyper { body, timeout }, inner: Inner::Streaming {
}
}
pub(crate) fn wrap(body: hyper::Body) -> Body {
Body {
inner: Inner::Hyper {
body, body,
timeout: None, timeout: None,
}, },
} }
} }
#[cfg(any(feature = "blocking", feature = "gzip",))] pub(crate) fn response(body: hyper::Body, timeout: Option<Delay>) -> Body {
Body {
inner: Inner::Streaming {
body: Box::pin(WrapHyper(body)),
timeout,
},
}
}
#[cfg(feature = "blocking")]
pub(crate) fn wrap(body: hyper::Body) -> Body {
Body {
inner: Inner::Streaming {
body: Box::pin(WrapHyper(body)),
timeout: None,
},
}
}
pub(crate) fn empty() -> Body { pub(crate) fn empty() -> Body {
Body::wrap(hyper::Body::empty()) Body::reusable(Bytes::new())
} }
pub(crate) fn reusable(chunk: Bytes) -> Body { pub(crate) fn reusable(chunk: Bytes) -> Body {
@@ -78,14 +101,13 @@ impl Body {
} }
} }
pub(crate) fn into_hyper(self) -> (Option<Bytes>, hyper::Body) { pub(crate) fn try_reuse(self) -> (Option<Bytes>, Self) {
match self.inner { let reuse = match self.inner {
Inner::Reusable(chunk) => (Some(chunk.clone()), chunk.into()), Inner::Reusable(ref chunk) => Some(chunk.clone()),
Inner::Hyper { body, timeout } => { Inner::Streaming { .. } => None,
debug_assert!(timeout.is_none()); };
(None, body)
} (reuse, self)
}
} }
pub(crate) fn into_stream(self) -> ImplStream { pub(crate) fn into_stream(self) -> ImplStream {
@@ -95,7 +117,7 @@ impl Body {
pub(crate) fn content_length(&self) -> Option<u64> { pub(crate) fn content_length(&self) -> Option<u64> {
match self.inner { match self.inner {
Inner::Reusable(ref bytes) => Some(bytes.len() as u64), Inner::Reusable(ref bytes) => Some(bytes.len() as u64),
Inner::Hyper { ref body, .. } => body.size_hint().exact(), Inner::Streaming { ref body, .. } => body.size_hint().exact(),
} }
} }
} }
@@ -143,13 +165,71 @@ impl fmt::Debug for Body {
// ===== impl ImplStream ===== // ===== impl ImplStream =====
impl HttpBody for ImplStream {
type Data = hyper::Chunk;
type Error = crate::Error;
fn poll_data(
mut self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
let opt_try_chunk = match self.0.inner {
Inner::Streaming {
ref mut body,
ref mut timeout,
} => {
if let Some(ref mut timeout) = timeout {
if let Poll::Ready(()) = Pin::new(timeout).poll(cx) {
return Poll::Ready(Some(Err(crate::error::body(crate::error::TimedOut))));
}
}
futures_core::ready!(Pin::new(body).poll_data(cx))
.map(|opt_chunk| opt_chunk.map(Into::into).map_err(crate::error::body))
}
Inner::Reusable(ref mut bytes) => {
if bytes.is_empty() {
None
} else {
Some(Ok(std::mem::replace(bytes, Bytes::new()).into()))
}
}
};
Poll::Ready(opt_try_chunk)
}
fn poll_trailers(
self: Pin<&mut Self>,
_cx: &mut Context,
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
Poll::Ready(Ok(None))
}
fn is_end_stream(&self) -> bool {
match self.0.inner {
Inner::Streaming { ref body, .. } => body.is_end_stream(),
Inner::Reusable(ref bytes) => bytes.is_empty(),
}
}
fn size_hint(&self) -> http_body::SizeHint {
match self.0.inner {
Inner::Streaming { ref body, .. } => body.size_hint(),
Inner::Reusable(ref bytes) => {
let mut hint = http_body::SizeHint::default();
hint.set_exact(bytes.len() as u64);
hint
}
}
}
}
impl Stream for ImplStream { impl Stream for ImplStream {
type Item = Result<Bytes, crate::Error>; type Item = Result<Bytes, crate::Error>;
#[inline]
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
let opt_try_chunk = match self.0.inner { let opt_try_chunk = match self.0.inner {
Inner::Hyper { Inner::Streaming {
ref mut body, ref mut body,
ref mut timeout, ref mut timeout,
} => { } => {
@@ -173,3 +253,67 @@ impl Stream for ImplStream {
Poll::Ready(opt_try_chunk) Poll::Ready(opt_try_chunk)
} }
} }
// ===== impl WrapStream =====
impl<S, D, E> HttpBody for WrapStream<S>
where
S: Stream<Item = Result<D, E>>,
D: Into<hyper::Chunk>,
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
type Data = hyper::Chunk;
type Error = E;
fn poll_data(
self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
// safe pin projection
let item =
futures_core::ready!(
unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) }.poll_next(cx)?
);
Poll::Ready(item.map(|val| Ok(val.into())))
}
fn poll_trailers(
self: Pin<&mut Self>,
_cx: &mut Context,
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
Poll::Ready(Ok(None))
}
}
// ===== impl WrapHyper =====
impl HttpBody for WrapHyper {
type Data = hyper::Chunk;
type Error = Box<dyn std::error::Error + Send + Sync>;
fn poll_data(
mut self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
// safe pin projection
Pin::new(&mut self.0)
.poll_data(cx)
.map(|opt| opt.map(|res| res.map_err(Into::into)))
}
fn poll_trailers(
self: Pin<&mut Self>,
_cx: &mut Context,
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
Poll::Ready(Ok(None))
}
fn is_end_stream(&self) -> bool {
self.0.is_end_stream()
}
fn size_hint(&self) -> http_body::SizeHint {
self.0.size_hint()
}
}

View File

@@ -5,11 +5,11 @@ use std::sync::RwLock;
use std::time::Duration; use std::time::Duration;
use std::{fmt, str}; use std::{fmt, str};
use crate::header::{ use bytes::Bytes;
use http::header::{
Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT, CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT,
}; };
use bytes::Bytes;
use http::Uri; use http::Uri;
use hyper::client::ResponseFuture; use hyper::client::ResponseFuture;
use mime; use mime;
@@ -24,6 +24,7 @@ use log::debug;
use super::request::{Request, RequestBuilder}; use super::request::{Request, RequestBuilder};
use super::response::Response; use super::response::Response;
use super::Body;
use crate::connect::Connector; use crate::connect::Connector;
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
use crate::cookie; use crate::cookie;
@@ -472,7 +473,7 @@ impl ClientBuilder {
} }
} }
type HyperClient = hyper::Client<Connector>; type HyperClient = hyper::Client<Connector, super::body::ImplStream>;
impl Client { impl Client {
/// Constructs a new `Client`. /// Constructs a new `Client`.
@@ -612,10 +613,10 @@ impl Client {
let (reusable, body) = match body { let (reusable, body) = match body {
Some(body) => { Some(body) => {
let (reusable, body) = body.into_hyper(); let (reusable, body) = body.try_reuse();
(Some(reusable), body) (Some(reusable), body)
} }
None => (None, hyper::Body::empty()), None => (None, Body::empty()),
}; };
self.proxy_auth(&uri, &mut headers); self.proxy_auth(&uri, &mut headers);
@@ -623,7 +624,7 @@ impl Client {
let mut req = hyper::Request::builder() let mut req = hyper::Request::builder()
.method(method.clone()) .method(method.clone())
.uri(uri.clone()) .uri(uri.clone())
.body(body) .body(body.into_stream())
.expect("valid request parts"); .expect("valid request parts");
*req.headers_mut() = headers.clone(); *req.headers_mut() = headers.clone();
@@ -884,13 +885,13 @@ impl Future for PendingRequest {
debug!("redirecting to {:?} '{}'", self.method, self.url); debug!("redirecting to {:?} '{}'", self.method, self.url);
let uri = expect_uri(&self.url); let uri = expect_uri(&self.url);
let body = match self.body { let body = match self.body {
Some(Some(ref body)) => hyper::Body::from(body.clone()), Some(Some(ref body)) => Body::reusable(body.clone()),
_ => hyper::Body::empty(), _ => Body::empty(),
}; };
let mut req = hyper::Request::builder() let mut req = hyper::Request::builder()
.method(self.method.clone()) .method(self.method.clone())
.uri(uri.clone()) .uri(uri.clone())
.body(body) .body(body.into_stream())
.expect("valid request parts"); .expect("valid request parts");
// Add cookies from the cookie store. // Add cookies from the cookie store.

View File

@@ -1,13 +1,16 @@
//! multipart/form-data //! multipart/form-data
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::pin::Pin;
use bytes::Bytes;
use http::HeaderMap; use http::HeaderMap;
use mime_guess::Mime; use mime_guess::Mime;
use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC}; use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
use uuid::Uuid; use uuid::Uuid;
use futures_util::StreamExt; use futures_core::Stream;
use futures_util::{future, stream, StreamExt};
use super::Body; use super::Body;
@@ -96,50 +99,58 @@ impl Form {
self.with_inner(|inner| inner.percent_encode_noop()) self.with_inner(|inner| inner.percent_encode_noop())
} }
/// Consume this instance and transform into an instance of hyper::Body for use in a request. /// Consume this instance and transform into an instance of Body for use in a request.
pub(crate) fn stream(mut self) -> hyper::Body { pub(crate) fn stream(mut self) -> Body {
if self.inner.fields.is_empty() { if self.inner.fields.is_empty() {
return hyper::Body::empty(); return Body::empty();
} }
// create initial part to init reduce chain // create initial part to init reduce chain
let (name, part) = self.inner.fields.remove(0); let (name, part) = self.inner.fields.remove(0);
let start = self.part_stream(name, part); let start = Box::pin(self.part_stream(name, part))
as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
let fields = self.inner.take_fields(); let fields = self.inner.take_fields();
// for each field, chain an additional stream // for each field, chain an additional stream
let stream = fields.into_iter().fold(start, |memo, (name, part)| { let stream = fields.into_iter().fold(start, |memo, (name, part)| {
let part_stream = self.part_stream(name, part); let part_stream = self.part_stream(name, part);
hyper::Body::wrap_stream(memo.chain(part_stream)) Box::pin(memo.chain(part_stream))
as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>
}); });
// append special ending boundary // append special ending boundary
let last = hyper::Body::from(format!("--{}--\r\n", self.boundary())); let last = stream::once(future::ready(Ok(
hyper::Body::wrap_stream(stream.chain(last)) format!("--{}--\r\n", self.boundary()).into()
)));
Body::wrap_stream(stream.chain(last))
} }
/// Generate a hyper::Body stream for a single Part instance of a Form request. /// Generate a hyper::Body stream for a single Part instance of a Form request.
pub(crate) fn part_stream<T>(&mut self, name: T, part: Part) -> hyper::Body pub(crate) fn part_stream<T>(
&mut self,
name: T,
part: Part,
) -> impl Stream<Item = Result<Bytes, crate::Error>>
where where
T: Into<Cow<'static, str>>, T: Into<Cow<'static, str>>,
{ {
// start with boundary // start with boundary
let boundary = hyper::Body::from(format!("--{}\r\n", self.boundary())); let boundary = stream::once(future::ready(Ok(
format!("--{}\r\n", self.boundary()).into()
)));
// append headers // append headers
let header = hyper::Body::from({ let header = stream::once(future::ready(Ok({
let mut h = self let mut h = self
.inner .inner
.percent_encoding .percent_encoding
.encode_headers(&name.into(), &part.meta); .encode_headers(&name.into(), &part.meta);
h.extend_from_slice(b"\r\n\r\n"); h.extend_from_slice(b"\r\n\r\n");
h h.into()
}); })));
// then append form data followed by terminating CRLF // then append form data followed by terminating CRLF
hyper::Body::wrap_stream(
boundary boundary
.chain(header) .chain(header)
.chain(hyper::Body::wrap_stream(part.value.into_stream())) .chain(part.value.into_stream())
.chain(hyper::Body::from("\r\n".to_owned())), .chain(stream::once(future::ready(Ok("\r\n".into()))))
)
} }
pub(crate) fn compute_length(&mut self) -> Option<u64> { pub(crate) fn compute_length(&mut self) -> Option<u64> {
@@ -482,8 +493,8 @@ mod tests {
let form = Form::new(); let form = Form::new();
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
let body = form.stream(); let body = form.stream().into_stream();
let s = body.map(|try_c| try_c.map(|c| c.into_bytes())).try_concat(); let s = body.map(|try_c| try_c.map(Bytes::from)).try_concat();
let out = rt.block_on(s); let out = rt.block_on(s);
assert_eq!(out.unwrap(), Vec::new()); assert_eq!(out.unwrap(), Vec::new());
@@ -530,8 +541,8 @@ mod tests {
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n"; value3\r\n--boundary--\r\n";
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
let body = form.stream(); let body = form.stream().into_stream();
let s = body.map(|try_c| try_c.map(|c| c.into_bytes())).try_concat(); let s = body.map(|try_c| try_c.map(Bytes::from)).try_concat();
let out = rt.block_on(s).unwrap(); let out = rt.block_on(s).unwrap();
// These prints are for debug purposes in case the test fails // These prints are for debug purposes in case the test fails
@@ -557,8 +568,8 @@ mod tests {
value2\r\n\ value2\r\n\
--boundary--\r\n"; --boundary--\r\n";
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
let body = form.stream(); let body = form.stream().into_stream();
let s = body.map(|try_c| try_c.map(|c| c.into_bytes())).try_concat(); let s = body.map(|try_c| try_c.map(Bytes::from)).try_concat();
let out = rt.block_on(s).unwrap(); let out = rt.block_on(s).unwrap();
// These prints are for debug purposes in case the test fails // These prints are for debug purposes in case the test fails

View File

@@ -219,7 +219,7 @@ impl RequestBuilder {
}; };
if let Ok(ref mut req) = builder.request { if let Ok(ref mut req) = builder.request {
*req.body_mut() = Some(Body::wrap(multipart.stream())) *req.body_mut() = Some(multipart.stream())
} }
builder builder
} }

View File

@@ -80,12 +80,14 @@ async fn test_redirect_307_and_308_tries_to_get_again() {
#[tokio::test] #[tokio::test]
async fn test_redirect_307_and_308_tries_to_post_again() { async fn test_redirect_307_and_308_tries_to_post_again() {
let _ = env_logger::try_init();
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let codes = [307u16, 308]; let codes = [307u16, 308];
for &code in codes.iter() { for &code in codes.iter() {
let redirect = server::http(move |mut req| { let redirect = server::http(move |mut req| {
async move { async move {
assert_eq!(req.method(), "POST"); assert_eq!(req.method(), "POST");
assert_eq!(req.headers()["content-length"], "5");
let data = req.body_mut().next().await.unwrap().unwrap(); let data = req.body_mut().next().await.unwrap().unwrap();
assert_eq!(&*data, b"Hello"); assert_eq!(&*data, b"Hello");