Update tokio and hyper alphas
This commit is contained in:
@@ -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
|
||||||
|
|||||||
26
Cargo.toml
26
Cargo.toml
@@ -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"]
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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(part.value.into_stream())
|
||||||
.chain(hyper::Body::wrap_stream(part.value.into_stream()))
|
.chain(stream::once(future::ready(Ok("\r\n".into()))))
|
||||||
.chain(hyper::Body::from("\r\n".to_owned())),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user