committed by
Sean McArthur
parent
81e0f1ff2a
commit
cf8944a0f0
@@ -1,7 +1,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use futures::{Future, Stream, Poll, Async, try_ready};
|
||||
use bytes::{Buf, Bytes};
|
||||
use futures::{try_ready, Async, Future, Poll, Stream};
|
||||
use hyper::body::Payload;
|
||||
use tokio::timer::Delay;
|
||||
|
||||
@@ -15,7 +15,7 @@ enum Inner {
|
||||
Hyper {
|
||||
body: hyper::Body,
|
||||
timeout: Option<Delay>,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
impl Body {
|
||||
@@ -29,10 +29,7 @@ impl Body {
|
||||
#[inline]
|
||||
pub(crate) fn response(body: hyper::Body, timeout: Option<Delay>) -> Body {
|
||||
Body {
|
||||
inner: Inner::Hyper {
|
||||
body,
|
||||
timeout,
|
||||
},
|
||||
inner: Inner::Hyper { body, timeout },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +62,7 @@ impl Body {
|
||||
Inner::Hyper { body, timeout } => {
|
||||
debug_assert!(timeout.is_none());
|
||||
(None, body)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,14 +74,17 @@ impl Stream for Body {
|
||||
#[inline]
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
let opt = match self.inner {
|
||||
Inner::Hyper { ref mut body, ref mut timeout } => {
|
||||
Inner::Hyper {
|
||||
ref mut body,
|
||||
ref mut timeout,
|
||||
} => {
|
||||
if let Some(ref mut timeout) = timeout {
|
||||
if let Async::Ready(()) = try_!(timeout.poll()) {
|
||||
return Err(crate::error::timedout(None));
|
||||
}
|
||||
}
|
||||
try_ready!(body.poll_data().map_err(crate::error::from))
|
||||
},
|
||||
}
|
||||
Inner::Reusable(ref mut bytes) => {
|
||||
return if bytes.is_empty() {
|
||||
Ok(Async::Ready(None))
|
||||
@@ -93,12 +93,10 @@ impl Stream for Body {
|
||||
*bytes = Bytes::new();
|
||||
Ok(Async::Ready(Some(chunk)))
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Async::Ready(opt.map(|chunk| Chunk {
|
||||
inner: chunk,
|
||||
})))
|
||||
Ok(Async::Ready(opt.map(|chunk| Chunk { inner: chunk })))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +159,7 @@ impl Chunk {
|
||||
#[inline]
|
||||
pub(crate) fn from_chunk(chunk: Bytes) -> Chunk {
|
||||
Chunk {
|
||||
inner: hyper::Chunk::from(chunk)
|
||||
inner: hyper::Chunk::from(chunk),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +195,9 @@ impl std::ops::Deref for Chunk {
|
||||
|
||||
impl Extend<u8> for Chunk {
|
||||
fn extend<T>(&mut self, iter: T)
|
||||
where T: IntoIterator<Item=u8> {
|
||||
where
|
||||
T: IntoIterator<Item = u8>,
|
||||
{
|
||||
self.inner.extend(iter)
|
||||
}
|
||||
}
|
||||
@@ -219,7 +219,9 @@ impl From<Vec<u8>> for Chunk {
|
||||
|
||||
impl From<&'static [u8]> for Chunk {
|
||||
fn from(slice: &'static [u8]) -> Chunk {
|
||||
Chunk { inner: slice.into() }
|
||||
Chunk {
|
||||
inner: slice.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,13 +233,17 @@ impl From<String> for Chunk {
|
||||
|
||||
impl From<&'static str> for Chunk {
|
||||
fn from(slice: &'static str) -> Chunk {
|
||||
Chunk { inner: slice.into() }
|
||||
Chunk {
|
||||
inner: slice.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bytes> for Chunk {
|
||||
fn from(bytes: Bytes) -> Chunk {
|
||||
Chunk { inner: bytes.into() }
|
||||
Chunk {
|
||||
inner: bytes.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,8 +255,7 @@ impl From<Chunk> for hyper::Chunk {
|
||||
|
||||
impl fmt::Debug for Body {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Body")
|
||||
.finish()
|
||||
f.debug_struct("Body").finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
use std::{fmt, str};
|
||||
use std::net::IpAddr;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
use std::net::IpAddr;
|
||||
use std::{fmt, str};
|
||||
|
||||
use crate::header::{
|
||||
Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
|
||||
CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{Async, Future, Poll};
|
||||
use crate::header::{
|
||||
Entry,
|
||||
HeaderMap,
|
||||
HeaderValue,
|
||||
ACCEPT,
|
||||
ACCEPT_ENCODING,
|
||||
CONTENT_LENGTH,
|
||||
CONTENT_ENCODING,
|
||||
CONTENT_TYPE,
|
||||
LOCATION,
|
||||
PROXY_AUTHORIZATION,
|
||||
RANGE,
|
||||
REFERER,
|
||||
TRANSFER_ENCODING,
|
||||
USER_AGENT,
|
||||
};
|
||||
use http::Uri;
|
||||
use hyper::client::ResponseFuture;
|
||||
use mime;
|
||||
@@ -28,24 +16,22 @@ use mime;
|
||||
use native_tls::TlsConnector;
|
||||
use tokio::{clock, timer::Delay};
|
||||
|
||||
use log::{debug};
|
||||
|
||||
use log::debug;
|
||||
|
||||
use super::request::{Request, RequestBuilder};
|
||||
use super::response::Response;
|
||||
use crate::connect::Connector;
|
||||
use crate::into_url::{expect_uri, try_uri};
|
||||
use crate::cookie;
|
||||
use crate::redirect::{self, RedirectPolicy, remove_sensitive_headers};
|
||||
use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
|
||||
use crate::into_url::{expect_uri, try_uri};
|
||||
use crate::proxy::get_proxies;
|
||||
#[cfg(feature = "tls")]
|
||||
use crate::{Certificate, Identity};
|
||||
use crate::redirect::{self, remove_sensitive_headers, RedirectPolicy};
|
||||
#[cfg(feature = "tls")]
|
||||
use crate::tls::TlsBackend;
|
||||
#[cfg(feature = "tls")]
|
||||
use crate::{Certificate, Identity};
|
||||
use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
|
||||
|
||||
static DEFAULT_USER_AGENT: &str =
|
||||
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||
static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
/// An asynchronous `Client` to make Requests with.
|
||||
///
|
||||
@@ -98,7 +84,10 @@ impl ClientBuilder {
|
||||
pub fn new() -> ClientBuilder {
|
||||
let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2);
|
||||
headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_USER_AGENT));
|
||||
headers.insert(ACCEPT, HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime"));
|
||||
headers.insert(
|
||||
ACCEPT,
|
||||
HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime"),
|
||||
);
|
||||
|
||||
ClientBuilder {
|
||||
config: Config {
|
||||
@@ -156,8 +145,13 @@ impl ClientBuilder {
|
||||
id.add_to_native_tls(&mut tls)?;
|
||||
}
|
||||
|
||||
Connector::new_default_tls(tls, proxies.clone(), config.local_address, config.nodelay)?
|
||||
},
|
||||
Connector::new_default_tls(
|
||||
tls,
|
||||
proxies.clone(),
|
||||
config.local_address,
|
||||
config.nodelay,
|
||||
)?
|
||||
}
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
TlsBackend::Rustls => {
|
||||
use crate::tls::NoVerifier;
|
||||
@@ -166,15 +160,14 @@ impl ClientBuilder {
|
||||
if config.http2_only {
|
||||
tls.set_protocols(&["h2".into()]);
|
||||
} else {
|
||||
tls.set_protocols(&[
|
||||
"h2".into(),
|
||||
"http/1.1".into(),
|
||||
]);
|
||||
tls.set_protocols(&["h2".into(), "http/1.1".into()]);
|
||||
}
|
||||
tls.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||
tls.root_store
|
||||
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||
|
||||
if !config.certs_verification {
|
||||
tls.dangerous().set_certificate_verifier(Arc::new(NoVerifier));
|
||||
tls.dangerous()
|
||||
.set_certificate_verifier(Arc::new(NoVerifier));
|
||||
}
|
||||
|
||||
for cert in config.root_certs {
|
||||
@@ -185,7 +178,12 @@ impl ClientBuilder {
|
||||
id.add_to_rustls(&mut tls)?;
|
||||
}
|
||||
|
||||
Connector::new_rustls_tls(tls, proxies.clone(), config.local_address, config.nodelay)?
|
||||
Connector::new_rustls_tls(
|
||||
tls,
|
||||
proxies.clone(),
|
||||
config.local_address,
|
||||
config.nodelay,
|
||||
)?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,9 +206,7 @@ impl ClientBuilder {
|
||||
|
||||
let hyper_client = builder.build(connector);
|
||||
|
||||
let proxies_maybe_http_auth = proxies
|
||||
.iter()
|
||||
.any(|p| p.maybe_has_http_auth());
|
||||
let proxies_maybe_http_auth = proxies.iter().any(|p| p.maybe_has_http_auth());
|
||||
|
||||
let cookie_store = config.cookie_store.map(RwLock::new);
|
||||
|
||||
@@ -277,7 +273,10 @@ impl ClientBuilder {
|
||||
/// site will be trusted for use from any other. This introduces a
|
||||
/// significant vulnerability to man-in-the-middle attacks.
|
||||
#[cfg(feature = "default-tls")]
|
||||
pub fn danger_accept_invalid_hostnames(mut self, accept_invalid_hostname: bool) -> ClientBuilder {
|
||||
pub fn danger_accept_invalid_hostnames(
|
||||
mut self,
|
||||
accept_invalid_hostname: bool,
|
||||
) -> ClientBuilder {
|
||||
self.config.hostname_verification = !accept_invalid_hostname;
|
||||
self
|
||||
}
|
||||
@@ -299,7 +298,6 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
/// Sets the default headers for every request.
|
||||
pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
|
||||
for (key, value) in headers.iter() {
|
||||
@@ -349,7 +347,6 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
/// Set a `RedirectPolicy` for this client.
|
||||
///
|
||||
/// Default will follow redirects up to a maximum of 10.
|
||||
@@ -454,9 +451,7 @@ impl Client {
|
||||
/// Use `Client::builder()` if you wish to handle the failure as an `Error`
|
||||
/// instead of panicking.
|
||||
pub fn new() -> Client {
|
||||
ClientBuilder::new()
|
||||
.build()
|
||||
.expect("Client::new()")
|
||||
ClientBuilder::new().build().expect("Client::new()")
|
||||
}
|
||||
|
||||
/// Creates a `ClientBuilder` to configure a `Client`.
|
||||
@@ -529,9 +524,7 @@ impl Client {
|
||||
///
|
||||
/// This method fails whenever supplied `Url` cannot be parsed.
|
||||
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
|
||||
let req = url
|
||||
.into_url()
|
||||
.map(move |url| Request::new(method, url));
|
||||
let req = url.into_url().map(move |url| Request::new(method, url));
|
||||
RequestBuilder::new(self.clone(), req)
|
||||
}
|
||||
|
||||
@@ -551,14 +544,8 @@ impl Client {
|
||||
self.execute_request(request)
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn execute_request(&self, req: Request) -> Pending {
|
||||
let (
|
||||
method,
|
||||
url,
|
||||
mut headers,
|
||||
body
|
||||
) = req.pieces();
|
||||
let (method, url, mut headers, body) = req.pieces();
|
||||
|
||||
// insert default headers in the request headers
|
||||
// without overwriting already appended headers.
|
||||
@@ -576,9 +563,8 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
if self.inner.gzip &&
|
||||
!headers.contains_key(ACCEPT_ENCODING) &&
|
||||
!headers.contains_key(RANGE) {
|
||||
if self.inner.gzip && !headers.contains_key(ACCEPT_ENCODING) && !headers.contains_key(RANGE)
|
||||
{
|
||||
headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip"));
|
||||
}
|
||||
|
||||
@@ -588,10 +574,8 @@ impl Client {
|
||||
Some(body) => {
|
||||
let (reusable, body) = body.into_hyper();
|
||||
(Some(reusable), body)
|
||||
},
|
||||
None => {
|
||||
(None, hyper::Body::empty())
|
||||
}
|
||||
None => (None, hyper::Body::empty()),
|
||||
};
|
||||
|
||||
self.proxy_auth(&uri, &mut headers);
|
||||
@@ -606,9 +590,10 @@ impl Client {
|
||||
|
||||
let in_flight = self.inner.hyper.request(req);
|
||||
|
||||
let timeout = self.inner.request_timeout.map(|dur| {
|
||||
Delay::new(clock::now() + dur)
|
||||
});
|
||||
let timeout = self
|
||||
.inner
|
||||
.request_timeout
|
||||
.map(|dur| Delay::new(clock::now() + dur));
|
||||
|
||||
Pending {
|
||||
inner: PendingInner::Request(PendingRequest {
|
||||
@@ -643,14 +628,10 @@ impl Client {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for proxy in self.inner.proxies.iter() {
|
||||
if proxy.is_match(dst) {
|
||||
if let Some(header) = proxy.http_basic_auth(dst) {
|
||||
headers.insert(
|
||||
PROXY_AUTHORIZATION,
|
||||
header,
|
||||
);
|
||||
headers.insert(PROXY_AUTHORIZATION, header);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -671,8 +652,7 @@ impl fmt::Debug for Client {
|
||||
|
||||
impl fmt::Debug for ClientBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("ClientBuilder")
|
||||
.finish()
|
||||
f.debug_struct("ClientBuilder").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,7 +706,9 @@ impl Future for Pending {
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.inner {
|
||||
PendingInner::Request(ref mut req) => req.poll(),
|
||||
PendingInner::Error(ref mut err) => Err(err.take().expect("Pending error polled more than once")),
|
||||
PendingInner::Error(ref mut err) => {
|
||||
Err(err.take().expect("Pending error polled more than once"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,56 +737,58 @@ impl Future for PendingRequest {
|
||||
store.0.store_response_cookies(cookies, &self.url);
|
||||
}
|
||||
let should_redirect = match res.status() {
|
||||
StatusCode::MOVED_PERMANENTLY |
|
||||
StatusCode::FOUND |
|
||||
StatusCode::SEE_OTHER => {
|
||||
StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => {
|
||||
self.body = None;
|
||||
for header in &[TRANSFER_ENCODING, CONTENT_ENCODING, CONTENT_TYPE, CONTENT_LENGTH] {
|
||||
for header in &[
|
||||
TRANSFER_ENCODING,
|
||||
CONTENT_ENCODING,
|
||||
CONTENT_TYPE,
|
||||
CONTENT_LENGTH,
|
||||
] {
|
||||
self.headers.remove(header);
|
||||
}
|
||||
|
||||
match self.method {
|
||||
Method::GET | Method::HEAD => {},
|
||||
Method::GET | Method::HEAD => {}
|
||||
_ => {
|
||||
self.method = Method::GET;
|
||||
}
|
||||
}
|
||||
true
|
||||
},
|
||||
StatusCode::TEMPORARY_REDIRECT |
|
||||
StatusCode::PERMANENT_REDIRECT => match self.body {
|
||||
Some(Some(_)) | None => true,
|
||||
Some(None) => false,
|
||||
},
|
||||
}
|
||||
StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {
|
||||
match self.body {
|
||||
Some(Some(_)) | None => true,
|
||||
Some(None) => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if should_redirect {
|
||||
let loc = res.headers()
|
||||
.get(LOCATION)
|
||||
.and_then(|val| {
|
||||
let loc = (|| -> Option<Url> {
|
||||
// Some sites may send a utf-8 Location header,
|
||||
// even though we're supposed to treat those bytes
|
||||
// as opaque, we'll check specifically for utf8.
|
||||
self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok()
|
||||
})();
|
||||
let loc = res.headers().get(LOCATION).and_then(|val| {
|
||||
let loc = (|| -> Option<Url> {
|
||||
// Some sites may send a utf-8 Location header,
|
||||
// even though we're supposed to treat those bytes
|
||||
// as opaque, we'll check specifically for utf8.
|
||||
self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok()
|
||||
})();
|
||||
|
||||
// Check that the `url` is also a valid `http::Uri`.
|
||||
//
|
||||
// If not, just log it and skip the redirect.
|
||||
let loc = loc.and_then(|url| {
|
||||
if try_uri(&url).is_some() {
|
||||
Some(url)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if loc.is_none() {
|
||||
debug!("Location header had invalid URI: {:?}", val);
|
||||
// Check that the `url` is also a valid `http::Uri`.
|
||||
//
|
||||
// If not, just log it and skip the redirect.
|
||||
let loc = loc.and_then(|url| {
|
||||
if try_uri(&url).is_some() {
|
||||
Some(url)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
loc
|
||||
});
|
||||
|
||||
if loc.is_none() {
|
||||
debug!("Location header had invalid URI: {:?}", val);
|
||||
}
|
||||
loc
|
||||
});
|
||||
if let Some(loc) = loc {
|
||||
if self.client.referer {
|
||||
if let Some(referer) = make_referer(&loc, &self.url) {
|
||||
@@ -812,11 +796,10 @@ impl Future for PendingRequest {
|
||||
}
|
||||
}
|
||||
self.urls.push(self.url.clone());
|
||||
let action = self.client.redirect_policy.check(
|
||||
res.status(),
|
||||
&loc,
|
||||
&self.urls,
|
||||
);
|
||||
let action = self
|
||||
.client
|
||||
.redirect_policy
|
||||
.check(res.status(), &loc, &self.urls);
|
||||
|
||||
match action {
|
||||
redirect::Action::Follow => {
|
||||
@@ -844,13 +827,13 @@ impl Future for PendingRequest {
|
||||
*req.headers_mut() = self.headers.clone();
|
||||
self.in_flight = self.client.hyper.request(req);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
redirect::Action::Stop => {
|
||||
debug!("redirect_policy disallowed redirection to '{}'", loc);
|
||||
},
|
||||
}
|
||||
redirect::Action::LoopDetected => {
|
||||
return Err(crate::error::loop_detected(self.url.clone()));
|
||||
},
|
||||
}
|
||||
redirect::Action::TooManyRedirects => {
|
||||
return Err(crate::error::too_many_redirects(self.url.clone()));
|
||||
}
|
||||
@@ -866,17 +849,12 @@ impl Future for PendingRequest {
|
||||
impl fmt::Debug for Pending {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.inner {
|
||||
PendingInner::Request(ref req) => {
|
||||
f.debug_struct("Pending")
|
||||
.field("method", &req.method)
|
||||
.field("url", &req.url)
|
||||
.finish()
|
||||
},
|
||||
PendingInner::Error(ref err) => {
|
||||
f.debug_struct("Pending")
|
||||
.field("error", err)
|
||||
.finish()
|
||||
}
|
||||
PendingInner::Request(ref req) => f
|
||||
.debug_struct("Pending")
|
||||
.field("method", &req.method)
|
||||
.field("url", &req.url)
|
||||
.finish(),
|
||||
PendingInner::Error(ref err) => f.debug_struct("Pending").field("error", err).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -903,7 +881,7 @@ fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &cookie::CookieStore
|
||||
if !header.is_empty() {
|
||||
headers.insert(
|
||||
crate::header::COOKIE,
|
||||
HeaderValue::from_bytes(header.as_bytes()).unwrap()
|
||||
HeaderValue::from_bytes(header.as_bytes()).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,18 +20,18 @@ The following types directly support the gzip compression case:
|
||||
- `Pending` is a non-blocking constructor for a `Decoder` in case the body needs to be checked for EOF
|
||||
*/
|
||||
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::io::{self, Read};
|
||||
use std::mem;
|
||||
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use flate2::read::GzDecoder;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use hyper::{HeaderMap};
|
||||
use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
|
||||
use hyper::HeaderMap;
|
||||
|
||||
use log::{warn};
|
||||
use log::warn;
|
||||
|
||||
use super::{Body, Chunk};
|
||||
use crate::error;
|
||||
@@ -42,7 +42,7 @@ const INIT_BUFFER_SIZE: usize = 8192;
|
||||
///
|
||||
/// The inner decoder may be constructed asynchronously.
|
||||
pub struct Decoder {
|
||||
inner: Inner
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
enum Inner {
|
||||
@@ -51,7 +51,7 @@ enum Inner {
|
||||
/// A `Gzip` decoder will uncompress the gzipped response content before returning it.
|
||||
Gzip(Gzip),
|
||||
/// A decoder that doesn't have a value yet.
|
||||
Pending(Pending)
|
||||
Pending(Pending),
|
||||
}
|
||||
|
||||
/// A future attempt to poll the response body for EOF so we know whether to use gzip or not.
|
||||
@@ -68,8 +68,7 @@ struct Gzip {
|
||||
|
||||
impl fmt::Debug for Decoder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Decoder")
|
||||
.finish()
|
||||
f.debug_struct("Decoder").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +79,7 @@ impl Decoder {
|
||||
#[inline]
|
||||
pub fn empty() -> Decoder {
|
||||
Decoder {
|
||||
inner: Inner::PlainText(Body::empty())
|
||||
inner: Inner::PlainText(Body::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +89,7 @@ impl Decoder {
|
||||
#[inline]
|
||||
fn plain_text(body: Body) -> Decoder {
|
||||
Decoder {
|
||||
inner: Inner::PlainText(body)
|
||||
inner: Inner::PlainText(body),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +99,9 @@ impl Decoder {
|
||||
#[inline]
|
||||
fn gzip(body: Body) -> Decoder {
|
||||
Decoder {
|
||||
inner: Inner::Pending(Pending { body: ReadableChunks::new(body) })
|
||||
inner: Inner::Pending(Pending {
|
||||
body: ReadableChunks::new(body),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,11 +121,11 @@ impl Decoder {
|
||||
.get_all(CONTENT_ENCODING)
|
||||
.iter()
|
||||
.any(|enc| enc == "gzip");
|
||||
content_encoding_gzip ||
|
||||
headers
|
||||
.get_all(TRANSFER_ENCODING)
|
||||
.iter()
|
||||
.any(|enc| enc == "gzip")
|
||||
content_encoding_gzip
|
||||
|| headers
|
||||
.get_all(TRANSFER_ENCODING)
|
||||
.iter()
|
||||
.any(|enc| enc == "gzip")
|
||||
};
|
||||
if is_gzip {
|
||||
if let Some(content_length) = headers.get(CONTENT_LENGTH) {
|
||||
@@ -153,15 +154,13 @@ impl Stream for Decoder {
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
// Do a read or poll for a pendidng decoder value.
|
||||
let new_value = match self.inner {
|
||||
Inner::Pending(ref mut future) => {
|
||||
match future.poll() {
|
||||
Ok(Async::Ready(inner)) => inner,
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(e) => return Err(e)
|
||||
}
|
||||
Inner::Pending(ref mut future) => match future.poll() {
|
||||
Ok(Async::Ready(inner)) => inner,
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
Inner::PlainText(ref mut body) => return body.poll(),
|
||||
Inner::Gzip(ref mut decoder) => return decoder.poll()
|
||||
Inner::Gzip(ref mut decoder) => return decoder.poll(),
|
||||
};
|
||||
|
||||
self.inner = new_value;
|
||||
@@ -177,13 +176,13 @@ impl Future for Pending {
|
||||
let body_state = match self.body.poll_stream() {
|
||||
Ok(Async::Ready(state)) => state,
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(e) => return Err(e)
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
let body = mem::replace(&mut self.body, ReadableChunks::new(Body::empty()));
|
||||
match body_state {
|
||||
StreamState::Eof => Ok(Async::Ready(Inner::PlainText(Body::empty()))),
|
||||
StreamState::HasMore => Ok(Async::Ready(Inner::Gzip(Gzip::new(body))))
|
||||
StreamState::HasMore => Ok(Async::Ready(Inner::Gzip(Gzip::new(body)))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,7 +257,7 @@ enum StreamState {
|
||||
/// More bytes can be read from the stream.
|
||||
HasMore,
|
||||
/// No more bytes can be read from the stream.
|
||||
Eof
|
||||
Eof,
|
||||
}
|
||||
|
||||
impl<S> ReadableChunks<S> {
|
||||
@@ -273,8 +272,7 @@ impl<S> ReadableChunks<S> {
|
||||
|
||||
impl<S> fmt::Debug for ReadableChunks<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("ReadableChunks")
|
||||
.finish()
|
||||
f.debug_struct("ReadableChunks").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,20 +294,12 @@ where
|
||||
} else {
|
||||
return Ok(len);
|
||||
}
|
||||
},
|
||||
ReadState::NotReady => {
|
||||
match self.poll_stream() {
|
||||
Ok(Async::Ready(StreamState::HasMore)) => continue,
|
||||
Ok(Async::Ready(StreamState::Eof)) => {
|
||||
return Ok(0)
|
||||
},
|
||||
Ok(Async::NotReady) => {
|
||||
return Err(io::ErrorKind::WouldBlock.into())
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(error::into_io(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
ReadState::NotReady => match self.poll_stream() {
|
||||
Ok(Async::Ready(StreamState::HasMore)) => continue,
|
||||
Ok(Async::Ready(StreamState::Eof)) => return Ok(0),
|
||||
Ok(Async::NotReady) => return Err(io::ErrorKind::WouldBlock.into()),
|
||||
Err(e) => return Err(error::into_io(e)),
|
||||
},
|
||||
ReadState::Eof => return Ok(0),
|
||||
}
|
||||
@@ -320,7 +310,8 @@ where
|
||||
}
|
||||
|
||||
impl<S> ReadableChunks<S>
|
||||
where S: Stream<Item = Chunk, Error = error::Error>
|
||||
where
|
||||
S: Stream<Item = Chunk, Error = error::Error>,
|
||||
{
|
||||
/// Poll the readiness of the inner reader.
|
||||
///
|
||||
@@ -332,16 +323,14 @@ impl<S> ReadableChunks<S>
|
||||
self.state = ReadState::Ready(chunk);
|
||||
|
||||
Ok(Async::Ready(StreamState::HasMore))
|
||||
},
|
||||
}
|
||||
Ok(Async::Ready(None)) => {
|
||||
self.state = ReadState::Eof;
|
||||
|
||||
Ok(Async::Ready(StreamState::Eof))
|
||||
},
|
||||
Ok(Async::NotReady) => {
|
||||
Ok(Async::NotReady)
|
||||
},
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
use http::HeaderMap;
|
||||
use mime_guess::Mime;
|
||||
use url::percent_encoding::{self, EncodeSet, PATH_SEGMENT_ENCODE_SET};
|
||||
use uuid::Uuid;
|
||||
use http::HeaderMap;
|
||||
|
||||
use futures::Stream;
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Form {
|
||||
|
||||
/// Consume this instance and transform into an instance of hyper::Body for use in a request.
|
||||
pub(crate) fn stream(mut self) -> hyper::Body {
|
||||
if self.inner.fields.is_empty(){
|
||||
if self.inner.fields.is_empty() {
|
||||
return hyper::Body::empty();
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ impl Form {
|
||||
hyper::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
|
||||
where
|
||||
T: Into<Cow<'static, str>>,
|
||||
@@ -126,12 +126,20 @@ impl Form {
|
||||
let boundary = hyper::Body::from(format!("--{}\r\n", self.boundary()));
|
||||
// append headers
|
||||
let header = hyper::Body::from({
|
||||
let mut h = self.inner.percent_encoding.encode_headers(&name.into(), &part.meta);
|
||||
let mut h = self
|
||||
.inner
|
||||
.percent_encoding
|
||||
.encode_headers(&name.into(), &part.meta);
|
||||
h.extend_from_slice(b"\r\n\r\n");
|
||||
h
|
||||
});
|
||||
// then append form data followed by terminating CRLF
|
||||
hyper::Body::wrap_stream(boundary.chain(header).chain(hyper::Body::wrap_stream(part.value)).chain(hyper::Body::from("\r\n".to_owned())))
|
||||
hyper::Body::wrap_stream(
|
||||
boundary
|
||||
.chain(header)
|
||||
.chain(hyper::Body::wrap_stream(part.value))
|
||||
.chain(hyper::Body::from("\r\n".to_owned())),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn compute_length(&mut self) -> Option<u64> {
|
||||
@@ -188,7 +196,9 @@ impl Part {
|
||||
T::Item: Into<Chunk>,
|
||||
T::Error: std::error::Error + Send + Sync,
|
||||
{
|
||||
Part::new(Body::wrap(hyper::Body::wrap_stream(value.map(|chunk| chunk.into()))))
|
||||
Part::new(Body::wrap(hyper::Body::wrap_stream(
|
||||
value.map(|chunk| chunk.into()),
|
||||
)))
|
||||
}
|
||||
|
||||
fn new(value: Body) -> Part {
|
||||
@@ -306,7 +316,13 @@ impl<P: PartProps> FormParts<P> {
|
||||
// in Reader. Not the cleanest solution because if that format string is
|
||||
// ever changed then this formula needs to be changed too which is not an
|
||||
// obvious dependency in the code.
|
||||
length += 2 + self.boundary().len() as u64 + 2 + header_length as u64 + 4 + value_length + 2
|
||||
length += 2
|
||||
+ self.boundary().len() as u64
|
||||
+ 2
|
||||
+ header_length as u64
|
||||
+ 4
|
||||
+ value_length
|
||||
+ 2
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
@@ -340,7 +356,7 @@ impl PartMetadata {
|
||||
PartMetadata {
|
||||
mime: None,
|
||||
file_name: None,
|
||||
headers: HeaderMap::default()
|
||||
headers: HeaderMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,11 +374,10 @@ impl PartMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl PartMetadata {
|
||||
pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
|
||||
&self,
|
||||
debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>
|
||||
debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
|
||||
) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
|
||||
debug_struct
|
||||
.field("mime", &self.mime)
|
||||
@@ -377,22 +392,24 @@ pub(crate) struct AttrCharEncodeSet;
|
||||
impl EncodeSet for AttrCharEncodeSet {
|
||||
fn contains(&self, ch: u8) -> bool {
|
||||
match ch as char {
|
||||
'!' => false,
|
||||
'#' => false,
|
||||
'$' => false,
|
||||
'&' => false,
|
||||
'+' => false,
|
||||
'-' => false,
|
||||
'.' => false,
|
||||
'^' => false,
|
||||
'_' => false,
|
||||
'`' => false,
|
||||
'|' => false,
|
||||
'~' => false,
|
||||
_ => {
|
||||
let is_alpha_numeric = ch >= 0x41 && ch <= 0x5a || ch >= 0x61 && ch <= 0x7a || ch >= 0x30 && ch <= 0x39;
|
||||
!is_alpha_numeric
|
||||
}
|
||||
'!' => false,
|
||||
'#' => false,
|
||||
'$' => false,
|
||||
'&' => false,
|
||||
'+' => false,
|
||||
'-' => false,
|
||||
'.' => false,
|
||||
'^' => false,
|
||||
'_' => false,
|
||||
'`' => false,
|
||||
'|' => false,
|
||||
'~' => false,
|
||||
_ => {
|
||||
let is_alpha_numeric = ch >= 0x41 && ch <= 0x5a
|
||||
|| ch >= 0x61 && ch <= 0x7a
|
||||
|| ch >= 0x30 && ch <= 0x39;
|
||||
!is_alpha_numeric
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,36 +434,38 @@ impl PercentEncoding {
|
||||
None => "".to_string(),
|
||||
},
|
||||
);
|
||||
field.headers.iter().fold(s.into_bytes(), |mut header, (k,v)| {
|
||||
header.extend_from_slice(b"\r\n");
|
||||
header.extend_from_slice(k.as_str().as_bytes());
|
||||
header.extend_from_slice(b": ");
|
||||
header.extend_from_slice(v.as_bytes());
|
||||
header
|
||||
})
|
||||
field
|
||||
.headers
|
||||
.iter()
|
||||
.fold(s.into_bytes(), |mut header, (k, v)| {
|
||||
header.extend_from_slice(b"\r\n");
|
||||
header.extend_from_slice(k.as_str().as_bytes());
|
||||
header.extend_from_slice(b": ");
|
||||
header.extend_from_slice(v.as_bytes());
|
||||
header
|
||||
})
|
||||
}
|
||||
|
||||
// According to RFC7578 Section 4.2, `filename*=` syntax is invalid.
|
||||
// See https://github.com/seanmonstar/reqwest/issues/419.
|
||||
fn format_filename(&self, filename: &str) -> String {
|
||||
let legal_filename = filename.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\r", "\\\r")
|
||||
.replace("\n", "\\\n");
|
||||
let legal_filename = filename
|
||||
.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\r", "\\\r")
|
||||
.replace("\n", "\\\n");
|
||||
format!("filename=\"{}\"", legal_filename)
|
||||
}
|
||||
|
||||
fn format_parameter(&self, name: &str, value: &str) -> String {
|
||||
let legal_value = match *self {
|
||||
PercentEncoding::PathSegment => {
|
||||
percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET)
|
||||
.to_string()
|
||||
},
|
||||
percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string()
|
||||
}
|
||||
PercentEncoding::AttrChar => {
|
||||
percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet)
|
||||
.to_string()
|
||||
},
|
||||
PercentEncoding::NoOp => { value.to_string() },
|
||||
percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet).to_string()
|
||||
}
|
||||
PercentEncoding::NoOp => value.to_string(),
|
||||
};
|
||||
if value.len() == legal_value.len() {
|
||||
// nothing has been percent encoded
|
||||
@@ -477,38 +496,45 @@ mod tests {
|
||||
#[test]
|
||||
fn stream_to_end() {
|
||||
let mut form = Form::new()
|
||||
.part("reader1", Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from("part1".to_owned())))))
|
||||
.part("key1", Part::text("value1"))
|
||||
.part(
|
||||
"key2",
|
||||
Part::text("value2").mime(mime::IMAGE_BMP),
|
||||
"reader1",
|
||||
Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from(
|
||||
"part1".to_owned(),
|
||||
)))),
|
||||
)
|
||||
.part("reader2", Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from("part2".to_owned())))))
|
||||
.part("key1", Part::text("value1"))
|
||||
.part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
|
||||
.part(
|
||||
"key3",
|
||||
Part::text("value3").file_name("filename"),
|
||||
);
|
||||
"reader2",
|
||||
Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from(
|
||||
"part2".to_owned(),
|
||||
)))),
|
||||
)
|
||||
.part("key3", Part::text("value3").file_name("filename"));
|
||||
form.inner.boundary = "boundary".to_string();
|
||||
let expected = "--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
|
||||
part1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
|
||||
value1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key2\"\r\n\
|
||||
Content-Type: image/bmp\r\n\r\n\
|
||||
value2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
|
||||
part2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
|
||||
value3\r\n--boundary--\r\n";
|
||||
let expected =
|
||||
"--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
|
||||
part1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
|
||||
value1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key2\"\r\n\
|
||||
Content-Type: image/bmp\r\n\r\n\
|
||||
value2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
|
||||
part2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
|
||||
value3\r\n--boundary--\r\n";
|
||||
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
|
||||
let body_ft = form.stream();
|
||||
|
||||
let out = rt.block_on(body_ft.map(|c| c.into_bytes()).concat2()).unwrap();
|
||||
let out = rt
|
||||
.block_on(body_ft.map(|c| c.into_bytes()).concat2())
|
||||
.unwrap();
|
||||
// These prints are for debug purposes in case the test fails
|
||||
println!(
|
||||
"START REAL\n{}\nEND REAL",
|
||||
@@ -534,7 +560,9 @@ mod tests {
|
||||
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
|
||||
let body_ft = form.stream();
|
||||
|
||||
let out = rt.block_on(body_ft.map(|c| c.into_bytes()).concat2()).unwrap();
|
||||
let out = rt
|
||||
.block_on(body_ft.map(|c| c.into_bytes()).concat2())
|
||||
.unwrap();
|
||||
// These prints are for debug purposes in case the test fails
|
||||
println!(
|
||||
"START REAL\n{}\nEND REAL",
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use std::fmt;
|
||||
|
||||
use base64::{encode};
|
||||
use base64::encode;
|
||||
use futures::Future;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use serde_urlencoded;
|
||||
|
||||
use super::body::{Body};
|
||||
use super::body::Body;
|
||||
use super::client::{Client, Pending};
|
||||
use super::multipart;
|
||||
use super::response::Response;
|
||||
use crate::header::{CONTENT_LENGTH, CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue};
|
||||
use http::HttpTryFrom;
|
||||
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use crate::{Method, Url};
|
||||
use http::HttpTryFrom;
|
||||
|
||||
/// A request which can be executed with `Client::execute()`.
|
||||
pub struct Request {
|
||||
@@ -95,10 +95,7 @@ impl Request {
|
||||
|
||||
impl RequestBuilder {
|
||||
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
|
||||
RequestBuilder {
|
||||
client,
|
||||
request,
|
||||
}
|
||||
RequestBuilder { client, request }
|
||||
}
|
||||
|
||||
/// Add a `Header` to this Request.
|
||||
@@ -110,11 +107,11 @@ impl RequestBuilder {
|
||||
let mut error = None;
|
||||
if let Ok(ref mut req) = self.request {
|
||||
match <HeaderName as HttpTryFrom<K>>::try_from(key) {
|
||||
Ok(key) => {
|
||||
match <HeaderValue as HttpTryFrom<V>>::try_from(value) {
|
||||
Ok(value) => { req.headers_mut().append(key, value); }
|
||||
Err(e) => error = Some(crate::error::from(e.into())),
|
||||
Ok(key) => match <HeaderValue as HttpTryFrom<V>>::try_from(value) {
|
||||
Ok(value) => {
|
||||
req.headers_mut().append(key, value);
|
||||
}
|
||||
Err(e) => error = Some(crate::error::from(e.into())),
|
||||
},
|
||||
Err(e) => error = Some(crate::error::from(e.into())),
|
||||
};
|
||||
@@ -168,7 +165,7 @@ impl RequestBuilder {
|
||||
{
|
||||
let auth = match password {
|
||||
Some(password) => format!("{}:{}", username, password),
|
||||
None => format!("{}:", username)
|
||||
None => format!("{}:", username),
|
||||
};
|
||||
let header_value = format!("Basic {}", encode(&auth));
|
||||
self.header(crate::header::AUTHORIZATION, &*header_value)
|
||||
@@ -221,10 +218,7 @@ impl RequestBuilder {
|
||||
pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder {
|
||||
let mut builder = self.header(
|
||||
CONTENT_TYPE,
|
||||
format!(
|
||||
"multipart/form-data; boundary={}",
|
||||
multipart.boundary()
|
||||
).as_str()
|
||||
format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
|
||||
);
|
||||
|
||||
builder = match multipart.compute_length() {
|
||||
@@ -286,10 +280,10 @@ impl RequestBuilder {
|
||||
Ok(body) => {
|
||||
req.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/x-www-form-urlencoded")
|
||||
HeaderValue::from_static("application/x-www-form-urlencoded"),
|
||||
);
|
||||
*req.body_mut() = Some(body.into());
|
||||
},
|
||||
}
|
||||
Err(err) => error = Some(crate::error::from(err)),
|
||||
}
|
||||
}
|
||||
@@ -310,12 +304,10 @@ impl RequestBuilder {
|
||||
if let Ok(ref mut req) = self.request {
|
||||
match serde_json::to_vec(json) {
|
||||
Ok(body) => {
|
||||
req.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/json")
|
||||
);
|
||||
req.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||
*req.body_mut() = Some(body.into());
|
||||
},
|
||||
}
|
||||
Err(err) => error = Some(crate::error::from(err)),
|
||||
}
|
||||
}
|
||||
@@ -368,8 +360,7 @@ impl RequestBuilder {
|
||||
|
||||
impl fmt::Debug for Request {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt_request_fields(&mut f.debug_struct("Request"), self)
|
||||
.finish()
|
||||
fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,27 +368,22 @@ impl fmt::Debug for RequestBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut builder = f.debug_struct("RequestBuilder");
|
||||
match self.request {
|
||||
Ok(ref req) => {
|
||||
fmt_request_fields(&mut builder, req)
|
||||
.finish()
|
||||
},
|
||||
Err(ref err) => {
|
||||
builder
|
||||
.field("error", err)
|
||||
.finish()
|
||||
}
|
||||
Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
|
||||
Err(ref err) => builder.field("error", err).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_request_fields<'a, 'b>(f: &'a mut fmt::DebugStruct<'a, 'b>, req: &Request) -> &'a mut fmt::DebugStruct<'a, 'b> {
|
||||
fn fmt_request_fields<'a, 'b>(
|
||||
f: &'a mut fmt::DebugStruct<'a, 'b>,
|
||||
req: &Request,
|
||||
) -> &'a mut fmt::DebugStruct<'a, 'b> {
|
||||
f.field("method", &req.method)
|
||||
.field("url", &req.url)
|
||||
.field("headers", &req.headers)
|
||||
}
|
||||
|
||||
pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
|
||||
|
||||
// IntoIter of HeaderMap yields (Option<HeaderName>, HeaderValue).
|
||||
// The first time a name is yielded, it will be Some(name), and if
|
||||
// there are more values with the same name, the next yield will be
|
||||
@@ -413,11 +399,11 @@ pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
|
||||
Some(key) => {
|
||||
dst.insert(key.clone(), value);
|
||||
prev_name = Some(key);
|
||||
},
|
||||
}
|
||||
None => match prev_name {
|
||||
Some(ref key) => {
|
||||
dst.append(key.clone(), value);
|
||||
},
|
||||
}
|
||||
None => unreachable!("HeaderMap::into_iter yielded None first"),
|
||||
},
|
||||
}
|
||||
@@ -427,8 +413,8 @@ pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Client;
|
||||
use std::collections::BTreeMap;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn add_query_append() {
|
||||
@@ -467,7 +453,10 @@ mod tests {
|
||||
let some_url = "https://google.com/";
|
||||
let r = client.get(some_url);
|
||||
|
||||
let params = Params { foo: "bar".into(), qux: 3 };
|
||||
let params = Params {
|
||||
foo: "bar".into(),
|
||||
qux: 3,
|
||||
};
|
||||
|
||||
let r = r.query(¶ms);
|
||||
|
||||
@@ -510,11 +499,7 @@ mod tests {
|
||||
|
||||
assert_eq!(req.headers()["im-a"], "keeper");
|
||||
|
||||
let foo = req
|
||||
.headers()
|
||||
.get_all("foo")
|
||||
.iter()
|
||||
.collect::<Vec<_>>();
|
||||
let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
|
||||
assert_eq!(foo.len(), 2);
|
||||
assert_eq!(foo[0], "bar");
|
||||
assert_eq!(foo[1], "baz");
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use futures::{Async, Future, Poll, Stream, try_ready};
|
||||
use futures::stream::Concat2;
|
||||
use futures::{try_ready, Async, Future, Poll, Stream};
|
||||
use http;
|
||||
use hyper::{HeaderMap, StatusCode, Version};
|
||||
use hyper::client::connect::HttpInfo;
|
||||
use hyper::header::{CONTENT_LENGTH};
|
||||
use hyper::header::CONTENT_LENGTH;
|
||||
use hyper::{HeaderMap, StatusCode, Version};
|
||||
use log::debug;
|
||||
use mime::Mime;
|
||||
use tokio::timer::Delay;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json;
|
||||
use tokio::timer::Delay;
|
||||
use url::Url;
|
||||
use log::{debug};
|
||||
|
||||
|
||||
use crate::cookie;
|
||||
use super::Decoder;
|
||||
use super::body::Body;
|
||||
|
||||
use super::Decoder;
|
||||
use crate::cookie;
|
||||
|
||||
/// A Response to a submitted `Request`.
|
||||
pub struct Response {
|
||||
@@ -37,7 +35,12 @@ pub struct Response {
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub(super) fn new(res: hyper::Response<hyper::Body>, url: Url, gzip: bool, timeout: Option<Delay>) -> Response {
|
||||
pub(super) fn new(
|
||||
res: hyper::Response<hyper::Body>,
|
||||
url: Url,
|
||||
gzip: bool,
|
||||
timeout: Option<Delay>,
|
||||
) -> Response {
|
||||
let (parts, body) = res.into_parts();
|
||||
let status = parts.status;
|
||||
let version = parts.version;
|
||||
@@ -57,7 +60,6 @@ impl Response {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Get the `StatusCode` of this `Response`.
|
||||
#[inline]
|
||||
pub fn status(&self) -> StatusCode {
|
||||
@@ -77,11 +79,10 @@ impl Response {
|
||||
}
|
||||
|
||||
/// Retrieve the cookies contained in the response.
|
||||
///
|
||||
///
|
||||
/// Note that invalid 'Set-Cookie' headers will be ignored.
|
||||
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
|
||||
cookie::extract_response_cookies(&self.headers)
|
||||
.filter_map(Result::ok)
|
||||
cookie::extract_response_cookies(&self.headers).filter_map(Result::ok)
|
||||
}
|
||||
|
||||
/// Get the final `Url` of this `Response`.
|
||||
@@ -92,8 +93,7 @@ impl Response {
|
||||
|
||||
/// Get the remote address used to get this `Response`.
|
||||
pub fn remote_addr(&self) -> Option<SocketAddr> {
|
||||
self
|
||||
.extensions
|
||||
self.extensions
|
||||
.get::<HttpInfo>()
|
||||
.map(|info| info.remote_addr())
|
||||
}
|
||||
@@ -106,8 +106,7 @@ impl Response {
|
||||
/// - The response is gzipped and automatically decoded (thus changing
|
||||
/// the actual decoded length).
|
||||
pub fn content_length(&self) -> Option<u64> {
|
||||
self
|
||||
.headers()
|
||||
self.headers()
|
||||
.get(CONTENT_LENGTH)
|
||||
.and_then(|ct_len| ct_len.to_str().ok())
|
||||
.and_then(|ct_len| ct_len.parse().ok())
|
||||
@@ -145,27 +144,24 @@ impl Response {
|
||||
}
|
||||
|
||||
/// Get the response text given a specific encoding
|
||||
pub fn text_with_charset(&mut self, default_encoding: &str) -> impl Future<Item = String, Error = crate::Error> {
|
||||
pub fn text_with_charset(
|
||||
&mut self,
|
||||
default_encoding: &str,
|
||||
) -> impl Future<Item = String, Error = crate::Error> {
|
||||
let body = mem::replace(&mut self.body, Decoder::empty());
|
||||
let content_type = self.headers.get(crate::header::CONTENT_TYPE)
|
||||
.and_then(|value| {
|
||||
value.to_str().ok()
|
||||
})
|
||||
.and_then(|value| {
|
||||
value.parse::<Mime>().ok()
|
||||
});
|
||||
let content_type = self
|
||||
.headers
|
||||
.get(crate::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<Mime>().ok());
|
||||
let encoding_name = content_type
|
||||
.as_ref()
|
||||
.and_then(|mime| {
|
||||
mime
|
||||
.get_param("charset")
|
||||
.map(|charset| charset.as_str())
|
||||
})
|
||||
.and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
|
||||
.unwrap_or(default_encoding);
|
||||
let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
|
||||
Text {
|
||||
concat: body.concat2(),
|
||||
encoding
|
||||
encoding,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +252,8 @@ impl<T: Into<Body>> From<http::Response<T>> for Response {
|
||||
let (mut parts, body) = r.into_parts();
|
||||
let body = body.into();
|
||||
let body = Decoder::detect(&mut parts.headers, body, false);
|
||||
let url = parts.extensions
|
||||
let url = parts
|
||||
.extensions
|
||||
.remove::<ResponseUrl>()
|
||||
.unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap()));
|
||||
let url = url.0;
|
||||
@@ -289,8 +286,7 @@ impl<T: DeserializeOwned> Future for Json<T> {
|
||||
|
||||
impl<T> fmt::Debug for Json<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Json")
|
||||
.finish()
|
||||
f.debug_struct("Json").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +304,9 @@ impl Future for Text {
|
||||
// a block because of borrow checker
|
||||
{
|
||||
let (text, _, _) = self.encoding.decode(&bytes);
|
||||
if let Cow::Owned(s) = text { return Ok(Async::Ready(s)) }
|
||||
if let Cow::Owned(s) = text {
|
||||
return Ok(Async::Ready(s));
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
// decoding returned Cow::Borrowed, meaning these bytes
|
||||
@@ -357,9 +355,9 @@ impl ResponseBuilderExt for http::response::Builder {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use url::Url;
|
||||
use super::{Response, ResponseBuilderExt, ResponseUrl};
|
||||
use http::response::Builder;
|
||||
use super::{Response, ResponseUrl, ResponseBuilderExt};
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn test_response_builder_ext() {
|
||||
@@ -370,7 +368,10 @@ mod tests {
|
||||
.body(())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.extensions().get::<ResponseUrl>(), Some(&ResponseUrl(url)));
|
||||
assert_eq!(
|
||||
response.extensions().get::<ResponseUrl>(),
|
||||
Some(&ResponseUrl(url))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user