cargo fmt (#604)

Run rustfmt and setup CI to check for it.
This commit is contained in:
danieleades
2019-08-29 17:52:39 +01:00
committed by Sean McArthur
parent 81e0f1ff2a
commit cf8944a0f0
41 changed files with 1399 additions and 1378 deletions

View File

@@ -1 +0,0 @@
disable_all_formatting = true

View File

@@ -55,7 +55,9 @@ dist: trusty
env: env:
global: global:
- REQWEST_TEST_BODY_FULL=1 - REQWEST_TEST_BODY_FULL=1
before_script:
- rustup component add rustfmt
script: script:
- cargo fmt -- --check
- cargo build $FEATURES - cargo build $FEATURES
- cargo test -v $FEATURES -- --test-threads=1 - cargo test -v $FEATURES -- --test-threads=1

View File

@@ -1,16 +1,11 @@
#![deny(warnings)] #![deny(warnings)]
extern crate futures;
extern crate reqwest;
extern crate tokio;
use std::mem;
use std::io::{self, Cursor};
use futures::{Future, Stream}; use futures::{Future, Stream};
use reqwest::r#async::{Client, Decoder}; use reqwest::r#async::{Client, Decoder};
use std::io::{self, Cursor};
use std::mem;
fn fetch() -> impl Future<Item = (), Error = ()> {
fn fetch() -> impl Future<Item=(), Error=()> {
Client::new() Client::new()
.get("https://hyper.rs") .get("https://hyper.rs")
.send() .send()
@@ -23,10 +18,9 @@ fn fetch() -> impl Future<Item=(), Error=()> {
.map_err(|err| println!("request error: {}", err)) .map_err(|err| println!("request error: {}", err))
.map(|body| { .map(|body| {
let mut body = Cursor::new(body); let mut body = Cursor::new(body);
let _ = io::copy(&mut body, &mut io::stdout()) let _ = io::copy(&mut body, &mut io::stdout()).map_err(|err| {
.map_err(|err| { println!("stdout error: {}", err);
println!("stdout error: {}", err); });
});
}) })
} }

View File

@@ -1,11 +1,5 @@
#![deny(warnings)] #![deny(warnings)]
extern crate futures;
extern crate reqwest;
extern crate tokio;
extern crate serde;
extern crate serde_json;
use futures::Future; use futures::Future;
use reqwest::r#async::{Client, Response}; use reqwest::r#async::{Client, Response};
use serde::Deserialize; use serde::Deserialize;
@@ -21,27 +15,18 @@ struct SlideshowContainer {
slideshow: Slideshow, slideshow: Slideshow,
} }
fn fetch() -> impl Future<Item=(), Error=()> { fn fetch() -> impl Future<Item = (), Error = ()> {
let client = Client::new(); let client = Client::new();
let json = |mut res : Response | { let json = |mut res: Response| res.json::<SlideshowContainer>();
res.json::<SlideshowContainer>()
};
let request1 = let request1 = client.get("https://httpbin.org/json").send().and_then(json);
client
.get("https://httpbin.org/json")
.send()
.and_then(json);
let request2 = let request2 = client.get("https://httpbin.org/json").send().and_then(json);
client
.get("https://httpbin.org/json")
.send()
.and_then(json);
request1.join(request2) request1
.map(|(res1, res2)|{ .join(request2)
.map(|(res1, res2)| {
println!("{:?}", res1); println!("{:?}", res1);
println!("{:?}", res2); println!("{:?}", res2);
}) })

View File

@@ -1,18 +1,11 @@
#![deny(warnings)] #![deny(warnings)]
#[macro_use]
extern crate futures;
extern crate bytes;
extern crate reqwest;
extern crate tokio;
extern crate tokio_threadpool;
use std::io::{self, Cursor}; use std::io::{self, Cursor};
use std::mem; use std::mem;
use std::path::Path; use std::path::Path;
use bytes::Bytes; use bytes::Bytes;
use futures::{Async, Future, Poll, Stream}; use futures::{try_ready, Async, Future, Poll, Stream};
use reqwest::r#async::{Client, Decoder}; use reqwest::r#async::{Client, Decoder};
use tokio::fs::File; use tokio::fs::File;
use tokio::io::AsyncRead; use tokio::io::AsyncRead;

View File

@@ -1,5 +1,3 @@
extern crate reqwest;
fn main() { fn main() {
reqwest::Client::new() reqwest::Client::new()
.post("http://www.baidu.com") .post("http://www.baidu.com")

View File

@@ -3,19 +3,16 @@
//! This is useful for some ad-hoc experiments and situations when you don't //! This is useful for some ad-hoc experiments and situations when you don't
//! really care about the structure of the JSON and just need to display it or //! really care about the structure of the JSON and just need to display it or
//! process it at runtime. //! process it at runtime.
extern crate reqwest; use serde_json::json;
#[macro_use] extern crate serde_json;
fn main() -> Result<(), reqwest::Error> { fn main() -> Result<(), reqwest::Error> {
let echo_json: serde_json::Value = reqwest::Client::new() let echo_json: serde_json::Value = reqwest::Client::new()
.post("https://jsonplaceholder.typicode.com/posts") .post("https://jsonplaceholder.typicode.com/posts")
.json( .json(&json!({
&json!({ "title": "Reqwest.rs",
"title": "Reqwest.rs", "body": "https://docs.rs/reqwest",
"body": "https://docs.rs/reqwest", "userId": 1
"userId": 1 }))
})
)
.send()? .send()?
.json()?; .json()?;

View File

@@ -3,9 +3,6 @@
//! In contrast to the arbitrary JSON example, this brings up the full power of //! In contrast to the arbitrary JSON example, this brings up the full power of
//! Rust compile-time type system guaranties though it requires a little bit //! Rust compile-time type system guaranties though it requires a little bit
//! more code. //! more code.
extern crate reqwest;
extern crate serde;
extern crate serde_json;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -23,7 +20,7 @@ fn main() -> Result<(), reqwest::Error> {
id: None, id: None,
title: "Reqwest.rs".into(), title: "Reqwest.rs".into(),
body: "https://docs.rs/reqwest".into(), body: "https://docs.rs/reqwest".into(),
user_id: 1 user_id: 1,
}; };
let new_post: Post = reqwest::Client::new() let new_post: Post = reqwest::Client::new()
.post("https://jsonplaceholder.typicode.com/posts") .post("https://jsonplaceholder.typicode.com/posts")

View File

@@ -2,9 +2,6 @@
//! `cargo run --example simple` //! `cargo run --example simple`
extern crate reqwest;
extern crate env_logger;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init(); env_logger::init();

View File

@@ -1,6 +1,6 @@
pub use self::body::{Body, Chunk}; pub use self::body::{Body, Chunk};
pub use self::decoder::{Decoder, ReadableChunks};
pub use self::client::{Client, ClientBuilder}; pub use self::client::{Client, ClientBuilder};
pub use self::decoder::{Decoder, ReadableChunks};
pub use self::request::{Request, RequestBuilder}; pub use self::request::{Request, RequestBuilder};
pub use self::response::{Response, ResponseBuilderExt}; pub use self::response::{Response, ResponseBuilderExt};

View File

@@ -1,7 +1,7 @@
use std::fmt; use std::fmt;
use futures::{Future, Stream, Poll, Async, try_ready};
use bytes::{Buf, Bytes}; use bytes::{Buf, Bytes};
use futures::{try_ready, Async, Future, Poll, Stream};
use hyper::body::Payload; use hyper::body::Payload;
use tokio::timer::Delay; use tokio::timer::Delay;
@@ -15,7 +15,7 @@ enum Inner {
Hyper { Hyper {
body: hyper::Body, body: hyper::Body,
timeout: Option<Delay>, timeout: Option<Delay>,
} },
} }
impl Body { impl Body {
@@ -29,10 +29,7 @@ impl Body {
#[inline] #[inline]
pub(crate) fn response(body: hyper::Body, timeout: Option<Delay>) -> Body { pub(crate) fn response(body: hyper::Body, timeout: Option<Delay>) -> Body {
Body { Body {
inner: Inner::Hyper { inner: Inner::Hyper { body, timeout },
body,
timeout,
},
} }
} }
@@ -65,7 +62,7 @@ impl Body {
Inner::Hyper { body, timeout } => { Inner::Hyper { body, timeout } => {
debug_assert!(timeout.is_none()); debug_assert!(timeout.is_none());
(None, body) (None, body)
}, }
} }
} }
} }
@@ -77,14 +74,17 @@ impl Stream for Body {
#[inline] #[inline]
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
let opt = match self.inner { 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 Some(ref mut timeout) = timeout {
if let Async::Ready(()) = try_!(timeout.poll()) { if let Async::Ready(()) = try_!(timeout.poll()) {
return Err(crate::error::timedout(None)); return Err(crate::error::timedout(None));
} }
} }
try_ready!(body.poll_data().map_err(crate::error::from)) try_ready!(body.poll_data().map_err(crate::error::from))
}, }
Inner::Reusable(ref mut bytes) => { Inner::Reusable(ref mut bytes) => {
return if bytes.is_empty() { return if bytes.is_empty() {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
@@ -93,12 +93,10 @@ impl Stream for Body {
*bytes = Bytes::new(); *bytes = Bytes::new();
Ok(Async::Ready(Some(chunk))) Ok(Async::Ready(Some(chunk)))
}; };
}, }
}; };
Ok(Async::Ready(opt.map(|chunk| Chunk { Ok(Async::Ready(opt.map(|chunk| Chunk { inner: chunk })))
inner: chunk,
})))
} }
} }
@@ -161,7 +159,7 @@ impl Chunk {
#[inline] #[inline]
pub(crate) fn from_chunk(chunk: Bytes) -> Chunk { pub(crate) fn from_chunk(chunk: Bytes) -> Chunk {
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 { impl Extend<u8> for Chunk {
fn extend<T>(&mut self, iter: T) fn extend<T>(&mut self, iter: T)
where T: IntoIterator<Item=u8> { where
T: IntoIterator<Item = u8>,
{
self.inner.extend(iter) self.inner.extend(iter)
} }
} }
@@ -219,7 +219,9 @@ impl From<Vec<u8>> for Chunk {
impl From<&'static [u8]> for Chunk { impl From<&'static [u8]> for Chunk {
fn from(slice: &'static [u8]) -> 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 { impl From<&'static str> for Chunk {
fn from(slice: &'static str) -> Chunk { fn from(slice: &'static str) -> Chunk {
Chunk { inner: slice.into() } Chunk {
inner: slice.into(),
}
} }
} }
impl From<Bytes> for Chunk { impl From<Bytes> for Chunk {
fn from(bytes: Bytes) -> 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 { impl fmt::Debug for Body {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Body") f.debug_struct("Body").finish()
.finish()
} }
} }

View File

@@ -1,26 +1,14 @@
use std::{fmt, str}; use std::net::IpAddr;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::time::Duration; 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 bytes::Bytes;
use futures::{Async, Future, Poll}; 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 http::Uri;
use hyper::client::ResponseFuture; use hyper::client::ResponseFuture;
use mime; use mime;
@@ -28,24 +16,22 @@ use mime;
use native_tls::TlsConnector; use native_tls::TlsConnector;
use tokio::{clock, timer::Delay}; use tokio::{clock, timer::Delay};
use log::{debug}; use log::debug;
use super::request::{Request, RequestBuilder}; use super::request::{Request, RequestBuilder};
use super::response::Response; use super::response::Response;
use crate::connect::Connector; use crate::connect::Connector;
use crate::into_url::{expect_uri, try_uri};
use crate::cookie; use crate::cookie;
use crate::redirect::{self, RedirectPolicy, remove_sensitive_headers}; use crate::into_url::{expect_uri, try_uri};
use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
use crate::proxy::get_proxies; use crate::proxy::get_proxies;
#[cfg(feature = "tls")] use crate::redirect::{self, remove_sensitive_headers, RedirectPolicy};
use crate::{Certificate, Identity};
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
use crate::tls::TlsBackend; use crate::tls::TlsBackend;
#[cfg(feature = "tls")]
use crate::{Certificate, Identity};
use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
static DEFAULT_USER_AGENT: &str = static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
/// An asynchronous `Client` to make Requests with. /// An asynchronous `Client` to make Requests with.
/// ///
@@ -98,7 +84,10 @@ impl ClientBuilder {
pub fn new() -> ClientBuilder { pub fn new() -> ClientBuilder {
let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2); let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2);
headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_USER_AGENT)); 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 { ClientBuilder {
config: Config { config: Config {
@@ -156,8 +145,13 @@ impl ClientBuilder {
id.add_to_native_tls(&mut tls)?; 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")] #[cfg(feature = "rustls-tls")]
TlsBackend::Rustls => { TlsBackend::Rustls => {
use crate::tls::NoVerifier; use crate::tls::NoVerifier;
@@ -166,15 +160,14 @@ impl ClientBuilder {
if config.http2_only { if config.http2_only {
tls.set_protocols(&["h2".into()]); tls.set_protocols(&["h2".into()]);
} else { } else {
tls.set_protocols(&[ tls.set_protocols(&["h2".into(), "http/1.1".into()]);
"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 { 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 { for cert in config.root_certs {
@@ -185,7 +178,12 @@ impl ClientBuilder {
id.add_to_rustls(&mut tls)?; 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 hyper_client = builder.build(connector);
let proxies_maybe_http_auth = proxies let proxies_maybe_http_auth = proxies.iter().any(|p| p.maybe_has_http_auth());
.iter()
.any(|p| p.maybe_has_http_auth());
let cookie_store = config.cookie_store.map(RwLock::new); 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 /// site will be trusted for use from any other. This introduces a
/// significant vulnerability to man-in-the-middle attacks. /// significant vulnerability to man-in-the-middle attacks.
#[cfg(feature = "default-tls")] #[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.config.hostname_verification = !accept_invalid_hostname;
self self
} }
@@ -299,7 +298,6 @@ impl ClientBuilder {
self self
} }
/// Sets the default headers for every request. /// Sets the default headers for every request.
pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder { pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
for (key, value) in headers.iter() { for (key, value) in headers.iter() {
@@ -349,7 +347,6 @@ impl ClientBuilder {
self self
} }
/// Set a `RedirectPolicy` for this client. /// Set a `RedirectPolicy` for this client.
/// ///
/// Default will follow redirects up to a maximum of 10. /// 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` /// Use `Client::builder()` if you wish to handle the failure as an `Error`
/// instead of panicking. /// instead of panicking.
pub fn new() -> Client { pub fn new() -> Client {
ClientBuilder::new() ClientBuilder::new().build().expect("Client::new()")
.build()
.expect("Client::new()")
} }
/// Creates a `ClientBuilder` to configure a `Client`. /// Creates a `ClientBuilder` to configure a `Client`.
@@ -529,9 +524,7 @@ impl Client {
/// ///
/// This method fails whenever supplied `Url` cannot be parsed. /// This method fails whenever supplied `Url` cannot be parsed.
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder { pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
let req = url let req = url.into_url().map(move |url| Request::new(method, url));
.into_url()
.map(move |url| Request::new(method, url));
RequestBuilder::new(self.clone(), req) RequestBuilder::new(self.clone(), req)
} }
@@ -551,14 +544,8 @@ impl Client {
self.execute_request(request) self.execute_request(request)
} }
pub(super) fn execute_request(&self, req: Request) -> Pending { pub(super) fn execute_request(&self, req: Request) -> Pending {
let ( let (method, url, mut headers, body) = req.pieces();
method,
url,
mut headers,
body
) = req.pieces();
// insert default headers in the request headers // insert default headers in the request headers
// without overwriting already appended headers. // without overwriting already appended headers.
@@ -576,9 +563,8 @@ impl Client {
} }
} }
if self.inner.gzip && if self.inner.gzip && !headers.contains_key(ACCEPT_ENCODING) && !headers.contains_key(RANGE)
!headers.contains_key(ACCEPT_ENCODING) && {
!headers.contains_key(RANGE) {
headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip")); headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip"));
} }
@@ -588,10 +574,8 @@ impl Client {
Some(body) => { Some(body) => {
let (reusable, body) = body.into_hyper(); let (reusable, body) = body.into_hyper();
(Some(reusable), body) (Some(reusable), body)
},
None => {
(None, hyper::Body::empty())
} }
None => (None, hyper::Body::empty()),
}; };
self.proxy_auth(&uri, &mut headers); self.proxy_auth(&uri, &mut headers);
@@ -606,9 +590,10 @@ impl Client {
let in_flight = self.inner.hyper.request(req); let in_flight = self.inner.hyper.request(req);
let timeout = self.inner.request_timeout.map(|dur| { let timeout = self
Delay::new(clock::now() + dur) .inner
}); .request_timeout
.map(|dur| Delay::new(clock::now() + dur));
Pending { Pending {
inner: PendingInner::Request(PendingRequest { inner: PendingInner::Request(PendingRequest {
@@ -643,14 +628,10 @@ impl Client {
return; return;
} }
for proxy in self.inner.proxies.iter() { for proxy in self.inner.proxies.iter() {
if proxy.is_match(dst) { if proxy.is_match(dst) {
if let Some(header) = proxy.http_basic_auth(dst) { if let Some(header) = proxy.http_basic_auth(dst) {
headers.insert( headers.insert(PROXY_AUTHORIZATION, header);
PROXY_AUTHORIZATION,
header,
);
} }
break; break;
@@ -671,8 +652,7 @@ impl fmt::Debug for Client {
impl fmt::Debug for ClientBuilder { impl fmt::Debug for ClientBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ClientBuilder") f.debug_struct("ClientBuilder").finish()
.finish()
} }
} }
@@ -726,7 +706,9 @@ impl Future for Pending {
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.inner { match self.inner {
PendingInner::Request(ref mut req) => req.poll(), 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); store.0.store_response_cookies(cookies, &self.url);
} }
let should_redirect = match res.status() { let should_redirect = match res.status() {
StatusCode::MOVED_PERMANENTLY | StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => {
StatusCode::FOUND |
StatusCode::SEE_OTHER => {
self.body = None; 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); self.headers.remove(header);
} }
match self.method { match self.method {
Method::GET | Method::HEAD => {}, Method::GET | Method::HEAD => {}
_ => { _ => {
self.method = Method::GET; self.method = Method::GET;
} }
} }
true true
}, }
StatusCode::TEMPORARY_REDIRECT | StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {
StatusCode::PERMANENT_REDIRECT => match self.body { match self.body {
Some(Some(_)) | None => true, Some(Some(_)) | None => true,
Some(None) => false, Some(None) => false,
}, }
}
_ => false, _ => false,
}; };
if should_redirect { if should_redirect {
let loc = res.headers() let loc = res.headers().get(LOCATION).and_then(|val| {
.get(LOCATION) let loc = (|| -> Option<Url> {
.and_then(|val| { // Some sites may send a utf-8 Location header,
let loc = (|| -> Option<Url> { // even though we're supposed to treat those bytes
// Some sites may send a utf-8 Location header, // as opaque, we'll check specifically for utf8.
// even though we're supposed to treat those bytes self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok()
// 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`. // Check that the `url` is also a valid `http::Uri`.
// //
// If not, just log it and skip the redirect. // If not, just log it and skip the redirect.
let loc = loc.and_then(|url| { let loc = loc.and_then(|url| {
if try_uri(&url).is_some() { if try_uri(&url).is_some() {
Some(url) Some(url)
} else { } else {
None None
}
});
if loc.is_none() {
debug!("Location header had invalid URI: {:?}", val);
} }
loc
}); });
if loc.is_none() {
debug!("Location header had invalid URI: {:?}", val);
}
loc
});
if let Some(loc) = loc { if let Some(loc) = loc {
if self.client.referer { if self.client.referer {
if let Some(referer) = make_referer(&loc, &self.url) { if let Some(referer) = make_referer(&loc, &self.url) {
@@ -812,11 +796,10 @@ impl Future for PendingRequest {
} }
} }
self.urls.push(self.url.clone()); self.urls.push(self.url.clone());
let action = self.client.redirect_policy.check( let action = self
res.status(), .client
&loc, .redirect_policy
&self.urls, .check(res.status(), &loc, &self.urls);
);
match action { match action {
redirect::Action::Follow => { redirect::Action::Follow => {
@@ -844,13 +827,13 @@ impl Future for PendingRequest {
*req.headers_mut() = self.headers.clone(); *req.headers_mut() = self.headers.clone();
self.in_flight = self.client.hyper.request(req); self.in_flight = self.client.hyper.request(req);
continue; continue;
}, }
redirect::Action::Stop => { redirect::Action::Stop => {
debug!("redirect_policy disallowed redirection to '{}'", loc); debug!("redirect_policy disallowed redirection to '{}'", loc);
}, }
redirect::Action::LoopDetected => { redirect::Action::LoopDetected => {
return Err(crate::error::loop_detected(self.url.clone())); return Err(crate::error::loop_detected(self.url.clone()));
}, }
redirect::Action::TooManyRedirects => { redirect::Action::TooManyRedirects => {
return Err(crate::error::too_many_redirects(self.url.clone())); return Err(crate::error::too_many_redirects(self.url.clone()));
} }
@@ -866,17 +849,12 @@ impl Future for PendingRequest {
impl fmt::Debug for Pending { impl fmt::Debug for Pending {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.inner { match self.inner {
PendingInner::Request(ref req) => { PendingInner::Request(ref req) => f
f.debug_struct("Pending") .debug_struct("Pending")
.field("method", &req.method) .field("method", &req.method)
.field("url", &req.url) .field("url", &req.url)
.finish() .finish(),
}, PendingInner::Error(ref err) => f.debug_struct("Pending").field("error", err).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() { if !header.is_empty() {
headers.insert( headers.insert(
crate::header::COOKIE, crate::header::COOKIE,
HeaderValue::from_bytes(header.as_bytes()).unwrap() HeaderValue::from_bytes(header.as_bytes()).unwrap(),
); );
} }
} }

View File

@@ -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 - `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::cmp;
use std::fmt;
use std::io::{self, Read}; use std::io::{self, Read};
use std::mem;
use bytes::{Buf, BufMut, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use futures::{Async, Future, Poll, Stream}; use futures::{Async, Future, Poll, Stream};
use hyper::{HeaderMap};
use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
use hyper::HeaderMap;
use log::{warn}; use log::warn;
use super::{Body, Chunk}; use super::{Body, Chunk};
use crate::error; use crate::error;
@@ -42,7 +42,7 @@ const INIT_BUFFER_SIZE: usize = 8192;
/// ///
/// The inner decoder may be constructed asynchronously. /// The inner decoder may be constructed asynchronously.
pub struct Decoder { pub struct Decoder {
inner: Inner inner: Inner,
} }
enum Inner { enum Inner {
@@ -51,7 +51,7 @@ enum Inner {
/// A `Gzip` decoder will uncompress the gzipped response content before returning it. /// A `Gzip` decoder will uncompress the gzipped response content before returning it.
Gzip(Gzip), Gzip(Gzip),
/// A decoder that doesn't have a value yet. /// 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. /// 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 { impl fmt::Debug for Decoder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Decoder") f.debug_struct("Decoder").finish()
.finish()
} }
} }
@@ -80,7 +79,7 @@ impl Decoder {
#[inline] #[inline]
pub fn empty() -> Decoder { pub fn empty() -> Decoder {
Decoder { Decoder {
inner: Inner::PlainText(Body::empty()) inner: Inner::PlainText(Body::empty()),
} }
} }
@@ -90,7 +89,7 @@ impl Decoder {
#[inline] #[inline]
fn plain_text(body: Body) -> Decoder { fn plain_text(body: Body) -> Decoder {
Decoder { Decoder {
inner: Inner::PlainText(body) inner: Inner::PlainText(body),
} }
} }
@@ -100,7 +99,9 @@ impl Decoder {
#[inline] #[inline]
fn gzip(body: Body) -> Decoder { fn gzip(body: Body) -> Decoder {
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) .get_all(CONTENT_ENCODING)
.iter() .iter()
.any(|enc| enc == "gzip"); .any(|enc| enc == "gzip");
content_encoding_gzip || content_encoding_gzip
headers || headers
.get_all(TRANSFER_ENCODING) .get_all(TRANSFER_ENCODING)
.iter() .iter()
.any(|enc| enc == "gzip") .any(|enc| enc == "gzip")
}; };
if is_gzip { if is_gzip {
if let Some(content_length) = headers.get(CONTENT_LENGTH) { 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> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
// Do a read or poll for a pendidng decoder value. // Do a read or poll for a pendidng decoder value.
let new_value = match self.inner { let new_value = match self.inner {
Inner::Pending(ref mut future) => { Inner::Pending(ref mut future) => match future.poll() {
match future.poll() { Ok(Async::Ready(inner)) => inner,
Ok(Async::Ready(inner)) => inner, Ok(Async::NotReady) => return Ok(Async::NotReady),
Ok(Async::NotReady) => return Ok(Async::NotReady), Err(e) => return Err(e),
Err(e) => return Err(e)
}
}, },
Inner::PlainText(ref mut body) => return body.poll(), 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; self.inner = new_value;
@@ -177,13 +176,13 @@ impl Future for Pending {
let body_state = match self.body.poll_stream() { let body_state = match self.body.poll_stream() {
Ok(Async::Ready(state)) => state, Ok(Async::Ready(state)) => state,
Ok(Async::NotReady) => return Ok(Async::NotReady), 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())); let body = mem::replace(&mut self.body, ReadableChunks::new(Body::empty()));
match body_state { match body_state {
StreamState::Eof => Ok(Async::Ready(Inner::PlainText(Body::empty()))), 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. /// More bytes can be read from the stream.
HasMore, HasMore,
/// No more bytes can be read from the stream. /// No more bytes can be read from the stream.
Eof Eof,
} }
impl<S> ReadableChunks<S> { impl<S> ReadableChunks<S> {
@@ -273,8 +272,7 @@ impl<S> ReadableChunks<S> {
impl<S> fmt::Debug for ReadableChunks<S> { impl<S> fmt::Debug for ReadableChunks<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ReadableChunks") f.debug_struct("ReadableChunks").finish()
.finish()
} }
} }
@@ -296,20 +294,12 @@ where
} else { } else {
return Ok(len); return Ok(len);
} }
}, }
ReadState::NotReady => { ReadState::NotReady => match self.poll_stream() {
match self.poll_stream() { Ok(Async::Ready(StreamState::HasMore)) => continue,
Ok(Async::Ready(StreamState::HasMore)) => continue, Ok(Async::Ready(StreamState::Eof)) => return Ok(0),
Ok(Async::Ready(StreamState::Eof)) => { Ok(Async::NotReady) => return Err(io::ErrorKind::WouldBlock.into()),
return Ok(0) Err(e) => return Err(error::into_io(e)),
},
Ok(Async::NotReady) => {
return Err(io::ErrorKind::WouldBlock.into())
},
Err(e) => {
return Err(error::into_io(e))
}
}
}, },
ReadState::Eof => return Ok(0), ReadState::Eof => return Ok(0),
} }
@@ -320,7 +310,8 @@ where
} }
impl<S> ReadableChunks<S> 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. /// Poll the readiness of the inner reader.
/// ///
@@ -332,16 +323,14 @@ impl<S> ReadableChunks<S>
self.state = ReadState::Ready(chunk); self.state = ReadState::Ready(chunk);
Ok(Async::Ready(StreamState::HasMore)) Ok(Async::Ready(StreamState::HasMore))
}, }
Ok(Async::Ready(None)) => { Ok(Async::Ready(None)) => {
self.state = ReadState::Eof; self.state = ReadState::Eof;
Ok(Async::Ready(StreamState::Eof)) Ok(Async::Ready(StreamState::Eof))
}, }
Ok(Async::NotReady) => { Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::NotReady) Err(e) => Err(e),
},
Err(e) => Err(e)
} }
} }
} }

View File

@@ -2,10 +2,10 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
use http::HeaderMap;
use mime_guess::Mime; use mime_guess::Mime;
use url::percent_encoding::{self, EncodeSet, PATH_SEGMENT_ENCODE_SET}; use url::percent_encoding::{self, EncodeSet, PATH_SEGMENT_ENCODE_SET};
use uuid::Uuid; use uuid::Uuid;
use http::HeaderMap;
use futures::Stream; 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. /// Consume this instance and transform into an instance of hyper::Body for use in a request.
pub(crate) fn stream(mut self) -> hyper::Body { pub(crate) fn stream(mut self) -> hyper::Body {
if self.inner.fields.is_empty(){ if self.inner.fields.is_empty() {
return hyper::Body::empty(); return hyper::Body::empty();
} }
@@ -117,7 +117,7 @@ impl Form {
hyper::Body::wrap_stream(stream.chain(last)) 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 pub(crate) fn part_stream<T>(&mut self, name: T, part: Part) -> hyper::Body
where where
T: Into<Cow<'static, str>>, T: Into<Cow<'static, str>>,
@@ -126,12 +126,20 @@ impl Form {
let boundary = hyper::Body::from(format!("--{}\r\n", self.boundary())); let boundary = hyper::Body::from(format!("--{}\r\n", self.boundary()));
// append headers // append headers
let header = hyper::Body::from({ 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.extend_from_slice(b"\r\n\r\n");
h h
}); });
// then append form data followed by terminating CRLF // 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> { pub(crate) fn compute_length(&mut self) -> Option<u64> {
@@ -188,7 +196,9 @@ impl Part {
T::Item: Into<Chunk>, T::Item: Into<Chunk>,
T::Error: std::error::Error + Send + Sync, 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 { 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 // 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 // ever changed then this formula needs to be changed too which is not an
// obvious dependency in the code. // 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, _ => return None,
} }
@@ -340,7 +356,7 @@ impl PartMetadata {
PartMetadata { PartMetadata {
mime: None, mime: None,
file_name: None, file_name: None,
headers: HeaderMap::default() headers: HeaderMap::default(),
} }
} }
@@ -358,11 +374,10 @@ impl PartMetadata {
} }
} }
impl PartMetadata { impl PartMetadata {
pub(crate) fn fmt_fields<'f, 'fa, 'fb>( pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
&self, &self,
debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb> debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
) -> &'f mut fmt::DebugStruct<'fa, 'fb> { ) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
debug_struct debug_struct
.field("mime", &self.mime) .field("mime", &self.mime)
@@ -377,22 +392,24 @@ pub(crate) struct AttrCharEncodeSet;
impl EncodeSet for AttrCharEncodeSet { impl EncodeSet for AttrCharEncodeSet {
fn contains(&self, ch: u8) -> bool { fn contains(&self, ch: u8) -> bool {
match ch as char { match ch as char {
'!' => false, '!' => false,
'#' => false, '#' => false,
'$' => false, '$' => false,
'&' => false, '&' => false,
'+' => false, '+' => false,
'-' => false, '-' => false,
'.' => 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; let is_alpha_numeric = ch >= 0x41 && ch <= 0x5a
!is_alpha_numeric || ch >= 0x61 && ch <= 0x7a
} || ch >= 0x30 && ch <= 0x39;
!is_alpha_numeric
}
} }
} }
} }
@@ -417,36 +434,38 @@ impl PercentEncoding {
None => "".to_string(), None => "".to_string(),
}, },
); );
field.headers.iter().fold(s.into_bytes(), |mut header, (k,v)| { field
header.extend_from_slice(b"\r\n"); .headers
header.extend_from_slice(k.as_str().as_bytes()); .iter()
header.extend_from_slice(b": "); .fold(s.into_bytes(), |mut header, (k, v)| {
header.extend_from_slice(v.as_bytes()); header.extend_from_slice(b"\r\n");
header 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. // According to RFC7578 Section 4.2, `filename*=` syntax is invalid.
// See https://github.com/seanmonstar/reqwest/issues/419. // See https://github.com/seanmonstar/reqwest/issues/419.
fn format_filename(&self, filename: &str) -> String { fn format_filename(&self, filename: &str) -> String {
let legal_filename = filename.replace("\\", "\\\\") let legal_filename = filename
.replace("\"", "\\\"") .replace("\\", "\\\\")
.replace("\r", "\\\r") .replace("\"", "\\\"")
.replace("\n", "\\\n"); .replace("\r", "\\\r")
.replace("\n", "\\\n");
format!("filename=\"{}\"", legal_filename) format!("filename=\"{}\"", legal_filename)
} }
fn format_parameter(&self, name: &str, value: &str) -> String { fn format_parameter(&self, name: &str, value: &str) -> String {
let legal_value = match *self { let legal_value = match *self {
PercentEncoding::PathSegment => { PercentEncoding::PathSegment => {
percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET) percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string()
.to_string() }
},
PercentEncoding::AttrChar => { PercentEncoding::AttrChar => {
percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet) percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet).to_string()
.to_string() }
}, PercentEncoding::NoOp => value.to_string(),
PercentEncoding::NoOp => { value.to_string() },
}; };
if value.len() == legal_value.len() { if value.len() == legal_value.len() {
// nothing has been percent encoded // nothing has been percent encoded
@@ -477,38 +496,45 @@ mod tests {
#[test] #[test]
fn stream_to_end() { fn stream_to_end() {
let mut form = Form::new() 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( .part(
"key2", "reader1",
Part::text("value2").mime(mime::IMAGE_BMP), 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( .part(
"key3", "reader2",
Part::text("value3").file_name("filename"), 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(); form.inner.boundary = "boundary".to_string();
let expected = "--boundary\r\n\ let expected =
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\ "--boundary\r\n\
part1\r\n\ Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
--boundary\r\n\ part1\r\n\
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ --boundary\r\n\
value1\r\n\ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
--boundary\r\n\ value1\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\ --boundary\r\n\
Content-Type: image/bmp\r\n\r\n\ Content-Disposition: form-data; name=\"key2\"\r\n\
value2\r\n\ Content-Type: image/bmp\r\n\r\n\
--boundary\r\n\ value2\r\n\
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\ --boundary\r\n\
part2\r\n\ Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
--boundary\r\n\ part2\r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ --boundary\r\n\
value3\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 mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
let body_ft = form.stream(); 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 // These prints are for debug purposes in case the test fails
println!( println!(
"START REAL\n{}\nEND REAL", "START REAL\n{}\nEND REAL",
@@ -534,7 +560,9 @@ mod tests {
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_ft = form.stream(); 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 // These prints are for debug purposes in case the test fails
println!( println!(
"START REAL\n{}\nEND REAL", "START REAL\n{}\nEND REAL",

View File

@@ -1,18 +1,18 @@
use std::fmt; use std::fmt;
use base64::{encode}; use base64::encode;
use futures::Future; use futures::Future;
use serde::Serialize; use serde::Serialize;
use serde_json; use serde_json;
use serde_urlencoded; use serde_urlencoded;
use super::body::{Body}; use super::body::Body;
use super::client::{Client, Pending}; use super::client::{Client, Pending};
use super::multipart; use super::multipart;
use super::response::Response; use super::response::Response;
use crate::header::{CONTENT_LENGTH, CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue}; use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE};
use http::HttpTryFrom;
use crate::{Method, Url}; use crate::{Method, Url};
use http::HttpTryFrom;
/// A request which can be executed with `Client::execute()`. /// A request which can be executed with `Client::execute()`.
pub struct Request { pub struct Request {
@@ -95,10 +95,7 @@ impl Request {
impl RequestBuilder { impl RequestBuilder {
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder { pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
RequestBuilder { RequestBuilder { client, request }
client,
request,
}
} }
/// Add a `Header` to this Request. /// Add a `Header` to this Request.
@@ -110,11 +107,11 @@ impl RequestBuilder {
let mut error = None; let mut error = None;
if let Ok(ref mut req) = self.request { if let Ok(ref mut req) = self.request {
match <HeaderName as HttpTryFrom<K>>::try_from(key) { match <HeaderName as HttpTryFrom<K>>::try_from(key) {
Ok(key) => { Ok(key) => match <HeaderValue as HttpTryFrom<V>>::try_from(value) {
match <HeaderValue as HttpTryFrom<V>>::try_from(value) { Ok(value) => {
Ok(value) => { req.headers_mut().append(key, 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())),
}, },
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 { let auth = match password {
Some(password) => format!("{}:{}", username, password), Some(password) => format!("{}:{}", username, password),
None => format!("{}:", username) None => format!("{}:", username),
}; };
let header_value = format!("Basic {}", encode(&auth)); let header_value = format!("Basic {}", encode(&auth));
self.header(crate::header::AUTHORIZATION, &*header_value) self.header(crate::header::AUTHORIZATION, &*header_value)
@@ -221,10 +218,7 @@ impl RequestBuilder {
pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder { pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder {
let mut builder = self.header( let mut builder = self.header(
CONTENT_TYPE, CONTENT_TYPE,
format!( format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
"multipart/form-data; boundary={}",
multipart.boundary()
).as_str()
); );
builder = match multipart.compute_length() { builder = match multipart.compute_length() {
@@ -286,10 +280,10 @@ impl RequestBuilder {
Ok(body) => { Ok(body) => {
req.headers_mut().insert( req.headers_mut().insert(
CONTENT_TYPE, CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded") HeaderValue::from_static("application/x-www-form-urlencoded"),
); );
*req.body_mut() = Some(body.into()); *req.body_mut() = Some(body.into());
}, }
Err(err) => error = Some(crate::error::from(err)), Err(err) => error = Some(crate::error::from(err)),
} }
} }
@@ -310,12 +304,10 @@ impl RequestBuilder {
if let Ok(ref mut req) = self.request { if let Ok(ref mut req) = self.request {
match serde_json::to_vec(json) { match serde_json::to_vec(json) {
Ok(body) => { Ok(body) => {
req.headers_mut().insert( req.headers_mut()
CONTENT_TYPE, .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
HeaderValue::from_static("application/json")
);
*req.body_mut() = Some(body.into()); *req.body_mut() = Some(body.into());
}, }
Err(err) => error = Some(crate::error::from(err)), Err(err) => error = Some(crate::error::from(err)),
} }
} }
@@ -368,8 +360,7 @@ impl RequestBuilder {
impl fmt::Debug for Request { impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_request_fields(&mut f.debug_struct("Request"), self) fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
.finish()
} }
} }
@@ -377,27 +368,22 @@ impl fmt::Debug for RequestBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("RequestBuilder"); let mut builder = f.debug_struct("RequestBuilder");
match self.request { match self.request {
Ok(ref req) => { Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
fmt_request_fields(&mut builder, req) Err(ref err) => builder.field("error", err).finish(),
.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) f.field("method", &req.method)
.field("url", &req.url) .field("url", &req.url)
.field("headers", &req.headers) .field("headers", &req.headers)
} }
pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) { pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
// IntoIter of HeaderMap yields (Option<HeaderName>, HeaderValue). // IntoIter of HeaderMap yields (Option<HeaderName>, HeaderValue).
// The first time a name is yielded, it will be Some(name), and if // 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 // 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) => { Some(key) => {
dst.insert(key.clone(), value); dst.insert(key.clone(), value);
prev_name = Some(key); prev_name = Some(key);
}, }
None => match prev_name { None => match prev_name {
Some(ref key) => { Some(ref key) => {
dst.append(key.clone(), value); dst.append(key.clone(), value);
}, }
None => unreachable!("HeaderMap::into_iter yielded None first"), None => unreachable!("HeaderMap::into_iter yielded None first"),
}, },
} }
@@ -427,8 +413,8 @@ pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Client; use super::Client;
use std::collections::BTreeMap;
use serde::Serialize; use serde::Serialize;
use std::collections::BTreeMap;
#[test] #[test]
fn add_query_append() { fn add_query_append() {
@@ -467,7 +453,10 @@ mod tests {
let some_url = "https://google.com/"; let some_url = "https://google.com/";
let r = client.get(some_url); 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(&params); let r = r.query(&params);
@@ -510,11 +499,7 @@ mod tests {
assert_eq!(req.headers()["im-a"], "keeper"); assert_eq!(req.headers()["im-a"], "keeper");
let foo = req let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
.headers()
.get_all("foo")
.iter()
.collect::<Vec<_>>();
assert_eq!(foo.len(), 2); assert_eq!(foo.len(), 2);
assert_eq!(foo[0], "bar"); assert_eq!(foo[0], "bar");
assert_eq!(foo[1], "baz"); assert_eq!(foo[1], "baz");

View File

@@ -1,28 +1,26 @@
use std::fmt;
use std::mem;
use std::marker::PhantomData;
use std::net::SocketAddr;
use std::borrow::Cow; 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 encoding_rs::{Encoding, UTF_8};
use futures::{Async, Future, Poll, Stream, try_ready};
use futures::stream::Concat2; use futures::stream::Concat2;
use futures::{try_ready, Async, Future, Poll, Stream};
use http; use http;
use hyper::{HeaderMap, StatusCode, Version};
use hyper::client::connect::HttpInfo; 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 mime::Mime;
use tokio::timer::Delay;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde_json; use serde_json;
use tokio::timer::Delay;
use url::Url; use url::Url;
use log::{debug};
use crate::cookie;
use super::Decoder;
use super::body::Body; use super::body::Body;
use super::Decoder;
use crate::cookie;
/// A Response to a submitted `Request`. /// A Response to a submitted `Request`.
pub struct Response { pub struct Response {
@@ -37,7 +35,12 @@ pub struct Response {
} }
impl 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 (parts, body) = res.into_parts();
let status = parts.status; let status = parts.status;
let version = parts.version; let version = parts.version;
@@ -57,7 +60,6 @@ impl Response {
} }
} }
/// Get the `StatusCode` of this `Response`. /// Get the `StatusCode` of this `Response`.
#[inline] #[inline]
pub fn status(&self) -> StatusCode { pub fn status(&self) -> StatusCode {
@@ -77,11 +79,10 @@ impl Response {
} }
/// Retrieve the cookies contained in the response. /// Retrieve the cookies contained in the response.
/// ///
/// Note that invalid 'Set-Cookie' headers will be ignored. /// Note that invalid 'Set-Cookie' headers will be ignored.
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a { pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
cookie::extract_response_cookies(&self.headers) cookie::extract_response_cookies(&self.headers).filter_map(Result::ok)
.filter_map(Result::ok)
} }
/// Get the final `Url` of this `Response`. /// Get the final `Url` of this `Response`.
@@ -92,8 +93,7 @@ impl Response {
/// Get the remote address used to get this `Response`. /// Get the remote address used to get this `Response`.
pub fn remote_addr(&self) -> Option<SocketAddr> { pub fn remote_addr(&self) -> Option<SocketAddr> {
self self.extensions
.extensions
.get::<HttpInfo>() .get::<HttpInfo>()
.map(|info| info.remote_addr()) .map(|info| info.remote_addr())
} }
@@ -106,8 +106,7 @@ impl Response {
/// - The response is gzipped and automatically decoded (thus changing /// - The response is gzipped and automatically decoded (thus changing
/// the actual decoded length). /// the actual decoded length).
pub fn content_length(&self) -> Option<u64> { pub fn content_length(&self) -> Option<u64> {
self self.headers()
.headers()
.get(CONTENT_LENGTH) .get(CONTENT_LENGTH)
.and_then(|ct_len| ct_len.to_str().ok()) .and_then(|ct_len| ct_len.to_str().ok())
.and_then(|ct_len| ct_len.parse().ok()) .and_then(|ct_len| ct_len.parse().ok())
@@ -145,27 +144,24 @@ impl Response {
} }
/// Get the response text given a specific encoding /// 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 body = mem::replace(&mut self.body, Decoder::empty());
let content_type = self.headers.get(crate::header::CONTENT_TYPE) let content_type = self
.and_then(|value| { .headers
value.to_str().ok() .get(crate::header::CONTENT_TYPE)
}) .and_then(|value| value.to_str().ok())
.and_then(|value| { .and_then(|value| value.parse::<Mime>().ok());
value.parse::<Mime>().ok()
});
let encoding_name = content_type let encoding_name = content_type
.as_ref() .as_ref()
.and_then(|mime| { .and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
mime
.get_param("charset")
.map(|charset| charset.as_str())
})
.unwrap_or(default_encoding); .unwrap_or(default_encoding);
let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8); let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
Text { Text {
concat: body.concat2(), 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 (mut parts, body) = r.into_parts();
let body = body.into(); let body = body.into();
let body = Decoder::detect(&mut parts.headers, body, false); let body = Decoder::detect(&mut parts.headers, body, false);
let url = parts.extensions let url = parts
.extensions
.remove::<ResponseUrl>() .remove::<ResponseUrl>()
.unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap())); .unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap()));
let url = url.0; let url = url.0;
@@ -289,8 +286,7 @@ impl<T: DeserializeOwned> Future for Json<T> {
impl<T> fmt::Debug for Json<T> { impl<T> fmt::Debug for Json<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Json") f.debug_struct("Json").finish()
.finish()
} }
} }
@@ -308,7 +304,9 @@ impl Future for Text {
// a block because of borrow checker // a block because of borrow checker
{ {
let (text, _, _) = self.encoding.decode(&bytes); 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 { unsafe {
// decoding returned Cow::Borrowed, meaning these bytes // decoding returned Cow::Borrowed, meaning these bytes
@@ -357,9 +355,9 @@ impl ResponseBuilderExt for http::response::Builder {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use url::Url; use super::{Response, ResponseBuilderExt, ResponseUrl};
use http::response::Builder; use http::response::Builder;
use super::{Response, ResponseUrl, ResponseBuilderExt}; use url::Url;
#[test] #[test]
fn test_response_builder_ext() { fn test_response_builder_ext() {
@@ -370,7 +368,10 @@ mod tests {
.body(()) .body(())
.unwrap(); .unwrap();
assert_eq!(response.extensions().get::<ResponseUrl>(), Some(&ResponseUrl(url))); assert_eq!(
response.extensions().get::<ResponseUrl>(),
Some(&ResponseUrl(url))
);
} }
#[test] #[test]

View File

@@ -1,12 +1,12 @@
use std::fs::File;
use std::fmt; use std::fmt;
use std::fs::File;
use std::io::{self, Cursor, Read}; use std::io::{self, Cursor, Read};
use bytes::Bytes; use bytes::Bytes;
use futures::{Future, try_ready}; use futures::{try_ready, Future};
use hyper::{self}; use hyper::{self};
use crate::{async_impl}; use crate::async_impl;
/// The body of a `Request`. /// The body of a `Request`.
/// ///
@@ -102,7 +102,7 @@ impl Body {
tx, tx,
}; };
(Some(tx), async_impl::Body::wrap(rx), len) (Some(tx), async_impl::Body::wrap(rx), len)
}, }
Kind::Bytes(chunk) => { Kind::Bytes(chunk) => {
let len = chunk.len() as u64; let len = chunk.len() as u64;
(None, async_impl::Body::reusable(chunk), Some(len)) (None, async_impl::Body::reusable(chunk), Some(len))
@@ -111,12 +111,10 @@ impl Body {
} }
pub(crate) fn try_clone(&self) -> Option<Body> { pub(crate) fn try_clone(&self) -> Option<Body> {
self.kind.try_clone() self.kind.try_clone().map(|kind| Body { kind })
.map(|kind| Body { kind })
} }
} }
enum Kind { enum Kind {
Reader(Box<dyn Read + Send>, Option<u64>), Reader(Box<dyn Read + Send>, Option<u64>),
Bytes(Bytes), Bytes(Bytes),
@@ -147,7 +145,6 @@ impl From<String> for Body {
} }
} }
impl From<&'static [u8]> for Body { impl From<&'static [u8]> for Body {
#[inline] #[inline]
fn from(s: &'static [u8]) -> Body { fn from(s: &'static [u8]) -> Body {
@@ -177,7 +174,8 @@ impl From<File> for Body {
impl fmt::Debug for Kind { impl fmt::Debug for Kind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Kind::Reader(_, ref v) => f.debug_struct("Reader") Kind::Reader(_, ref v) => f
.debug_struct("Reader")
.field("length", &DebugLength(v)) .field("length", &DebugLength(v))
.finish(), .finish(),
Kind::Bytes(ref v) => fmt::Debug::fmt(v, f), Kind::Bytes(ref v) => fmt::Debug::fmt(v, f),
@@ -218,10 +216,10 @@ pub(crate) struct Sender {
impl Sender { impl Sender {
// A `Future` that may do blocking read calls. // A `Future` that may do blocking read calls.
// As a `Future`, this integrates easily with `wait::timeout`. // As a `Future`, this integrates easily with `wait::timeout`.
pub(crate) fn send(self) -> impl Future<Item=(), Error=crate::Error> { pub(crate) fn send(self) -> impl Future<Item = (), Error = crate::Error> {
use std::cmp;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use futures::future; use futures::future;
use std::cmp;
let con_len = self.body.1; let con_len = self.body.1;
let cap = cmp::min(self.body.1.unwrap_or(8192), 8192); let cap = cmp::min(self.body.1.unwrap_or(8192), 8192);
@@ -261,15 +259,12 @@ impl Sender {
// read. Return. // read. Return.
return Ok(().into()); return Ok(().into());
} }
Ok(n) => { Ok(n) => unsafe {
unsafe { buf.advance_mut(n); } buf.advance_mut(n);
} },
Err(e) => { Err(e) => {
let ret = io::Error::new(e.kind(), e.to_string()); let ret = io::Error::new(e.kind(), e.to_string());
tx tx.take().expect("tx only taken on error").abort();
.take()
.expect("tx only taken on error")
.abort();
return Err(crate::error::from(ret)); return Err(crate::error::from(ret));
} }
} }
@@ -297,8 +292,8 @@ impl Sender {
pub(crate) fn read_to_string(mut body: Body) -> io::Result<String> { pub(crate) fn read_to_string(mut body: Body) -> io::Result<String> {
let mut s = String::new(); let mut s = String::new();
match body.kind { match body.kind {
Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s), Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s),
Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s), Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s),
} }
.map(|_| s) .map(|_| s)
} }

View File

@@ -1,18 +1,18 @@
use std::fmt; use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use std::thread;
use std::net::IpAddr; use std::net::IpAddr;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use futures::{Async, Future, Stream};
use futures::future::{self, Either}; use futures::future::{self, Either};
use futures::sync::{mpsc, oneshot}; use futures::sync::{mpsc, oneshot};
use futures::{Async, Future, Stream};
use log::{trace}; use log::trace;
use crate::request::{Request, RequestBuilder}; use crate::request::{Request, RequestBuilder};
use crate::response::Response; use crate::response::Response;
use crate::{async_impl, header, Method, IntoUrl, Proxy, RedirectPolicy, wait}; use crate::{async_impl, header, wait, IntoUrl, Method, Proxy, RedirectPolicy};
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
use crate::{Certificate, Identity}; use crate::{Certificate, Identity};
@@ -81,9 +81,7 @@ impl ClientBuilder {
/// This method fails if TLS backend cannot be initialized, or the resolver /// This method fails if TLS backend cannot be initialized, or the resolver
/// cannot load the system configuration. /// cannot load the system configuration.
pub fn build(self) -> crate::Result<Client> { pub fn build(self) -> crate::Result<Client> {
ClientHandle::new(self).map(|handle| Client { ClientHandle::new(self).map(|handle| Client { inner: handle })
inner: handle,
})
} }
/// Disable proxy setting. /// Disable proxy setting.
@@ -184,7 +182,6 @@ impl ClientBuilder {
self.with_inner(move |inner| inner.identity(identity)) self.with_inner(move |inner| inner.identity(identity))
} }
/// Controls the use of hostname verification. /// Controls the use of hostname verification.
/// ///
/// Defaults to `false`. /// Defaults to `false`.
@@ -399,7 +396,6 @@ impl ClientBuilder {
} }
} }
impl Client { impl Client {
/// Constructs a new `Client`. /// Constructs a new `Client`.
/// ///
@@ -411,9 +407,7 @@ impl Client {
/// Use `Client::builder()` if you wish to handle the failure as an `Error` /// Use `Client::builder()` if you wish to handle the failure as an `Error`
/// instead of panicking. /// instead of panicking.
pub fn new() -> Client { pub fn new() -> Client {
ClientBuilder::new() ClientBuilder::new().build().expect("Client::new()")
.build()
.expect("Client::new()")
} }
/// Creates a `ClientBuilder` to configure a `Client`. /// Creates a `ClientBuilder` to configure a `Client`.
@@ -486,9 +480,7 @@ impl Client {
/// ///
/// This method fails whenever supplied `Url` cannot be parsed. /// This method fails whenever supplied `Url` cannot be parsed.
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder { pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
let req = url let req = url.into_url().map(move |url| Request::new(method, url));
.into_url()
.map(move |url| Request::new(method, url));
RequestBuilder::new(self.clone(), req) RequestBuilder::new(self.clone(), req)
} }
@@ -521,22 +513,24 @@ impl fmt::Debug for Client {
impl fmt::Debug for ClientBuilder { impl fmt::Debug for ClientBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ClientBuilder") f.debug_struct("ClientBuilder").finish()
.finish()
} }
} }
#[derive(Clone)] #[derive(Clone)]
struct ClientHandle { struct ClientHandle {
timeout: Timeout, timeout: Timeout,
inner: Arc<InnerClientHandle> inner: Arc<InnerClientHandle>,
} }
type ThreadSender = mpsc::UnboundedSender<(async_impl::Request, oneshot::Sender<crate::Result<async_impl::Response>>)>; type ThreadSender = mpsc::UnboundedSender<(
async_impl::Request,
oneshot::Sender<crate::Result<async_impl::Response>>,
)>;
struct InnerClientHandle { struct InnerClientHandle {
tx: Option<ThreadSender>, tx: Option<ThreadSender>,
thread: Option<thread::JoinHandle<()>> thread: Option<thread::JoinHandle<()>>,
} }
impl Drop for InnerClientHandle { impl Drop for InnerClientHandle {
@@ -552,66 +546,64 @@ impl ClientHandle {
let builder = builder.inner; let builder = builder.inner;
let (tx, rx) = mpsc::unbounded(); let (tx, rx) = mpsc::unbounded();
let (spawn_tx, spawn_rx) = oneshot::channel::<crate::Result<()>>(); let (spawn_tx, spawn_rx) = oneshot::channel::<crate::Result<()>>();
let handle = try_!(thread::Builder::new().name("reqwest-internal-sync-runtime".into()).spawn(move || { let handle = try_!(thread::Builder::new()
use tokio::runtime::current_thread::Runtime; .name("reqwest-internal-sync-runtime".into())
.spawn(move || {
use tokio::runtime::current_thread::Runtime;
let built = (|| { let built = (|| {
let rt = try_!(Runtime::new()); let rt = try_!(Runtime::new());
let client = builder.build()?; let client = builder.build()?;
Ok((rt, client)) Ok((rt, client))
})(); })();
let (mut rt, client) = match built { let (mut rt, client) = match built {
Ok((rt, c)) => { Ok((rt, c)) => {
if spawn_tx.send(Ok(())).is_err() { if spawn_tx.send(Ok(())).is_err() {
return;
}
(rt, c)
}
Err(e) => {
let _ = spawn_tx.send(Err(e));
return; return;
} }
(rt, c) };
},
Err(e) => {
let _ = spawn_tx.send(Err(e));
return;
}
};
let work = rx.for_each(move |(req, tx)| { let work = rx.for_each(move |(req, tx)| {
let mut tx_opt: Option<oneshot::Sender<crate::Result<async_impl::Response>>> = Some(tx); let mut tx_opt: Option<oneshot::Sender<crate::Result<async_impl::Response>>> =
let mut res_fut = client.execute(req); Some(tx);
let mut res_fut = client.execute(req);
let task = future::poll_fn(move || { let task = future::poll_fn(move || {
let canceled = tx_opt let canceled = tx_opt
.as_mut() .as_mut()
.expect("polled after complete")
.poll_cancel()
.expect("poll_cancel cannot error")
.is_ready();
if canceled {
trace!("response receiver is canceled");
Ok(Async::Ready(()))
} else {
let result = match res_fut.poll() {
Ok(Async::NotReady) => return Ok(Async::NotReady),
Ok(Async::Ready(res)) => Ok(res),
Err(err) => Err(err),
};
let _ = tx_opt
.take()
.expect("polled after complete") .expect("polled after complete")
.send(result); .poll_cancel()
Ok(Async::Ready(())) .expect("poll_cancel cannot error")
} .is_ready();
if canceled {
trace!("response receiver is canceled");
Ok(Async::Ready(()))
} else {
let result = match res_fut.poll() {
Ok(Async::NotReady) => return Ok(Async::NotReady),
Ok(Async::Ready(res)) => Ok(res),
Err(err) => Err(err),
};
let _ = tx_opt.take().expect("polled after complete").send(result);
Ok(Async::Ready(()))
}
});
tokio::spawn(task);
Ok(())
}); });
tokio::spawn(task);
Ok(())
});
// work is Future<(), ()>, and our closure will never return Err
// work is Future<(), ()>, and our closure will never return Err rt.block_on(work).expect("runtime unexpected error");
rt.block_on(work) }));
.expect("runtime unexpected error");
}));
// Wait for the runtime thread to start up... // Wait for the runtime thread to start up...
match spawn_rx.wait() { match spawn_rx.wait() {
@@ -620,13 +612,11 @@ impl ClientHandle {
Err(_canceled) => event_loop_panicked(), Err(_canceled) => event_loop_panicked(),
} }
let inner_handle = Arc::new(InnerClientHandle { let inner_handle = Arc::new(InnerClientHandle {
tx: Some(tx), tx: Some(tx),
thread: Some(handle) thread: Some(handle),
}); });
Ok(ClientHandle { Ok(ClientHandle {
timeout, timeout,
inner: inner_handle, inner: inner_handle,
@@ -637,7 +627,8 @@ impl ClientHandle {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let (req, body) = req.into_async(); let (req, body) = req.into_async();
let url = req.url().clone(); let url = req.url().clone();
self.inner.tx self.inner
.tx
.as_ref() .as_ref()
.expect("core thread exited early") .expect("core thread exited early")
.unbounded_send((req, tx)) .unbounded_send((req, tx))
@@ -645,7 +636,7 @@ impl ClientHandle {
let write = if let Some(body) = body { let write = if let Some(body) = body {
Either::A(body.send()) Either::A(body.send())
//try_!(body.send(self.timeout.0), &url); //try_!(body.send(self.timeout.0), &url);
} else { } else {
Either::B(future::ok(())) Either::B(future::ok(()))
}; };
@@ -657,15 +648,17 @@ impl ClientHandle {
let res = match wait::timeout(fut, self.timeout.0) { let res = match wait::timeout(fut, self.timeout.0) {
Ok(res) => res, Ok(res) => res,
Err(wait::Waited::TimedOut) => return Err(crate::error::timedout(Some(url))), Err(wait::Waited::TimedOut) => return Err(crate::error::timedout(Some(url))),
Err(wait::Waited::Executor(err)) => { Err(wait::Waited::Executor(err)) => return Err(crate::error::from(err).with_url(url)),
return Err(crate::error::from(err).with_url(url))
},
Err(wait::Waited::Inner(err)) => { Err(wait::Waited::Inner(err)) => {
return Err(err.with_url(url)); return Err(err.with_url(url));
}, }
}; };
res.map(|res| { res.map(|res| {
Response::new(res, self.timeout.0, KeepCoreThreadAlive(Some(self.inner.clone()))) Response::new(
res,
self.timeout.0,
KeepCoreThreadAlive(Some(self.inner.clone())),
)
}) })
} }
} }

View File

@@ -4,16 +4,16 @@ use hyper::client::connect::{Connect, Connected, Destination};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::Timeout; use tokio_timer::Timeout;
#[cfg(feature = "default-tls")]
use native_tls::{TlsConnector, TlsConnectorBuilder};
#[cfg(feature = "tls")]
use futures::Poll;
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
use bytes::BufMut; use bytes::BufMut;
#[cfg(feature = "tls")]
use futures::Poll;
#[cfg(feature = "default-tls")]
use native_tls::{TlsConnector, TlsConnectorBuilder};
use std::io; use std::io;
use std::sync::Arc;
use std::net::IpAddr; use std::net::IpAddr;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
#[cfg(feature = "trust-dns")] #[cfg(feature = "trust-dns")]
@@ -25,13 +25,12 @@ type HttpConnector = hyper::client::HttpConnector<TrustDnsResolver>;
#[cfg(not(feature = "trust-dns"))] #[cfg(not(feature = "trust-dns"))]
type HttpConnector = hyper::client::HttpConnector; type HttpConnector = hyper::client::HttpConnector;
pub(crate) struct Connector { pub(crate) struct Connector {
inner: Inner, inner: Inner,
proxies: Arc<Vec<Proxy>>, proxies: Arc<Vec<Proxy>>,
timeout: Option<Duration>, timeout: Option<Duration>,
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
nodelay: bool nodelay: bool,
} }
enum Inner { enum Inner {
@@ -43,17 +42,20 @@ enum Inner {
RustlsTls { RustlsTls {
http: HttpConnector, http: HttpConnector,
tls: Arc<rustls::ClientConfig>, tls: Arc<rustls::ClientConfig>,
tls_proxy: Arc<rustls::ClientConfig> tls_proxy: Arc<rustls::ClientConfig>,
} },
} }
impl Connector { impl Connector {
#[cfg(not(feature = "tls"))] #[cfg(not(feature = "tls"))]
pub(crate) fn new<T>(proxies: Arc<Vec<Proxy>>, local_addr: T, nodelay: bool) -> crate::Result<Connector> pub(crate) fn new<T>(
proxies: Arc<Vec<Proxy>>,
local_addr: T,
nodelay: bool,
) -> crate::Result<Connector>
where where
T: Into<Option<IpAddr>> T: Into<Option<IpAddr>>,
{ {
let mut http = http_connector()?; let mut http = http_connector()?;
http.set_local_address(local_addr.into()); http.set_local_address(local_addr.into());
http.set_nodelay(nodelay); http.set_nodelay(nodelay);
@@ -69,9 +71,10 @@ impl Connector {
tls: TlsConnectorBuilder, tls: TlsConnectorBuilder,
proxies: Arc<Vec<Proxy>>, proxies: Arc<Vec<Proxy>>,
local_addr: T, local_addr: T,
nodelay: bool) -> crate::Result<Connector> nodelay: bool,
where ) -> crate::Result<Connector>
T: Into<Option<IpAddr>>, where
T: Into<Option<IpAddr>>,
{ {
let tls = try_!(tls.build()); let tls = try_!(tls.build());
@@ -83,7 +86,7 @@ impl Connector {
inner: Inner::DefaultTls(http, tls), inner: Inner::DefaultTls(http, tls),
proxies, proxies,
timeout: None, timeout: None,
nodelay nodelay,
}) })
} }
@@ -92,9 +95,10 @@ impl Connector {
tls: rustls::ClientConfig, tls: rustls::ClientConfig,
proxies: Arc<Vec<Proxy>>, proxies: Arc<Vec<Proxy>>,
local_addr: T, local_addr: T,
nodelay: bool) -> crate::Result<Connector> nodelay: bool,
where ) -> crate::Result<Connector>
T: Into<Option<IpAddr>>, where
T: Into<Option<IpAddr>>,
{ {
let mut http = http_connector()?; let mut http = http_connector()?;
http.set_local_address(local_addr.into()); http.set_local_address(local_addr.into());
@@ -110,10 +114,14 @@ impl Connector {
}; };
Ok(Connector { Ok(Connector {
inner: Inner::RustlsTls { http, tls, tls_proxy }, inner: Inner::RustlsTls {
http,
tls,
tls_proxy,
},
proxies, proxies,
timeout: None, timeout: None,
nodelay nodelay,
}) })
} }
@@ -138,56 +146,62 @@ impl Connector {
} else { } else {
Box::new($future) Box::new($future)
} }
} };
} }
let dns = match proxy { let dns = match proxy {
ProxyScheme::Socks5 { remote_dns: false, .. } => socks::DnsResolve::Local, ProxyScheme::Socks5 {
ProxyScheme::Socks5 { remote_dns: true, .. } => socks::DnsResolve::Proxy, remote_dns: false, ..
} => socks::DnsResolve::Local,
ProxyScheme::Socks5 {
remote_dns: true, ..
} => socks::DnsResolve::Proxy,
ProxyScheme::Http { .. } => { ProxyScheme::Http { .. } => {
unreachable!("connect_socks is only called for socks proxies"); unreachable!("connect_socks is only called for socks proxies");
}, }
}; };
match &self.inner { match &self.inner {
#[cfg(feature = "default-tls")] #[cfg(feature = "default-tls")]
Inner::DefaultTls(_http, tls) => if dst.scheme() == "https" { Inner::DefaultTls(_http, tls) => {
use self::native_tls_async::TlsConnectorExt; if dst.scheme() == "https" {
use self::native_tls_async::TlsConnectorExt;
let tls = tls.clone(); let tls = tls.clone();
let host = dst.host().to_owned(); let host = dst.host().to_owned();
let socks_connecting = socks::connect(proxy, dst, dns); let socks_connecting = socks::connect(proxy, dst, dns);
return timeout!(socks_connecting.and_then(move |(conn, connected)| { return timeout!(socks_connecting.and_then(move |(conn, connected)| {
tls.connect_async(&host, conn) tls.connect_async(&host, conn)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
.map(move |io| (Box::new(io) as Conn, connected)) .map(move |io| (Box::new(io) as Conn, connected))
})); }));
}, }
}
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
Inner::RustlsTls { tls_proxy, .. } => if dst.scheme() == "https" { Inner::RustlsTls { tls_proxy, .. } => {
use tokio_rustls::TlsConnector as RustlsConnector; if dst.scheme() == "https" {
use tokio_rustls::webpki::DNSNameRef; use tokio_rustls::webpki::DNSNameRef;
use tokio_rustls::TlsConnector as RustlsConnector;
let tls = tls_proxy.clone(); let tls = tls_proxy.clone();
let host = dst.host().to_owned(); let host = dst.host().to_owned();
let socks_connecting = socks::connect(proxy, dst, dns); let socks_connecting = socks::connect(proxy, dst, dns);
return timeout!(socks_connecting.and_then(move |(conn, connected)| { return timeout!(socks_connecting.and_then(move |(conn, connected)| {
let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host) let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host)
.map(|dnsname| dnsname.to_owned()) .map(|dnsname| dnsname.to_owned())
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name")); .map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name"));
futures::future::result(maybe_dnsname) futures::future::result(maybe_dnsname)
.and_then(move |dnsname| { .and_then(move |dnsname| {
RustlsConnector::from(tls).connect(dnsname.as_ref(), conn) RustlsConnector::from(tls)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)) .connect(dnsname.as_ref(), conn)
}) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
.map(move |io| { })
(Box::new(io) as Conn, connected) .map(move |io| (Box::new(io) as Conn, connected))
}) }));
})); }
}, }
#[cfg(not(feature = "tls"))] #[cfg(not(feature = "tls"))]
Inner::Http(_) => () Inner::Http(_) => (),
} }
// else no TLS // else no TLS
@@ -231,12 +245,13 @@ impl Connect for Connector {
} else { } else {
Box::new($future) Box::new($future)
} }
} };
} }
macro_rules! connect { macro_rules! connect {
( $http:expr, $dst:expr, $proxy:expr ) => { ( $http:expr, $dst:expr, $proxy:expr ) => {
timeout!($http.connect($dst) timeout!($http
.connect($dst)
.map(|(io, connected)| (Box::new(io) as Conn, connected.proxy($proxy)))) .map(|(io, connected)| (Box::new(io) as Conn, connected.proxy($proxy))))
}; };
( $dst:expr, $proxy:expr ) => { ( $dst:expr, $proxy:expr ) => {
@@ -250,17 +265,16 @@ impl Connect for Connector {
http.set_nodelay(nodelay || ($dst.scheme() == "https")); http.set_nodelay(nodelay || ($dst.scheme() == "https"));
let http = hyper_tls::HttpsConnector::from((http, tls.clone())); let http = hyper_tls::HttpsConnector::from((http, tls.clone()));
timeout!(http.connect($dst) timeout!(http.connect($dst).and_then(move |(io, connected)| {
.and_then(move |(io, connected)| { if let hyper_tls::MaybeHttpsStream::Https(stream) = &io {
if let hyper_tls::MaybeHttpsStream::Https(stream) = &io { if !nodelay {
if !nodelay { stream.get_ref().get_ref().set_nodelay(false)?;
stream.get_ref().get_ref().set_nodelay(false)?;
}
} }
}
Ok((Box::new(io) as Conn, connected.proxy($proxy))) Ok((Box::new(io) as Conn, connected.proxy($proxy)))
})) }))
}, }
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
Inner::RustlsTls { http, tls, .. } => { Inner::RustlsTls { http, tls, .. } => {
let mut http = http.clone(); let mut http = http.clone();
@@ -271,17 +285,16 @@ impl Connect for Connector {
http.set_nodelay(nodelay || ($dst.scheme() == "https")); http.set_nodelay(nodelay || ($dst.scheme() == "https"));
let http = hyper_rustls::HttpsConnector::from((http, tls.clone())); let http = hyper_rustls::HttpsConnector::from((http, tls.clone()));
timeout!(http.connect($dst) timeout!(http.connect($dst).and_then(move |(io, connected)| {
.and_then(move |(io, connected)| { if let hyper_rustls::MaybeHttpsStream::Https(stream) = &io {
if let hyper_rustls::MaybeHttpsStream::Https(stream) = &io { if !nodelay {
if !nodelay { let (io, _) = stream.get_ref();
let (io, _) = stream.get_ref(); io.set_nodelay(false)?;
io.set_nodelay(false)?;
}
} }
}
Ok((Box::new(io) as Conn, connected.proxy($proxy))) Ok((Box::new(io) as Conn, connected.proxy($proxy)))
})) }))
} }
} }
}; };
@@ -299,10 +312,7 @@ impl Connect for Connector {
let mut ndst = dst.clone(); let mut ndst = dst.clone();
let new_scheme = puri let new_scheme = puri.scheme_part().map(Scheme::as_str).unwrap_or("http");
.scheme_part()
.map(Scheme::as_str)
.unwrap_or("http");
ndst.set_scheme(new_scheme) ndst.set_scheme(new_scheme)
.expect("proxy target scheme should be valid"); .expect("proxy target scheme should be valid");
@@ -316,60 +326,81 @@ impl Connect for Connector {
match &self.inner { match &self.inner {
#[cfg(feature = "default-tls")] #[cfg(feature = "default-tls")]
Inner::DefaultTls(http, tls) => if dst.scheme() == "https" { Inner::DefaultTls(http, tls) => {
use self::native_tls_async::TlsConnectorExt; if dst.scheme() == "https" {
use self::native_tls_async::TlsConnectorExt;
let host = dst.host().to_owned(); let host = dst.host().to_owned();
let port = dst.port().unwrap_or(443); let port = dst.port().unwrap_or(443);
let mut http = http.clone(); let mut http = http.clone();
http.set_nodelay(nodelay); http.set_nodelay(nodelay);
let http = hyper_tls::HttpsConnector::from((http, tls.clone())); let http = hyper_tls::HttpsConnector::from((http, tls.clone()));
let tls = tls.clone(); let tls = tls.clone();
return timeout!(http.connect(ndst).and_then(move |(conn, connected)| { return timeout!(http.connect(ndst).and_then(
log::trace!("tunneling HTTPS over proxy"); move |(conn, connected)| {
tunnel(conn, host.clone(), port, auth) log::trace!("tunneling HTTPS over proxy");
.and_then(move |tunneled| { tunnel(conn, host.clone(), port, auth)
tls.connect_async(&host, tunneled) .and_then(move |tunneled| {
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)) tls.connect_async(&host, tunneled).map_err(|e| {
}) io::Error::new(io::ErrorKind::Other, e)
.map(|io| (Box::new(io) as Conn, connected.proxy(true))) })
})); })
}, .map(|io| (Box::new(io) as Conn, connected.proxy(true)))
}
));
}
}
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
Inner::RustlsTls { http, tls, tls_proxy } => if dst.scheme() == "https" { Inner::RustlsTls {
use rustls::Session; http,
use tokio_rustls::TlsConnector as RustlsConnector; tls,
use tokio_rustls::webpki::DNSNameRef; tls_proxy,
} => {
if dst.scheme() == "https" {
use rustls::Session;
use tokio_rustls::webpki::DNSNameRef;
use tokio_rustls::TlsConnector as RustlsConnector;
let host = dst.host().to_owned(); let host = dst.host().to_owned();
let port = dst.port().unwrap_or(443); let port = dst.port().unwrap_or(443);
let mut http = http.clone(); let mut http = http.clone();
http.set_nodelay(nodelay); http.set_nodelay(nodelay);
let http = hyper_rustls::HttpsConnector::from((http, tls_proxy.clone())); let http =
let tls = tls.clone(); hyper_rustls::HttpsConnector::from((http, tls_proxy.clone()));
return timeout!(http.connect(ndst).and_then(move |(conn, connected)| { let tls = tls.clone();
log::trace!("tunneling HTTPS over proxy"); return timeout!(http.connect(ndst).and_then(
let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host) move |(conn, connected)| {
.map(|dnsname| dnsname.to_owned()) log::trace!("tunneling HTTPS over proxy");
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name")); let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host)
tunnel(conn, host, port, auth) .map(|dnsname| dnsname.to_owned())
.and_then(move |tunneled| Ok((maybe_dnsname?, tunneled))) .map_err(|_| {
.and_then(move |(dnsname, tunneled)| { io::Error::new(io::ErrorKind::Other, "Invalid DNS Name")
RustlsConnector::from(tls).connect(dnsname.as_ref(), tunneled) });
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)) tunnel(conn, host, port, auth)
}) .and_then(move |tunneled| Ok((maybe_dnsname?, tunneled)))
.map(|io| { .and_then(move |(dnsname, tunneled)| {
let connected = if io.get_ref().1.get_alpn_protocol() == Some(b"h2") { RustlsConnector::from(tls)
connected.negotiated_h2() .connect(dnsname.as_ref(), tunneled)
} else { .map_err(|e| {
connected io::Error::new(io::ErrorKind::Other, e)
}; })
(Box::new(io) as Conn, connected.proxy(true)) })
}) .map(|io| {
})); let connected = if io.get_ref().1.get_alpn_protocol()
}, == Some(b"h2")
{
connected.negotiated_h2()
} else {
connected
};
(Box::new(io) as Conn, connected.proxy(true))
})
}
));
}
}
#[cfg(not(feature = "tls"))] #[cfg(not(feature = "tls"))]
Inner::Http(_) => () Inner::Http(_) => (),
} }
return connect!(ndst, true); return connect!(ndst, true);
@@ -384,21 +415,30 @@ pub(crate) trait AsyncConn: AsyncRead + AsyncWrite {}
impl<T: AsyncRead + AsyncWrite> AsyncConn for T {} impl<T: AsyncRead + AsyncWrite> AsyncConn for T {}
pub(crate) type Conn = Box<dyn AsyncConn + Send + Sync + 'static>; pub(crate) type Conn = Box<dyn AsyncConn + Send + Sync + 'static>;
pub(crate) type Connecting = Box<dyn Future<Item=(Conn, Connected), Error=io::Error> + Send>; pub(crate) type Connecting = Box<dyn Future<Item = (Conn, Connected), Error = io::Error> + Send>;
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
fn tunnel<T>(conn: T, host: String, port: u16, auth: Option<http::header::HeaderValue>) -> Tunnel<T> { fn tunnel<T>(
let mut buf = format!("\ conn: T,
CONNECT {0}:{1} HTTP/1.1\r\n\ host: String,
Host: {0}:{1}\r\n\ port: u16,
", host, port).into_bytes(); auth: Option<http::header::HeaderValue>,
) -> Tunnel<T> {
let mut buf = format!(
"\
CONNECT {0}:{1} HTTP/1.1\r\n\
Host: {0}:{1}\r\n\
",
host, port
)
.into_bytes();
if let Some(value) = auth { if let Some(value) = auth {
log::debug!("tunnel to {}:{} using basic auth", host, port); log::debug!("tunnel to {}:{} using basic auth", host, port);
buf.extend_from_slice(b"Proxy-Authorization: "); buf.extend_from_slice(b"Proxy-Authorization: ");
buf.extend_from_slice(value.as_bytes()); buf.extend_from_slice(value.as_bytes());
buf.extend_from_slice(b"\r\n"); buf.extend_from_slice(b"\r\n");
} }
// headers end // headers end
buf.extend_from_slice(b"\r\n"); buf.extend_from_slice(b"\r\n");
@@ -420,7 +460,7 @@ struct Tunnel<T> {
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
enum TunnelState { enum TunnelState {
Writing, Writing,
Reading Reading,
} }
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
@@ -442,7 +482,11 @@ where
return Err(tunnel_eof()); return Err(tunnel_eof());
} }
} else { } else {
let n = futures::try_ready!(self.conn.as_mut().unwrap().read_buf(&mut self.buf.get_mut())); let n = futures::try_ready!(self
.conn
.as_mut()
.unwrap()
.read_buf(&mut self.buf.get_mut()));
let read = &self.buf.get_ref()[..]; let read = &self.buf.get_ref()[..];
if n == 0 { if n == 0 {
return Err(tunnel_eof()); return Err(tunnel_eof());
@@ -451,9 +495,12 @@ where
if read.ends_with(b"\r\n\r\n") { if read.ends_with(b"\r\n\r\n") {
return Ok(self.conn.take().unwrap().into()); return Ok(self.conn.take().unwrap().into());
} }
// else read more // else read more
} else if read.starts_with(b"HTTP/1.1 407") { } else if read.starts_with(b"HTTP/1.1 407") {
return Err(io::Error::new(io::ErrorKind::Other, "proxy authentication required")); return Err(io::Error::new(
io::ErrorKind::Other,
"proxy authentication required",
));
} else if read.starts_with(b"HTTP/1.1 403") { } else if read.starts_with(b"HTTP/1.1 403") {
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
@@ -477,7 +524,7 @@ where
fn tunnel_eof() -> io::Error { fn tunnel_eof() -> io::Error {
io::Error::new( io::Error::new(
io::ErrorKind::UnexpectedEof, io::ErrorKind::UnexpectedEof,
"unexpected eof while tunneling" "unexpected eof while tunneling",
) )
} }
@@ -485,9 +532,9 @@ fn tunnel_eof() -> io::Error {
mod native_tls_async { mod native_tls_async {
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use futures::{Poll, Future, Async}; use futures::{Async, Future, Poll};
use native_tls::{self, HandshakeError, Error, TlsConnector}; use native_tls::{self, Error, HandshakeError, TlsConnector};
use tokio_io::{AsyncRead, AsyncWrite, try_nb}; use tokio_io::{try_nb, AsyncRead, AsyncWrite};
/// A wrapper around an underlying raw stream which implements the TLS or SSL /// A wrapper around an underlying raw stream which implements the TLS or SSL
/// protocol. /// protocol.
@@ -533,7 +580,8 @@ mod native_tls_async {
/// and `AsyncWrite` traits as well, otherwise this function will not work /// and `AsyncWrite` traits as well, otherwise this function will not work
/// properly. /// properly.
fn connect_async<S>(&self, domain: &str, stream: S) -> ConnectAsync<S> fn connect_async<S>(&self, domain: &str, stream: S) -> ConnectAsync<S>
where S: Read + Write; // TODO: change to AsyncRead + AsyncWrite where
S: Read + Write; // TODO: change to AsyncRead + AsyncWrite
} }
mod sealed { mod sealed {
@@ -556,9 +604,7 @@ mod native_tls_async {
} }
} }
impl<S: AsyncRead + AsyncWrite> AsyncRead for TlsStream<S> {}
impl<S: AsyncRead + AsyncWrite> AsyncRead for TlsStream<S> {
}
impl<S: AsyncRead + AsyncWrite> AsyncWrite for TlsStream<S> { impl<S: AsyncRead + AsyncWrite> AsyncWrite for TlsStream<S> {
fn shutdown(&mut self) -> Poll<(), io::Error> { fn shutdown(&mut self) -> Poll<(), io::Error> {
@@ -569,7 +615,8 @@ mod native_tls_async {
impl TlsConnectorExt for TlsConnector { impl TlsConnectorExt for TlsConnector {
fn connect_async<S>(&self, domain: &str, stream: S) -> ConnectAsync<S> fn connect_async<S>(&self, domain: &str, stream: S) -> ConnectAsync<S>
where S: Read + Write, where
S: Read + Write,
{ {
ConnectAsync { ConnectAsync {
inner: MidHandshake { inner: MidHandshake {
@@ -600,51 +647,42 @@ mod native_tls_async {
match self.inner.take().expect("cannot poll MidHandshake twice") { match self.inner.take().expect("cannot poll MidHandshake twice") {
Ok(stream) => Ok(TlsStream { inner: stream }.into()), Ok(stream) => Ok(TlsStream { inner: stream }.into()),
Err(HandshakeError::Failure(e)) => Err(e), Err(HandshakeError::Failure(e)) => Err(e),
Err(HandshakeError::WouldBlock(s)) => { Err(HandshakeError::WouldBlock(s)) => match s.handshake() {
match s.handshake() { Ok(stream) => Ok(TlsStream { inner: stream }.into()),
Ok(stream) => Ok(TlsStream { inner: stream }.into()), Err(HandshakeError::Failure(e)) => Err(e),
Err(HandshakeError::Failure(e)) => Err(e), Err(HandshakeError::WouldBlock(s)) => {
Err(HandshakeError::WouldBlock(s)) => { self.inner = Some(Err(HandshakeError::WouldBlock(s)));
self.inner = Some(Err(HandshakeError::WouldBlock(s))); Ok(Async::NotReady)
Ok(Async::NotReady)
}
} }
} },
} }
} }
} }
} }
#[cfg(feature = "socks")] #[cfg(feature = "socks")]
mod socks { mod socks {
use std::io; use std::io;
use futures::{Future, future}; use futures::{future, Future};
use hyper::client::connect::{Connected, Destination}; use hyper::client::connect::{Connected, Destination};
use socks::Socks5Stream; use socks::Socks5Stream;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use tokio::{net::TcpStream, reactor}; use tokio::{net::TcpStream, reactor};
use super::{Connecting}; use super::Connecting;
use crate::proxy::{ProxyScheme}; use crate::proxy::ProxyScheme;
pub(super) enum DnsResolve { pub(super) enum DnsResolve {
Local, Local,
Proxy, Proxy,
} }
pub(super) fn connect( pub(super) fn connect(proxy: ProxyScheme, dst: Destination, dns: DnsResolve) -> Connecting {
proxy: ProxyScheme,
dst: Destination,
dns: DnsResolve,
) -> Connecting {
let https = dst.scheme() == "https"; let https = dst.scheme() == "https";
let original_host = dst.host().to_owned(); let original_host = dst.host().to_owned();
let mut host = original_host.clone(); let mut host = original_host.clone();
let port = dst.port().unwrap_or_else(|| { let port = dst.port().unwrap_or_else(|| if https { 443 } else { 80 });
if https { 443 } else { 80 }
});
if let DnsResolve::Local = dns { if let DnsResolve::Local = dns {
let maybe_new_target = match (host.as_str(), port).to_socket_addrs() { let maybe_new_target = match (host.as_str(), port).to_socket_addrs() {
@@ -664,20 +702,24 @@ mod socks {
}; };
// Get a Tokio TcpStream // Get a Tokio TcpStream
let stream = future::result(if let Some((username, password)) = auth { let stream = future::result(
Socks5Stream::connect_with_password( if let Some((username, password)) = auth {
socket_addr, (host.as_str(), port), &username, &password Socks5Stream::connect_with_password(
) socket_addr,
} else { (host.as_str(), port),
Socks5Stream::connect(socket_addr, (host.as_str(), port)) &username,
}.and_then(|s| { &password,
TcpStream::from_std(s.into_inner(), &reactor::Handle::default()) )
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)) } else {
})); Socks5Stream::connect(socket_addr, (host.as_str(), port))
}
.and_then(|s| {
TcpStream::from_std(s.into_inner(), &reactor::Handle::default())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}),
);
Box::new( Box::new(stream.map(|s| (Box::new(s) as super::Conn, Connected::new())))
stream.map(|s| (Box::new(s) as super::Conn, Connected::new()))
)
} }
} }
@@ -686,14 +728,14 @@ mod socks {
mod tests { mod tests {
extern crate tokio_tcp; extern crate tokio_tcp;
use std::io::{Read, Write};
use std::net::TcpListener;
use std::thread;
use futures::Future;
use tokio::runtime::current_thread::Runtime;
use self::tokio_tcp::TcpStream; use self::tokio_tcp::TcpStream;
use super::tunnel; use super::tunnel;
use crate::proxy; use crate::proxy;
use futures::Future;
use std::io::{Read, Write};
use std::net::TcpListener;
use std::thread;
use tokio::runtime::current_thread::Runtime;
static TUNNEL_OK: &[u8] = b"\ static TUNNEL_OK: &[u8] = b"\
HTTP/1.1 200 OK\r\n\ HTTP/1.1 200 OK\r\n\
@@ -701,21 +743,27 @@ mod tests {
"; ";
macro_rules! mock_tunnel { macro_rules! mock_tunnel {
() => ({ () => {{
mock_tunnel!(TUNNEL_OK) mock_tunnel!(TUNNEL_OK)
}); }};
($write:expr) => ({ ($write:expr) => {{
mock_tunnel!($write, "") mock_tunnel!($write, "")
}); }};
($write:expr, $auth:expr) => ({ ($write:expr, $auth:expr) => {{
let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap(); let addr = listener.local_addr().unwrap();
let connect_expected = format!("\ let connect_expected = format!(
CONNECT {0}:{1} HTTP/1.1\r\n\ "\
Host: {0}:{1}\r\n\ CONNECT {0}:{1} HTTP/1.1\r\n\
{2}\ Host: {0}:{1}\r\n\
\r\n\ {2}\
", addr.ip(), addr.port(), $auth).into_bytes(); \r\n\
",
addr.ip(),
addr.port(),
$auth
)
.into_bytes();
thread::spawn(move || { thread::spawn(move || {
let (mut sock, _) = listener.accept().unwrap(); let (mut sock, _) = listener.accept().unwrap();
@@ -726,7 +774,7 @@ mod tests {
sock.write_all($write).unwrap(); sock.write_all($write).unwrap();
}); });
addr addr
}) }};
} }
#[test] #[test]
@@ -737,9 +785,7 @@ mod tests {
let work = TcpStream::connect(&addr); let work = TcpStream::connect(&addr);
let host = addr.ip().to_string(); let host = addr.ip().to_string();
let port = addr.port(); let port = addr.port();
let work = work.and_then(|tcp| { let work = work.and_then(|tcp| tunnel(tcp, host, port, None));
tunnel(tcp, host, port, None)
});
rt.block_on(work).unwrap(); rt.block_on(work).unwrap();
} }
@@ -752,9 +798,7 @@ mod tests {
let work = TcpStream::connect(&addr); let work = TcpStream::connect(&addr);
let host = addr.ip().to_string(); let host = addr.ip().to_string();
let port = addr.port(); let port = addr.port();
let work = work.and_then(|tcp| { let work = work.and_then(|tcp| tunnel(tcp, host, port, None));
tunnel(tcp, host, port, None)
});
rt.block_on(work).unwrap_err(); rt.block_on(work).unwrap_err();
} }
@@ -767,28 +811,26 @@ mod tests {
let work = TcpStream::connect(&addr); let work = TcpStream::connect(&addr);
let host = addr.ip().to_string(); let host = addr.ip().to_string();
let port = addr.port(); let port = addr.port();
let work = work.and_then(|tcp| { let work = work.and_then(|tcp| tunnel(tcp, host, port, None));
tunnel(tcp, host, port, None)
});
rt.block_on(work).unwrap_err(); rt.block_on(work).unwrap_err();
} }
#[test] #[test]
fn test_tunnel_proxy_unauthorized() { fn test_tunnel_proxy_unauthorized() {
let addr = mock_tunnel!(b"\ let addr = mock_tunnel!(
b"\
HTTP/1.1 407 Proxy Authentication Required\r\n\ HTTP/1.1 407 Proxy Authentication Required\r\n\
Proxy-Authenticate: Basic realm=\"nope\"\r\n\ Proxy-Authenticate: Basic realm=\"nope\"\r\n\
\r\n\ \r\n\
"); "
);
let mut rt = Runtime::new().unwrap(); let mut rt = Runtime::new().unwrap();
let work = TcpStream::connect(&addr); let work = TcpStream::connect(&addr);
let host = addr.ip().to_string(); let host = addr.ip().to_string();
let port = addr.port(); let port = addr.port();
let work = work.and_then(|tcp| { let work = work.and_then(|tcp| tunnel(tcp, host, port, None));
tunnel(tcp, host, port, None)
});
let error = rt.block_on(work).unwrap_err(); let error = rt.block_on(work).unwrap_err();
assert_eq!(error.to_string(), "proxy authentication required"); assert_eq!(error.to_string(), "proxy authentication required");
@@ -806,7 +848,12 @@ mod tests {
let host = addr.ip().to_string(); let host = addr.ip().to_string();
let port = addr.port(); let port = addr.port();
let work = work.and_then(|tcp| { let work = work.and_then(|tcp| {
tunnel(tcp, host, port, Some(proxy::encode_basic_auth("Aladdin", "open sesame"))) tunnel(
tcp,
host,
port,
Some(proxy::encode_basic_auth("Aladdin", "open sesame")),
)
}); });
rt.block_on(work).unwrap(); rt.block_on(work).unwrap();

View File

@@ -109,7 +109,9 @@ impl<'a> Cookie<'a> {
/// Get the Max-Age information. /// Get the Max-Age information.
pub fn max_age(&self) -> Option<std::time::Duration> { pub fn max_age(&self) -> Option<std::time::Duration> {
self.0.max_age().map(|d| std::time::Duration::new(d.num_seconds() as u64, 0)) self.0
.max_age()
.map(|d| std::time::Duration::new(d.num_seconds() as u64, 0))
} }
/// The cookie expiration time. /// The cookie expiration time.

View File

@@ -1,6 +1,6 @@
use std::{io, vec};
use std::net::IpAddr; use std::net::IpAddr;
use std::sync::{Arc, Mutex, Once}; use std::sync::{Arc, Mutex, Once};
use std::{io, vec};
use futures::{future, Future}; use futures::{future, Future};
use hyper::client::connect::dns as hyper_dns; use hyper::client::connect::dns as hyper_dns;
@@ -13,7 +13,7 @@ use trust_dns_resolver::{system_conf, AsyncResolver, BackgroundLookupIp};
// //
// "Erasing" the internal resolver type saves us from this limit. // "Erasing" the internal resolver type saves us from this limit.
type ErasedResolver = Box<dyn Fn(hyper_dns::Name) -> BackgroundLookupIp + Send + Sync>; type ErasedResolver = Box<dyn Fn(hyper_dns::Name) -> BackgroundLookupIp + Send + Sync>;
type Background = Box<dyn Future<Item=(), Error=()> + Send>; type Background = Box<dyn Future<Item = (), Error = ()> + Send>;
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct TrustDnsResolver { pub(crate) struct TrustDnsResolver {
@@ -31,9 +31,7 @@ impl TrustDnsResolver {
let (conf, opts) = system_conf::read_system_conf()?; let (conf, opts) = system_conf::read_system_conf()?;
let (resolver, bg) = AsyncResolver::new(conf, opts); let (resolver, bg) = AsyncResolver::new(conf, opts);
let resolver: ErasedResolver = Box::new(move |name| { let resolver: ErasedResolver = Box::new(move |name| resolver.lookup_ip(name.as_str()));
resolver.lookup_ip(name.as_str())
});
let background = Mutex::new(Some(Box::new(bg) as Background)); let background = Mutex::new(Some(Box::new(bg) as Background));
let once = Once::new(); let once = Once::new();
@@ -49,7 +47,7 @@ impl TrustDnsResolver {
impl hyper_dns::Resolve for TrustDnsResolver { impl hyper_dns::Resolve for TrustDnsResolver {
type Addrs = vec::IntoIter<IpAddr>; type Addrs = vec::IntoIter<IpAddr>;
type Future = Box<dyn Future<Item=Self::Addrs, Error=io::Error> + Send>; type Future = Box<dyn Future<Item = Self::Addrs, Error = io::Error> + Send>;
fn resolve(&self, name: hyper_dns::Name) -> Self::Future { fn resolve(&self, name: hyper_dns::Name) -> Self::Future {
let inner = self.inner.clone(); let inner = self.inner.clone();
@@ -70,16 +68,8 @@ impl hyper_dns::Resolve for TrustDnsResolver {
}); });
(inner.resolver)(name) (inner.resolver)(name)
.map(|lookup| { .map(|lookup| lookup.iter().collect::<Vec<_>>().into_iter())
lookup .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))
.iter()
.collect::<Vec<_>>()
.into_iter()
})
.map_err(|err| {
io::Error::new(io::ErrorKind::Other, err.to_string())
})
})) }))
} }
} }

View File

@@ -62,17 +62,13 @@ struct Inner {
url: Option<Url>, url: Option<Url>,
} }
/// A `Result` alias where the `Err` case is `reqwest::Error`. /// A `Result` alias where the `Err` case is `reqwest::Error`.
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
impl Error { impl Error {
fn new(kind: Kind, url: Option<Url>) -> Error { fn new(kind: Kind, url: Option<Url>) -> Error {
Error { Error {
inner: Box::new(Inner { inner: Box::new(Inner { kind, url }),
kind,
url,
}),
} }
} }
@@ -148,13 +144,13 @@ impl Error {
Kind::Io(ref e) => Some(e), Kind::Io(ref e) => Some(e),
Kind::UrlEncoded(ref e) => Some(e), Kind::UrlEncoded(ref e) => Some(e),
Kind::Json(ref e) => Some(e), Kind::Json(ref e) => Some(e),
Kind::UrlBadScheme | Kind::UrlBadScheme
Kind::TooManyRedirects | | Kind::TooManyRedirects
Kind::RedirectLoop | | Kind::RedirectLoop
Kind::Status(_) | | Kind::Status(_)
Kind::UnknownProxyScheme | | Kind::UnknownProxyScheme
Kind::Timer | | Kind::Timer
Kind::BlockingClientInFutureContext => None, | Kind::BlockingClientInFutureContext => None,
} }
} }
@@ -172,15 +168,11 @@ impl Error {
pub fn is_timeout(&self) -> bool { pub fn is_timeout(&self) -> bool {
match self.inner.kind { match self.inner.kind {
Kind::Io(ref io) => io.kind() == io::ErrorKind::TimedOut, Kind::Io(ref io) => io.kind() == io::ErrorKind::TimedOut,
Kind::Hyper(ref error) => { Kind::Hyper(ref error) => error
error .source()
.source() .and_then(|cause| cause.downcast_ref::<io::Error>())
.and_then(|cause| { .map(|io| io.kind() == io::ErrorKind::TimedOut)
cause.downcast_ref::<io::Error>() .unwrap_or(false),
})
.map(|io| io.kind() == io::ErrorKind::TimedOut)
.unwrap_or(false)
},
_ => false, _ => false,
} }
} }
@@ -189,8 +181,7 @@ impl Error {
#[inline] #[inline]
pub fn is_serialization(&self) -> bool { pub fn is_serialization(&self) -> bool {
match self.inner.kind { match self.inner.kind {
Kind::Json(_) | Kind::Json(_) | Kind::UrlEncoded(_) => true,
Kind::UrlEncoded(_) => true,
_ => false, _ => false,
} }
} }
@@ -199,8 +190,7 @@ impl Error {
#[inline] #[inline]
pub fn is_redirect(&self) -> bool { pub fn is_redirect(&self) -> bool {
match self.inner.kind { match self.inner.kind {
Kind::TooManyRedirects | Kind::TooManyRedirects | Kind::RedirectLoop => true,
Kind::RedirectLoop => true,
_ => false, _ => false,
} }
} }
@@ -241,9 +231,7 @@ impl fmt::Debug for Error {
.field(url) .field(url)
.finish() .finish()
} else { } else {
f.debug_tuple("Error") f.debug_tuple("Error").field(&self.inner.kind).finish()
.field(&self.inner.kind)
.finish()
} }
} }
} }
@@ -269,9 +257,7 @@ impl fmt::Display for Error {
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
Kind::Rustls(ref e) => fmt::Display::fmt(e, f), Kind::Rustls(ref e) => fmt::Display::fmt(e, f),
#[cfg(feature = "trust-dns")] #[cfg(feature = "trust-dns")]
Kind::DnsSystemConf(ref e) => { Kind::DnsSystemConf(ref e) => write!(f, "failed to load DNS system conf: {}", e),
write!(f, "failed to load DNS system conf: {}", e)
},
Kind::Io(ref e) => fmt::Display::fmt(e, f), Kind::Io(ref e) => fmt::Display::fmt(e, f),
Kind::UrlEncoded(ref e) => fmt::Display::fmt(e, f), Kind::UrlEncoded(ref e) => fmt::Display::fmt(e, f),
Kind::Json(ref e) => fmt::Display::fmt(e, f), Kind::Json(ref e) => fmt::Display::fmt(e, f),
@@ -349,13 +335,13 @@ impl StdError for Error {
Kind::Io(ref e) => e.cause(), Kind::Io(ref e) => e.cause(),
Kind::UrlEncoded(ref e) => e.cause(), Kind::UrlEncoded(ref e) => e.cause(),
Kind::Json(ref e) => e.cause(), Kind::Json(ref e) => e.cause(),
Kind::UrlBadScheme | Kind::UrlBadScheme
Kind::TooManyRedirects | | Kind::TooManyRedirects
Kind::RedirectLoop | | Kind::RedirectLoop
Kind::Status(_) | | Kind::Status(_)
Kind::UnknownProxyScheme | | Kind::UnknownProxyScheme
Kind::Timer | | Kind::Timer
Kind::BlockingClientInFutureContext => None, | Kind::BlockingClientInFutureContext => None,
} }
} }
@@ -376,13 +362,13 @@ impl StdError for Error {
Kind::Io(ref e) => e.source(), Kind::Io(ref e) => e.source(),
Kind::UrlEncoded(ref e) => e.source(), Kind::UrlEncoded(ref e) => e.source(),
Kind::Json(ref e) => e.source(), Kind::Json(ref e) => e.source(),
Kind::UrlBadScheme | Kind::UrlBadScheme
Kind::TooManyRedirects | | Kind::TooManyRedirects
Kind::RedirectLoop | | Kind::RedirectLoop
Kind::Status(_) | | Kind::Status(_)
Kind::UnknownProxyScheme | | Kind::UnknownProxyScheme
Kind::Timer | | Kind::Timer
Kind::BlockingClientInFutureContext => None, | Kind::BlockingClientInFutureContext => None,
} }
} }
} }
@@ -413,7 +399,6 @@ pub(crate) enum Kind {
BlockingClientInFutureContext, BlockingClientInFutureContext,
} }
impl From<http::Error> for Kind { impl From<http::Error> for Kind {
#[inline] #[inline]
fn from(err: http::Error) -> Kind { fn from(err: http::Error) -> Kind {
@@ -478,10 +463,12 @@ impl From<rustls::TLSError> for Kind {
} }
impl<T> From<crate::wait::Waited<T>> for Kind impl<T> From<crate::wait::Waited<T>> for Kind
where T: Into<Kind> { where
T: Into<Kind>,
{
fn from(err: crate::wait::Waited<T>) -> Kind { fn from(err: crate::wait::Waited<T>) -> Kind {
match err { match err {
crate::wait::Waited::TimedOut => io_timeout().into(), crate::wait::Waited::TimedOut => io_timeout().into(),
crate::wait::Waited::Executor(e) => e.into(), crate::wait::Waited::Executor(e) => e.into(),
crate::wait::Waited::Inner(e) => e.into(), crate::wait::Waited::Inner(e) => e.into(),
} }
@@ -542,8 +529,7 @@ pub(crate) fn into_io(e: Error) -> io::Error {
pub(crate) fn from_io(e: io::Error) -> Error { pub(crate) fn from_io(e: io::Error) -> Error {
if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) { if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
*e *e.into_inner()
.into_inner()
.expect("io::Error::get_ref was Some(_)") .expect("io::Error::get_ref was Some(_)")
.downcast::<Error>() .downcast::<Error>()
.expect("StdError::is() was true") .expect("StdError::is() was true")
@@ -552,28 +538,30 @@ pub(crate) fn from_io(e: io::Error) -> Error {
} }
} }
macro_rules! try_ { macro_rules! try_ {
($e:expr) => ( ($e:expr) => {
match $e { match $e {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
return Err(crate::error::from(err)); return Err(crate::error::from(err));
} }
} }
); };
($e:expr, $url:expr) => ( ($e:expr, $url:expr) => {
match $e { match $e {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
return Err(crate::Error::from(crate::error::InternalFrom(err, Some($url.clone())))); return Err(crate::Error::from(crate::error::InternalFrom(
err,
Some($url.clone()),
)));
} }
} }
) };
} }
macro_rules! try_io { macro_rules! try_io {
($e:expr) => ( ($e:expr) => {
match $e { match $e {
Ok(v) => v, Ok(v) => v,
Err(ref err) if err.kind() == std::io::ErrorKind::WouldBlock => { Err(ref err) if err.kind() == std::io::ErrorKind::WouldBlock => {
@@ -583,7 +571,7 @@ macro_rules! try_io {
return Err(crate::error::from_io(err)); return Err(crate::error::from_io(err));
} }
} }
) };
} }
pub(crate) fn loop_detected(url: Url) -> Error { pub(crate) fn loop_detected(url: Url) -> Error {
@@ -625,7 +613,7 @@ mod tests {
#[derive(Debug)] #[derive(Debug)]
struct Chain<T>(Option<T>); struct Chain<T>(Option<T>);
impl<T: fmt::Display> fmt::Display for Chain<T> { impl<T: fmt::Display> fmt::Display for Chain<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref link) = self.0 { if let Some(ref link) = self.0 {
write!(f, "chain: {}", link) write!(f, "chain: {}", link)
@@ -658,7 +646,6 @@ mod tests {
assert!(err.cause().is_none()); assert!(err.cause().is_none());
assert_eq!(err.to_string(), "root"); assert_eq!(err.to_string(), "root");
let root = std::io::Error::new(std::io::ErrorKind::Other, Chain(None::<Error>)); let root = std::io::Error::new(std::io::ErrorKind::Other, Chain(None::<Error>));
let link = Chain(Some(root)); let link = Chain(Some(root));
let io = std::io::Error::new(std::io::ErrorKind::Other, link); let io = std::io::Error::new(std::io::ErrorKind::Other, link);

View File

@@ -27,8 +27,7 @@ impl PolyfillTryInto for Url {
impl<'a> PolyfillTryInto for &'a str { impl<'a> PolyfillTryInto for &'a str {
fn into_url(self) -> crate::Result<Url> { fn into_url(self) -> crate::Result<Url> {
try_!(Url::parse(self)) try_!(Url::parse(self)).into_url()
.into_url()
} }
} }
@@ -39,7 +38,9 @@ impl<'a> PolyfillTryInto for &'a String {
} }
pub(crate) fn expect_uri(url: &Url) -> hyper::Uri { pub(crate) fn expect_uri(url: &Url) -> hyper::Uri {
url.as_str().parse().expect("a parsed Url should always be a valid Uri") url.as_str()
.parse()
.expect("a parsed Url should always be a valid Uri")
} }
pub(crate) fn try_uri(url: &Url) -> Option<hyper::Uri> { pub(crate) fn try_uri(url: &Url) -> Option<hyper::Uri> {
@@ -52,9 +53,10 @@ mod tests {
#[test] #[test]
fn into_url_file_scheme() { fn into_url_file_scheme() {
let err = "file:///etc/hosts" let err = "file:///etc/hosts".into_url().unwrap_err();
.into_url() assert_eq!(
.unwrap_err(); err.to_string(),
assert_eq!(err.to_string(), "file:///etc/hosts: URL scheme is not allowed"); "file:///etc/hosts: URL scheme is not allowed"
);
} }
} }

View File

@@ -187,12 +187,12 @@ doctest!("../README.md");
pub use hyper::header; pub use hyper::header;
pub use hyper::Method; pub use hyper::Method;
pub use hyper::{StatusCode, Version}; pub use hyper::{StatusCode, Version};
pub use url::Url;
pub use url::ParseError as UrlError; pub use url::ParseError as UrlError;
pub use url::Url;
pub use self::body::Body;
pub use self::client::{Client, ClientBuilder}; pub use self::client::{Client, ClientBuilder};
pub use self::error::{Error, Result}; pub use self::error::{Error, Result};
pub use self::body::Body;
pub use self::into_url::IntoUrl; pub use self::into_url::IntoUrl;
pub use self::proxy::Proxy; pub use self::proxy::Proxy;
pub use self::redirect::{RedirectAction, RedirectAttempt, RedirectPolicy}; pub use self::redirect::{RedirectAction, RedirectAttempt, RedirectPolicy};
@@ -206,9 +206,9 @@ pub use self::tls::{Certificate, Identity};
mod error; mod error;
mod async_impl; mod async_impl;
mod connect;
mod body; mod body;
mod client; mod client;
mod connect;
pub mod cookie; pub mod cookie;
#[cfg(feature = "trust-dns")] #[cfg(feature = "trust-dns")]
mod dns; mod dns;
@@ -226,16 +226,8 @@ pub mod multipart;
/// An 'async' implementation of the reqwest `Client`. /// An 'async' implementation of the reqwest `Client`.
pub mod r#async { pub mod r#async {
pub use crate::async_impl::{ pub use crate::async_impl::{
Body, multipart, Body, Chunk, Client, ClientBuilder, Decoder, Request, RequestBuilder, Response,
Chunk,
Decoder,
Client,
ClientBuilder,
Request,
RequestBuilder,
Response,
ResponseBuilderExt, ResponseBuilderExt,
multipart
}; };
} }
@@ -269,10 +261,7 @@ pub mod r#async {
/// - redirect loop was detected /// - redirect loop was detected
/// - redirect limit was exhausted /// - redirect limit was exhausted
pub fn get<T: IntoUrl>(url: T) -> crate::Result<Response> { pub fn get<T: IntoUrl>(url: T) -> crate::Result<Response> {
Client::builder() Client::builder().build()?.get(url).send()
.build()?
.get(url)
.send()
} }
fn _assert_impls() { fn _assert_impls() {

View File

@@ -45,7 +45,7 @@ use std::path::Path;
use mime_guess::{self, Mime}; use mime_guess::{self, Mime};
use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps}; use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps};
use crate::{Body}; use crate::Body;
/// A multipart/form-data request. /// A multipart/form-data request.
pub struct Form { pub struct Form {
@@ -82,8 +82,9 @@ impl Form {
/// .text("password", "secret"); /// .text("password", "secret");
/// ``` /// ```
pub fn text<T, U>(self, name: T, value: U) -> Form pub fn text<T, U>(self, name: T, value: U) -> Form
where T: Into<Cow<'static, str>>, where
U: Into<Cow<'static, str>>, T: Into<Cow<'static, str>>,
U: Into<Cow<'static, str>>,
{ {
self.part(name, Part::text(value)) self.part(name, Part::text(value))
} }
@@ -106,8 +107,9 @@ impl Form {
/// ///
/// Errors when the file cannot be opened. /// Errors when the file cannot be opened.
pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form> pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
where T: Into<Cow<'static, str>>, where
U: AsRef<Path> T: Into<Cow<'static, str>>,
U: AsRef<Path>,
{ {
Ok(self.part(name, Part::file(path)?)) Ok(self.part(name, Part::file(path)?))
} }
@@ -162,11 +164,11 @@ impl fmt::Debug for Form {
} }
} }
impl Part { impl Part {
/// Makes a text parameter. /// Makes a text parameter.
pub fn text<T>(value: T) -> Part pub fn text<T>(value: T) -> Part
where T: Into<Cow<'static, str>>, where
T: Into<Cow<'static, str>>,
{ {
let body = match value.into() { let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice), Cow::Borrowed(slice) => Body::from(slice),
@@ -177,7 +179,8 @@ impl Part {
/// Makes a new parameter from arbitrary bytes. /// Makes a new parameter from arbitrary bytes.
pub fn bytes<T>(value: T) -> Part pub fn bytes<T>(value: T) -> Part
where T: Into<Cow<'static, [u8]>> where
T: Into<Cow<'static, [u8]>>,
{ {
let body = match value.into() { let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice), Cow::Borrowed(slice) => Body::from(slice),
@@ -207,16 +210,14 @@ impl Part {
/// Errors when the file cannot be opened. /// Errors when the file cannot be opened.
pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> { pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
let path = path.as_ref(); let path = path.as_ref();
let file_name = path.file_name().and_then(|filename| { let file_name = path
Some(filename.to_string_lossy().into_owned()) .file_name()
}); .map(|filename| filename.to_string_lossy().into_owned());
let ext = path.extension()
.and_then(|ext| ext.to_str()) let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
.unwrap_or("");
let mime = mime_guess::from_ext(ext).first_or_octet_stream(); let mime = mime_guess::from_ext(ext).first_or_octet_stream();
let file = File::open(path)?; let file = File::open(path)?;
let field = Part::new(Body::from(file)) let field = Part::new(Body::from(file)).mime(mime);
.mime(mime);
Ok(if let Some(file_name) = file_name { Ok(if let Some(file_name) = file_name {
field.file_name(file_name) field.file_name(file_name)
@@ -287,9 +288,7 @@ pub(crate) struct Reader {
impl fmt::Debug for Reader { impl fmt::Debug for Reader {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Reader") f.debug_struct("Reader").field("form", &self.form).finish()
.field("form", &self.form)
.finish()
} }
} }
@@ -313,7 +312,10 @@ impl Reader {
let mut h = if !self.form.inner.computed_headers.is_empty() { let mut h = if !self.form.inner.computed_headers.is_empty() {
self.form.inner.computed_headers.remove(0) self.form.inner.computed_headers.remove(0)
} else { } else {
self.form.inner.percent_encoding.encode_headers(&name, field.metadata()) self.form
.inner
.percent_encoding
.encode_headers(&name, field.metadata())
}; };
h.extend_from_slice(b"\r\n\r\n"); h.extend_from_slice(b"\r\n\r\n");
h h
@@ -327,9 +329,10 @@ impl Reader {
if !self.form.inner.fields.is_empty() { if !self.form.inner.fields.is_empty() {
Some(Box::new(reader)) Some(Box::new(reader))
} else { } else {
Some(Box::new(reader.chain(Cursor::new( Some(Box::new(reader.chain(Cursor::new(format!(
format!("--{}--\r\n", self.form.boundary()), "--{}--\r\n",
)))) self.form.boundary()
)))))
} }
} else { } else {
None None
@@ -379,33 +382,28 @@ mod tests {
let mut form = Form::new() let mut form = Form::new()
.part("reader1", Part::reader(std::io::empty())) .part("reader1", Part::reader(std::io::empty()))
.part("key1", Part::text("value1")) .part("key1", Part::text("value1"))
.part( .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
"key2",
Part::text("value2").mime(mime::IMAGE_BMP),
)
.part("reader2", Part::reader(std::io::empty())) .part("reader2", Part::reader(std::io::empty()))
.part( .part("key3", Part::text("value3").file_name("filename"));
"key3",
Part::text("value3").file_name("filename"),
);
form.inner.boundary = "boundary".to_string(); form.inner.boundary = "boundary".to_string();
let length = form.compute_length(); let length = form.compute_length();
let expected = "--boundary\r\n\ let expected =
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\ "--boundary\r\n\
\r\n\ Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
--boundary\r\n\ \r\n\
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ --boundary\r\n\
value1\r\n\ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
--boundary\r\n\ value1\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\ --boundary\r\n\
Content-Type: image/bmp\r\n\r\n\ Content-Disposition: form-data; name=\"key2\"\r\n\
value2\r\n\ Content-Type: image/bmp\r\n\r\n\
--boundary\r\n\ value2\r\n\
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\ --boundary\r\n\
\r\n\ Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
--boundary\r\n\ \r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ --boundary\r\n\
value3\r\n--boundary--\r\n"; Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap(); form.reader().read_to_end(&mut output).unwrap();
// These prints are for debug purposes in case the test fails // These prints are for debug purposes in case the test fails
println!( println!(
@@ -422,26 +420,21 @@ mod tests {
let mut output = Vec::new(); let mut output = Vec::new();
let mut form = Form::new() let mut form = Form::new()
.text("key1", "value1") .text("key1", "value1")
.part( .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
"key2", .part("key3", Part::text("value3").file_name("filename"));
Part::text("value2").mime(mime::IMAGE_BMP),
)
.part(
"key3",
Part::text("value3").file_name("filename"),
);
form.inner.boundary = "boundary".to_string(); form.inner.boundary = "boundary".to_string();
let length = form.compute_length(); let length = form.compute_length();
let expected = "--boundary\r\n\ let expected =
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ "--boundary\r\n\
value1\r\n\ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
--boundary\r\n\ value1\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\ --boundary\r\n\
Content-Type: image/bmp\r\n\r\n\ Content-Disposition: form-data; name=\"key2\"\r\n\
value2\r\n\ Content-Type: image/bmp\r\n\r\n\
--boundary\r\n\ value2\r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ --boundary\r\n\
value3\r\n--boundary--\r\n"; Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap(); form.reader().read_to_end(&mut output).unwrap();
// These prints are for debug purposes in case the test fails // These prints are for debug purposes in case the test fails
println!( println!(

View File

@@ -1,16 +1,16 @@
use std::fmt; use std::fmt;
use std::sync::Arc;
#[cfg(feature = "socks")] #[cfg(feature = "socks")]
use std::net::{SocketAddr, ToSocketAddrs}; use std::net::{SocketAddr, ToSocketAddrs};
use std::sync::Arc;
use crate::{IntoUrl, Url};
use http::{header::HeaderValue, Uri}; use http::{header::HeaderValue, Uri};
use hyper::client::connect::Destination; use hyper::client::connect::Destination;
use url::percent_encoding::percent_decode;
use crate::{IntoUrl, Url};
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use std::error::Error; use std::error::Error;
use url::percent_encoding::percent_decode;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use winreg::enums::HKEY_CURRENT_USER; use winreg::enums::HKEY_CURRENT_USER;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@@ -98,7 +98,7 @@ impl Proxy {
/// ``` /// ```
pub fn http<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> { pub fn http<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
Ok(Proxy::new(Intercept::Http( Ok(Proxy::new(Intercept::Http(
proxy_scheme.into_proxy_scheme()? proxy_scheme.into_proxy_scheme()?,
))) )))
} }
@@ -118,7 +118,7 @@ impl Proxy {
/// ``` /// ```
pub fn https<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> { pub fn https<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
Ok(Proxy::new(Intercept::Https( Ok(Proxy::new(Intercept::Https(
proxy_scheme.into_proxy_scheme()? proxy_scheme.into_proxy_scheme()?,
))) )))
} }
@@ -138,7 +138,7 @@ impl Proxy {
/// ``` /// ```
pub fn all<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> { pub fn all<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
Ok(Proxy::new(Intercept::All( Ok(Proxy::new(Intercept::All(
proxy_scheme.into_proxy_scheme()? proxy_scheme.into_proxy_scheme()?,
))) )))
} }
@@ -163,12 +163,12 @@ impl Proxy {
/// # } /// # }
/// # fn main() {} /// # fn main() {}
pub fn custom<F, U: IntoProxyScheme>(fun: F) -> Proxy pub fn custom<F, U: IntoProxyScheme>(fun: F) -> Proxy
where F: Fn(&Url) -> Option<U> + Send + Sync + 'static { where
F: Fn(&Url) -> Option<U> + Send + Sync + 'static,
{
Proxy::new(Intercept::Custom(Custom { Proxy::new(Intercept::Custom(Custom {
auth: None, auth: None,
func: Arc::new(move |url| { func: Arc::new(move |url| fun(url).map(IntoProxyScheme::into_proxy_scheme)),
fun(url).map(IntoProxyScheme::into_proxy_scheme)
}),
})) }))
} }
@@ -179,9 +179,7 @@ impl Proxy {
*/ */
fn new(intercept: Intercept) -> Proxy { fn new(intercept: Intercept) -> Proxy {
Proxy { Proxy { intercept }
intercept,
}
} }
/// Set the `Proxy-Authorization` header using Basic auth. /// Set the `Proxy-Authorization` header using Basic auth.
@@ -214,15 +212,13 @@ impl Proxy {
pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> { pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
match self.intercept { match self.intercept {
Intercept::All(ProxyScheme::Http { ref auth, .. }) | Intercept::All(ProxyScheme::Http { ref auth, .. })
Intercept::Http(ProxyScheme::Http { ref auth, .. }) => auth.clone(), | Intercept::Http(ProxyScheme::Http { ref auth, .. }) => auth.clone(),
Intercept::Custom(ref custom) => { Intercept::Custom(ref custom) => custom.call(uri).and_then(|scheme| match scheme {
custom.call(uri).and_then(|scheme| match scheme { ProxyScheme::Http { auth, .. } => auth,
ProxyScheme::Http { auth, .. } => auth, #[cfg(feature = "socks")]
#[cfg(feature = "socks")] _ => None,
_ => None, }),
})
}
_ => None, _ => None,
} }
} }
@@ -236,14 +232,14 @@ impl Proxy {
} else { } else {
None None
} }
}, }
Intercept::Https(ref u) => { Intercept::Https(ref u) => {
if uri.scheme() == "https" { if uri.scheme() == "https" {
Some(u.clone()) Some(u.clone())
} else { } else {
None None
} }
}, }
Intercept::Custom(ref custom) => custom.call(uri), Intercept::Custom(ref custom) => custom.call(uri),
} }
} }
@@ -251,12 +247,8 @@ impl Proxy {
pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool { pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool {
match self.intercept { match self.intercept {
Intercept::All(_) => true, Intercept::All(_) => true,
Intercept::Http(_) => { Intercept::Http(_) => uri.scheme() == "http",
uri.scheme() == "http" Intercept::Https(_) => uri.scheme() == "https",
},
Intercept::Https(_) => {
uri.scheme() == "https"
},
Intercept::Custom(ref custom) => custom.call(uri).is_some(), Intercept::Custom(ref custom) => custom.call(uri).is_some(),
} }
} }
@@ -304,7 +296,11 @@ impl ProxyScheme {
} }
/// Use a username and password when connecting to the proxy server /// Use a username and password when connecting to the proxy server
fn with_basic_auth<T: Into<String>, U: Into<String>>(mut self, username: T, password: U) -> Self { fn with_basic_auth<T: Into<String>, U: Into<String>>(
mut self,
username: T,
password: U,
) -> Self {
self.set_basic_auth(username, password); self.set_basic_auth(username, password);
self self
} }
@@ -314,7 +310,7 @@ impl ProxyScheme {
ProxyScheme::Http { ref mut auth, .. } => { ProxyScheme::Http { ref mut auth, .. } => {
let header = encode_basic_auth(&username.into(), &password.into()); let header = encode_basic_auth(&username.into(), &password.into());
*auth = Some(header); *auth = Some(header);
}, }
#[cfg(feature = "socks")] #[cfg(feature = "socks")]
ProxyScheme::Socks5 { ref mut auth, .. } => { ProxyScheme::Socks5 { ref mut auth, .. } => {
*auth = Some((username.into(), password.into())); *auth = Some((username.into(), password.into()));
@@ -332,12 +328,10 @@ impl ProxyScheme {
let to_addr = || { let to_addr = || {
let host_and_port = try_!(url.with_default_port(|url| match url.scheme() { let host_and_port = try_!(url.with_default_port(|url| match url.scheme() {
"socks5" | "socks5h" => Ok(1080), "socks5" | "socks5h" => Ok(1080),
_ => Err(()) _ => Err(()),
})); }));
let mut addr = try_!(host_and_port.to_socket_addrs()); let mut addr = try_!(host_and_port.to_socket_addrs());
addr addr.next().ok_or_else(crate::error::unknown_proxy_scheme)
.next()
.ok_or_else(crate::error::unknown_proxy_scheme)
}; };
let mut scheme = match url.scheme() { let mut scheme = match url.scheme() {
@@ -346,7 +340,7 @@ impl ProxyScheme {
"socks5" => Self::socks5(to_addr()?)?, "socks5" => Self::socks5(to_addr()?)?,
#[cfg(feature = "socks")] #[cfg(feature = "socks")]
"socks5h" => Self::socks5h(to_addr()?)?, "socks5h" => Self::socks5h(to_addr()?)?,
_ => return Err(crate::error::unknown_proxy_scheme()) _ => return Err(crate::error::unknown_proxy_scheme()),
}; };
if let Some(pwd) = url.password() { if let Some(pwd) = url.password() {
@@ -359,8 +353,6 @@ impl ProxyScheme {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum Intercept { enum Intercept {
All(ProxyScheme), All(ProxyScheme),
@@ -372,9 +364,9 @@ enum Intercept {
impl Intercept { impl Intercept {
fn set_basic_auth(&mut self, username: &str, password: &str) { fn set_basic_auth(&mut self, username: &str, password: &str) {
match self { match self {
Intercept::All(ref mut s) | Intercept::All(ref mut s)
Intercept::Http(ref mut s) | | Intercept::Http(ref mut s)
Intercept::Https(ref mut s) => s.set_basic_auth(username, password), | Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
Intercept::Custom(ref mut custom) => { Intercept::Custom(ref mut custom) => {
let header = encode_basic_auth(username, password); let header = encode_basic_auth(username, password);
custom.auth = Some(header); custom.auth = Some(header);
@@ -397,9 +389,10 @@ impl Custom {
uri.scheme(), uri.scheme(),
uri.host(), uri.host(),
uri.port().map(|_| ":").unwrap_or(""), uri.port().map(|_| ":").unwrap_or(""),
uri.port().map(|p| p.to_string()).unwrap_or_default()) uri.port().map(|p| p.to_string()).unwrap_or_default()
.parse() )
.expect("should be valid Url"); .parse()
.expect("should be valid Url");
(self.func)(&url) (self.func)(&url)
.and_then(|result| result.ok()) .and_then(|result| result.ok())
@@ -413,7 +406,7 @@ impl Custom {
uri, uri,
} }
} }
}, }
#[cfg(feature = "socks")] #[cfg(feature = "socks")]
socks => socks, socks => socks,
}) })
@@ -467,8 +460,7 @@ impl Dst for Uri {
} }
fn host(&self) -> &str { fn host(&self) -> &str {
Uri::host(self) Uri::host(self).expect("<Uri as Dst>::host should have a str")
.expect("<Uri as Dst>::host should have a str")
} }
fn port(&self) -> Option<u16> { fn port(&self) -> Option<u16> {
@@ -499,8 +491,7 @@ pub fn get_proxies() -> HashMap<String, Url> {
proxies proxies
} }
fn insert_proxy(proxies: &mut HashMap<String, Url>, schema: String, addr: String) fn insert_proxy(proxies: &mut HashMap<String, Url>, schema: String, addr: String) {
{
if let Ok(valid_addr) = Url::parse(&addr) { if let Ok(valid_addr) = Url::parse(&addr) {
proxies.insert(schema, valid_addr); proxies.insert(schema, valid_addr);
} }
@@ -522,7 +513,6 @@ fn get_from_environment() -> HashMap<String, Url> {
proxies proxies
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn get_from_registry_impl() -> Result<HashMap<String, Url>, Box<dyn Error>> { fn get_from_registry_impl() -> Result<HashMap<String, Url>, Box<dyn Error>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER); let hkcu = RegKey::predef(HKEY_CURRENT_USER);
@@ -543,7 +533,11 @@ fn get_from_registry_impl() -> Result<HashMap<String, Url>, Box<dyn Error>> {
let protocol_parts: Vec<&str> = p.split("=").collect(); let protocol_parts: Vec<&str> = p.split("=").collect();
match protocol_parts.as_slice() { match protocol_parts.as_slice() {
[protocol, address] => { [protocol, address] => {
insert_proxy(&mut proxies, String::from(*protocol), String::from(*address)); insert_proxy(
&mut proxies,
String::from(*protocol),
String::from(*address),
);
} }
_ => { _ => {
// Contains invalid protocol setting, just break the loop // Contains invalid protocol setting, just break the loop
@@ -558,9 +552,21 @@ fn get_from_registry_impl() -> Result<HashMap<String, Url>, Box<dyn Error>> {
if proxy_server.starts_with("http:") { if proxy_server.starts_with("http:") {
insert_proxy(&mut proxies, String::from("http"), proxy_server); insert_proxy(&mut proxies, String::from("http"), proxy_server);
} else { } else {
insert_proxy(&mut proxies, String::from("http"), format!("http://{}", proxy_server)); insert_proxy(
insert_proxy(&mut proxies, String::from("https"), format!("https://{}", proxy_server)); &mut proxies,
insert_proxy(&mut proxies, String::from("ftp"), format!("https://{}", proxy_server)); String::from("http"),
format!("http://{}", proxy_server),
);
insert_proxy(
&mut proxies,
String::from("https"),
format!("https://{}", proxy_server),
);
insert_proxy(
&mut proxies,
String::from("ftp"),
format!("https://{}", proxy_server),
);
} }
} }
Ok(proxies) Ok(proxies)
@@ -581,8 +587,7 @@ mod tests {
} }
fn host(&self) -> &str { fn host(&self) -> &str {
Url::host_str(self) Url::host_str(self).expect("<Url as Dst>::host should have a str")
.expect("<Url as Dst>::host should have a str")
} }
fn port(&self) -> Option<u16> { fn port(&self) -> Option<u16> {
@@ -594,7 +599,6 @@ mod tests {
s.parse().unwrap() s.parse().unwrap()
} }
fn intercepted_uri(p: &Proxy, s: &str) -> Uri { fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
match p.intercept(&url(s)).unwrap() { match p.intercept(&url(s)).unwrap() {
ProxyScheme::Http { uri, .. } => uri, ProxyScheme::Http { uri, .. } => uri,
@@ -641,7 +645,6 @@ mod tests {
assert_eq!(intercepted_uri(&p, other), target); assert_eq!(intercepted_uri(&p, other), target);
} }
#[test] #[test]
fn test_custom() { fn test_custom() {
let target1 = "http://example.domain/"; let target1 = "http://example.domain/";
@@ -687,7 +690,7 @@ mod tests {
// reset user setting. // reset user setting.
match system_proxy { match system_proxy {
Err(_) => env::remove_var("http_proxy"), Err(_) => env::remove_var("http_proxy"),
Ok(proxy) => env::set_var("http_proxy", proxy) Ok(proxy) => env::set_var("http_proxy", proxy),
} }
} }
} }

View File

@@ -1,13 +1,6 @@
use std::fmt; use std::fmt;
use crate::header::{ use crate::header::{HeaderMap, AUTHORIZATION, COOKIE, PROXY_AUTHORIZATION, WWW_AUTHENTICATE};
HeaderMap,
AUTHORIZATION,
COOKIE,
PROXY_AUTHORIZATION,
WWW_AUTHENTICATE,
};
use hyper::StatusCode; use hyper::StatusCode;
use crate::Url; use crate::Url;
@@ -141,19 +134,13 @@ impl RedirectPolicy {
} }
} }
pub(crate) fn check( pub(crate) fn check(&self, status: StatusCode, next: &Url, previous: &[Url]) -> Action {
&self, self.redirect(RedirectAttempt {
status: StatusCode, status,
next: &Url, next,
previous: &[Url], previous,
) -> Action { })
self .inner
.redirect(RedirectAttempt {
status,
next,
previous,
})
.inner
} }
} }
@@ -239,11 +226,10 @@ pub(crate) enum Action {
TooManyRedirects, TooManyRedirects,
} }
pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, previous: &[Url]) { pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, previous: &[Url]) {
if let Some(previous) = previous.last() { if let Some(previous) = previous.last() {
let cross_host = next.host_str() != previous.host_str() || let cross_host = next.host_str() != previous.host_str()
next.port_or_known_default() != previous.port_or_known_default(); || next.port_or_known_default() != previous.port_or_known_default();
if cross_host { if cross_host {
headers.remove(AUTHORIZATION); headers.remove(AUTHORIZATION);
headers.remove(COOKIE); headers.remove(COOKIE);
@@ -304,21 +290,15 @@ fn test_redirect_policy_custom() {
}); });
let next = Url::parse("http://bar/baz").unwrap(); let next = Url::parse("http://bar/baz").unwrap();
assert_eq!( assert_eq!(policy.check(StatusCode::FOUND, &next, &[]), Action::Follow);
policy.check(StatusCode::FOUND, &next, &[]),
Action::Follow
);
let next = Url::parse("http://foo/baz").unwrap(); let next = Url::parse("http://foo/baz").unwrap();
assert_eq!( assert_eq!(policy.check(StatusCode::FOUND, &next, &[]), Action::Stop);
policy.check(StatusCode::FOUND, &next, &[]),
Action::Stop
);
} }
#[test] #[test]
fn test_remove_sensitive_headers() { fn test_remove_sensitive_headers() {
use hyper::header::{ACCEPT, AUTHORIZATION, COOKIE, HeaderValue}; use hyper::header::{HeaderValue, ACCEPT, AUTHORIZATION, COOKIE};
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("*/*")); headers.insert(ACCEPT, HeaderValue::from_static("*/*"));

View File

@@ -7,8 +7,8 @@ use serde_urlencoded;
use crate::body::{self, Body}; use crate::body::{self, Body};
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}; use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
use http::HttpTryFrom;
use crate::{async_impl, Client, Method, Url}; use crate::{async_impl, Client, Method, Url};
use http::HttpTryFrom;
/// A request which can be executed with `Client::execute()`. /// A request which can be executed with `Client::execute()`.
pub struct Request { pub struct Request {
@@ -119,10 +119,7 @@ impl Request {
impl RequestBuilder { impl RequestBuilder {
pub(crate) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder { pub(crate) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
RequestBuilder { RequestBuilder { client, request }
client,
request,
}
} }
/// Add a `Header` to this Request. /// Add a `Header` to this Request.
@@ -146,11 +143,11 @@ impl RequestBuilder {
let mut error = None; let mut error = None;
if let Ok(ref mut req) = self.request { if let Ok(ref mut req) = self.request {
match <HeaderName as HttpTryFrom<K>>::try_from(key) { match <HeaderName as HttpTryFrom<K>>::try_from(key) {
Ok(key) => { Ok(key) => match <HeaderValue as HttpTryFrom<V>>::try_from(value) {
match <HeaderValue as HttpTryFrom<V>>::try_from(value) { Ok(value) => {
Ok(value) => { req.headers_mut().append(key, 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())),
}, },
Err(e) => error = Some(crate::error::from(e.into())), Err(e) => error = Some(crate::error::from(e.into())),
}; };
@@ -236,7 +233,7 @@ impl RequestBuilder {
{ {
let auth = match password { let auth = match password {
Some(password) => format!("{}:{}", username, password), Some(password) => format!("{}:{}", username, password),
None => format!("{}:", username) None => format!("{}:", username),
}; };
let header_value = format!("Basic {}", encode(&auth)); let header_value = format!("Basic {}", encode(&auth));
self.header(crate::header::AUTHORIZATION, &*header_value) self.header(crate::header::AUTHORIZATION, &*header_value)
@@ -397,10 +394,10 @@ impl RequestBuilder {
Ok(body) => { Ok(body) => {
req.headers_mut().insert( req.headers_mut().insert(
CONTENT_TYPE, CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded") HeaderValue::from_static("application/x-www-form-urlencoded"),
); );
*req.body_mut() = Some(body.into()); *req.body_mut() = Some(body.into());
}, }
Err(err) => error = Some(crate::error::from(err)), Err(err) => error = Some(crate::error::from(err)),
} }
} }
@@ -440,12 +437,10 @@ impl RequestBuilder {
if let Ok(ref mut req) = self.request { if let Ok(ref mut req) = self.request {
match serde_json::to_vec(json) { match serde_json::to_vec(json) {
Ok(body) => { Ok(body) => {
req.headers_mut().insert( req.headers_mut()
CONTENT_TYPE, .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
HeaderValue::from_static("application/json")
);
*req.body_mut() = Some(body.into()); *req.body_mut() = Some(body.into());
}, }
Err(err) => error = Some(crate::error::from(err)), Err(err) => error = Some(crate::error::from(err)),
} }
} }
@@ -477,10 +472,7 @@ impl RequestBuilder {
pub fn multipart(self, mut multipart: crate::multipart::Form) -> RequestBuilder { pub fn multipart(self, mut multipart: crate::multipart::Form) -> RequestBuilder {
let mut builder = self.header( let mut builder = self.header(
CONTENT_TYPE, CONTENT_TYPE,
format!( format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
"multipart/form-data; boundary={}",
multipart.boundary()
).as_str()
); );
if let Ok(ref mut req) = builder.request { if let Ok(ref mut req) = builder.request {
*req.body_mut() = Some(match multipart.compute_length() { *req.body_mut() = Some(match multipart.compute_length() {
@@ -552,26 +544,27 @@ impl RequestBuilder {
/// # } /// # }
/// ``` /// ```
pub fn try_clone(&self) -> Option<RequestBuilder> { pub fn try_clone(&self) -> Option<RequestBuilder> {
self.request.as_ref() self.request
.as_ref()
.ok() .ok()
.and_then(|req| req.try_clone()) .and_then(|req| req.try_clone())
.map(|req| { .map(|req| RequestBuilder {
RequestBuilder{ client: self.client.clone(),
client: self.client.clone(), request: Ok(req),
request: Ok(req),
}
}) })
} }
} }
impl fmt::Debug for Request { impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_request_fields(&mut f.debug_struct("Request"), self) fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
.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()) f.field("method", req.method())
.field("url", req.url()) .field("url", req.url())
.field("headers", req.headers()) .field("headers", req.headers())
@@ -579,12 +572,12 @@ fn fmt_request_fields<'a, 'b>(f: &'a mut fmt::DebugStruct<'a, 'b>, req: &Request
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, HOST};
use crate::{body, Client, Method}; use crate::{body, Client, Method};
use crate::header::{ACCEPT, HOST, HeaderMap, HeaderValue, CONTENT_TYPE};
use std::collections::{BTreeMap, HashMap};
use serde::Serialize; use serde::Serialize;
use serde_json; use serde_json;
use serde_urlencoded; use serde_urlencoded;
use std::collections::{BTreeMap, HashMap};
#[test] #[test]
fn basic_get_request() { fn basic_get_request() {
@@ -755,7 +748,10 @@ mod tests {
let some_url = "https://google.com/"; let some_url = "https://google.com/";
let mut r = client.get(some_url); let mut r = client.get(some_url);
let params = Params { foo: "bar".into(), qux: 3 }; let params = Params {
foo: "bar".into(),
qux: 3,
};
r = r.query(&params); r = r.query(&params);
@@ -791,7 +787,10 @@ mod tests {
let mut r = r.form(&form_data).build().unwrap(); let mut r = r.form(&form_data).build().unwrap();
// Make sure the content type was set // Make sure the content type was set
assert_eq!(r.headers().get(CONTENT_TYPE).unwrap(), &"application/x-www-form-urlencoded"); assert_eq!(
r.headers().get(CONTENT_TYPE).unwrap(),
&"application/x-www-form-urlencoded"
);
let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap(); let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap();
@@ -821,15 +820,16 @@ mod tests {
#[test] #[test]
fn add_json_fail() { fn add_json_fail() {
use serde::{Serialize, Serializer};
use serde::ser::Error; use serde::ser::Error;
use serde::{Serialize, Serializer};
struct MyStruct; struct MyStruct;
impl Serialize for MyStruct { impl Serialize for MyStruct {
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer where
{ S: Serializer,
Err(S::Error::custom("nope")) {
} Err(S::Error::custom("nope"))
}
} }
let client = Client::new(); let client = Client::new();
@@ -858,11 +858,7 @@ mod tests {
assert_eq!(req.headers()["im-a"], "keeper"); assert_eq!(req.headers()["im-a"], "keeper");
let foo = req let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
.headers()
.get_all("foo")
.iter()
.collect::<Vec<_>>();
assert_eq!(foo.len(), 2); assert_eq!(foo.len(), 2);
assert_eq!(foo[0], "bar"); assert_eq!(foo[0], "bar");
assert_eq!(foo[1], "baz"); assert_eq!(foo[1], "baz");

View File

@@ -1,6 +1,6 @@
use std::mem;
use std::fmt; use std::fmt;
use std::io::{self, Read}; use std::io::{self, Read};
use std::mem;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::time::Duration; use std::time::Duration;
@@ -8,10 +8,10 @@ use futures::{Async, Poll, Stream};
use http; use http;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use crate::cookie;
use crate::client::KeepCoreThreadAlive; use crate::client::KeepCoreThreadAlive;
use crate::cookie;
use crate::{async_impl, wait, StatusCode, Url, Version};
use hyper::header::HeaderMap; use hyper::header::HeaderMap;
use crate::{async_impl, StatusCode, Url, Version, wait};
/// A Response to a submitted `Request`. /// A Response to a submitted `Request`.
pub struct Response { pub struct Response {
@@ -28,7 +28,11 @@ impl fmt::Debug for Response {
} }
impl Response { impl Response {
pub(crate) fn new(res: async_impl::Response, timeout: Option<Duration>, thread: KeepCoreThreadAlive) -> Response { pub(crate) fn new(
res: async_impl::Response,
timeout: Option<Duration>,
thread: KeepCoreThreadAlive,
) -> Response {
Response { Response {
inner: res, inner: res,
body: None, body: None,
@@ -116,12 +120,10 @@ impl Response {
/// Retrieve the cookies contained in the response. /// Retrieve the cookies contained in the response.
/// ///
/// Note that invalid 'Set-Cookie' headers will be ignored. /// Note that invalid 'Set-Cookie' headers will be ignored.
pub fn cookies<'a>(&'a self) -> impl Iterator< Item = cookie::Cookie<'a> > + 'a { pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
cookie::extract_response_cookies(self.headers()) cookie::extract_response_cookies(self.headers()).filter_map(Result::ok)
.filter_map(Result::ok)
} }
/// Get the HTTP `Version` of this `Response`. /// Get the HTTP `Version` of this `Response`.
#[inline] #[inline]
pub fn version(&self) -> Version { pub fn version(&self) -> Version {
@@ -202,12 +204,10 @@ impl Response {
/// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
#[inline] #[inline]
pub fn json<T: DeserializeOwned>(&mut self) -> crate::Result<T> { pub fn json<T: DeserializeOwned>(&mut self) -> crate::Result<T> {
wait::timeout(self.inner.json(), self.timeout).map_err(|e| { wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e {
match e { wait::Waited::TimedOut => crate::error::timedout(None),
wait::Waited::TimedOut => crate::error::timedout(None), wait::Waited::Executor(e) => crate::error::from(e),
wait::Waited::Executor(e) => crate::error::from(e), wait::Waited::Inner(e) => e,
wait::Waited::Inner(e) => e,
}
}) })
} }
@@ -291,7 +291,8 @@ impl Response {
/// ``` /// ```
#[inline] #[inline]
pub fn copy_to<W: ?Sized>(&mut self, w: &mut W) -> crate::Result<u64> pub fn copy_to<W: ?Sized>(&mut self, w: &mut W) -> crate::Result<u64>
where W: io::Write where
W: io::Write,
{ {
io::copy(self, w).map_err(crate::error::from) io::copy(self, w).map_err(crate::error::from)
} }
@@ -314,14 +315,17 @@ impl Response {
/// ``` /// ```
#[inline] #[inline]
pub fn error_for_status(self) -> crate::Result<Self> { pub fn error_for_status(self) -> crate::Result<Self> {
let Response { body, inner, timeout, _thread_handle } = self; let Response {
inner.error_for_status().map(move |inner| { body,
Response { inner,
inner, timeout,
body, _thread_handle,
timeout, } = self;
_thread_handle, inner.error_for_status().map(move |inner| Response {
} inner,
body,
timeout,
_thread_handle,
}) })
} }
@@ -353,7 +357,7 @@ impl Read for Response {
if self.body.is_none() { if self.body.is_none() {
let body = mem::replace(self.inner.body_mut(), async_impl::Decoder::empty()); let body = mem::replace(self.inner.body_mut(), async_impl::Decoder::empty());
let body = async_impl::ReadableChunks::new(WaitBody { let body = async_impl::ReadableChunks::new(WaitBody {
inner: wait::stream(body, self.timeout) inner: wait::stream(body, self.timeout),
}); });
self.body = Some(body); self.body = Some(body);
} }
@@ -365,7 +369,7 @@ impl Read for Response {
} }
struct WaitBody { struct WaitBody {
inner: wait::WaitStream<async_impl::Decoder> inner: wait::WaitStream<async_impl::Decoder>,
} }
impl Stream for WaitBody { impl Stream for WaitBody {
@@ -383,7 +387,7 @@ impl Stream for WaitBody {
}; };
Err(req_err) Err(req_err)
}, }
None => Ok(Async::Ready(None)), None => Ok(Async::Ready(None)),
} }
} }

View File

@@ -1,6 +1,6 @@
use std::fmt;
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
use rustls::{TLSError, ServerCertVerifier, RootCertStore, ServerCertVerified}; use rustls::{RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError};
use std::fmt;
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
use tokio_rustls::webpki::DNSNameRef; use tokio_rustls::webpki::DNSNameRef;
@@ -17,7 +17,7 @@ pub struct Certificate {
#[derive(Clone)] #[derive(Clone)]
enum Cert { enum Cert {
Der(Vec<u8>), Der(Vec<u8>),
Pem(Vec<u8>) Pem(Vec<u8>),
} }
/// Represent a private key and X509 cert as a client certificate. /// Represent a private key and X509 cert as a client certificate.
@@ -32,7 +32,7 @@ enum ClientCert {
Pem { Pem {
key: rustls::PrivateKey, key: rustls::PrivateKey,
certs: Vec<rustls::Certificate>, certs: Vec<rustls::Certificate>,
} },
} }
impl Certificate { impl Certificate {
@@ -61,7 +61,6 @@ impl Certificate {
}) })
} }
/// Create a `Certificate` from a PEM encoded certificate /// Create a `Certificate` from a PEM encoded certificate
/// ///
/// # Examples /// # Examples
@@ -83,36 +82,32 @@ impl Certificate {
#[cfg(feature = "default-tls")] #[cfg(feature = "default-tls")]
native: try_!(native_tls::Certificate::from_pem(pem)), native: try_!(native_tls::Certificate::from_pem(pem)),
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
original: Cert::Pem(pem.to_owned()) original: Cert::Pem(pem.to_owned()),
}) })
} }
#[cfg(feature = "default-tls")] #[cfg(feature = "default-tls")]
pub(crate) fn add_to_native_tls( pub(crate) fn add_to_native_tls(self, tls: &mut native_tls::TlsConnectorBuilder) {
self,
tls: &mut native_tls::TlsConnectorBuilder,
) {
tls.add_root_certificate(self.native); tls.add_root_certificate(self.native);
} }
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
pub(crate) fn add_to_rustls( pub(crate) fn add_to_rustls(self, tls: &mut rustls::ClientConfig) -> crate::Result<()> {
self,
tls: &mut rustls::ClientConfig,
) -> crate::Result<()> {
use std::io::Cursor;
use rustls::internal::pemfile; use rustls::internal::pemfile;
use std::io::Cursor;
match self.original { match self.original {
Cert::Der(buf) => try_!(tls.root_store.add(&::rustls::Certificate(buf)) Cert::Der(buf) => try_!(tls
.root_store
.add(&::rustls::Certificate(buf))
.map_err(TLSError::WebPKIError)), .map_err(TLSError::WebPKIError)),
Cert::Pem(buf) => { Cert::Pem(buf) => {
let mut pem = Cursor::new(buf); let mut pem = Cursor::new(buf);
let certs = try_!(pemfile::certs(&mut pem) let certs = try_!(pemfile::certs(&mut pem).map_err(|_| TLSError::General(
.map_err(|_| TLSError::General(String::from("No valid certificate was found")))); String::from("No valid certificate was found")
)));
for c in certs { for c in certs {
try_!(tls.root_store.add(&c) try_!(tls.root_store.add(&c).map_err(TLSError::WebPKIError));
.map_err(TLSError::WebPKIError));
} }
} }
} }
@@ -151,9 +146,7 @@ impl Identity {
#[cfg(feature = "default-tls")] #[cfg(feature = "default-tls")]
pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> { pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> {
Ok(Identity { Ok(Identity {
inner: ClientCert::Pkcs12( inner: ClientCert::Pkcs12(try_!(native_tls::Identity::from_pkcs12(der, password))),
try_!(native_tls::Identity::from_pkcs12(der, password))
),
}) })
} }
@@ -178,8 +171,8 @@ impl Identity {
/// ``` /// ```
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> { pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> {
use std::io::Cursor;
use rustls::internal::pemfile; use rustls::internal::pemfile;
use std::io::Cursor;
let (key, certs) = { let (key, certs) = {
let mut pem = Cursor::new(buf); let mut pem = Cursor::new(buf);
@@ -202,15 +195,14 @@ impl Identity {
if let (Some(sk), false) = (sk.pop(), certs.is_empty()) { if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
(sk, certs) (sk, certs)
} else { } else {
return Err(crate::error::from(TLSError::General(String::from("private key or certificate not found")))); return Err(crate::error::from(TLSError::General(String::from(
"private key or certificate not found",
))));
} }
}; };
Ok(Identity { Ok(Identity {
inner: ClientCert::Pem { inner: ClientCert::Pem { key, certs },
key,
certs,
},
}) })
} }
@@ -223,39 +215,34 @@ impl Identity {
ClientCert::Pkcs12(id) => { ClientCert::Pkcs12(id) => {
tls.identity(id); tls.identity(id);
Ok(()) Ok(())
}, }
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
ClientCert::Pem { .. } => Err(crate::error::from(crate::error::Kind::TlsIncompatible)) ClientCert::Pem { .. } => Err(crate::error::from(crate::error::Kind::TlsIncompatible)),
} }
} }
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
pub(crate) fn add_to_rustls( pub(crate) fn add_to_rustls(self, tls: &mut rustls::ClientConfig) -> crate::Result<()> {
self,
tls: &mut rustls::ClientConfig,
) -> crate::Result<()> {
match self.inner { match self.inner {
ClientCert::Pem { key, certs } => { ClientCert::Pem { key, certs } => {
tls.set_single_client_cert(certs, key); tls.set_single_client_cert(certs, key);
Ok(()) Ok(())
}, }
#[cfg(feature = "default-tls")] #[cfg(feature = "default-tls")]
ClientCert::Pkcs12(..) => Err(crate::error::from(crate::error::Kind::TlsIncompatible)) ClientCert::Pkcs12(..) => Err(crate::error::from(crate::error::Kind::TlsIncompatible)),
} }
} }
} }
impl fmt::Debug for Certificate { impl fmt::Debug for Certificate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Certificate") f.debug_struct("Certificate").finish()
.finish()
} }
} }
impl fmt::Debug for Identity { impl fmt::Debug for Identity {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Identity") f.debug_struct("Identity").finish()
.finish()
} }
} }
@@ -263,16 +250,20 @@ pub(crate) enum TlsBackend {
#[cfg(feature = "default-tls")] #[cfg(feature = "default-tls")]
Default, Default,
#[cfg(feature = "rustls-tls")] #[cfg(feature = "rustls-tls")]
Rustls Rustls,
} }
impl Default for TlsBackend { impl Default for TlsBackend {
fn default() -> TlsBackend { fn default() -> TlsBackend {
#[cfg(feature = "default-tls")] #[cfg(feature = "default-tls")]
{ TlsBackend::Default } {
TlsBackend::Default
}
#[cfg(all(feature = "rustls-tls", not(feature = "default-tls")))] #[cfg(all(feature = "rustls-tls", not(feature = "default-tls")))]
{ TlsBackend::Rustls } {
TlsBackend::Rustls
}
} }
} }
@@ -286,7 +277,7 @@ impl ServerCertVerifier for NoVerifier {
_roots: &RootCertStore, _roots: &RootCertStore,
_presented_certs: &[rustls::Certificate], _presented_certs: &[rustls::Certificate],
_dns_name: DNSNameRef, _dns_name: DNSNameRef,
_ocsp_response: &[u8] _ocsp_response: &[u8],
) -> Result<ServerCertVerified, TLSError> { ) -> Result<ServerCertVerified, TLSError> {
Ok(ServerCertVerified::assertion()) Ok(ServerCertVerified::assertion())
} }

View File

@@ -2,8 +2,8 @@ use std::sync::Arc;
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use futures::{Async, Future, Poll, Stream};
use futures::executor::{self, Notify}; use futures::executor::{self, Notify};
use futures::{Async, Future, Poll, Stream};
use tokio_executor::{enter, EnterError}; use tokio_executor::{enter, EnterError};
pub(crate) fn timeout<F>(fut: F, timeout: Option<Duration>) -> Result<F::Item, Waited<F::Error>> pub(crate) fn timeout<F>(fut: F, timeout: Option<Duration>) -> Result<F::Item, Waited<F::Error>>
@@ -11,13 +11,13 @@ where
F: Future, F: Future,
{ {
let mut spawn = executor::spawn(fut); let mut spawn = executor::spawn(fut);
block_on(timeout, |notify| { block_on(timeout, |notify| spawn.poll_future_notify(notify, 0))
spawn.poll_future_notify(notify, 0)
})
} }
pub(crate) fn stream<S>(stream: S, timeout: Option<Duration>) -> WaitStream<S> pub(crate) fn stream<S>(stream: S, timeout: Option<Duration>) -> WaitStream<S>
where S: Stream { where
S: Stream,
{
WaitStream { WaitStream {
stream: executor::spawn(stream), stream: executor::spawn(stream),
timeout, timeout,
@@ -43,7 +43,9 @@ pub(crate) struct WaitStream<S> {
} }
impl<S> Iterator for WaitStream<S> impl<S> Iterator for WaitStream<S>
where S: Stream { where
S: Stream,
{
type Item = Result<S::Item, Waited<S::Error>>; type Item = Result<S::Item, Waited<S::Error>>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@@ -74,9 +76,7 @@ where
F: FnMut(&Arc<ThreadNotify>) -> Poll<U, E>, F: FnMut(&Arc<ThreadNotify>) -> Poll<U, E>,
{ {
let _entered = enter().map_err(Waited::Executor)?; let _entered = enter().map_err(Waited::Executor)?;
let deadline = timeout.map(|d| { let deadline = timeout.map(|d| Instant::now() + d);
Instant::now() + d
});
let notify = Arc::new(ThreadNotify { let notify = Arc::new(ThreadNotify {
thread: thread::current(), thread: thread::current(),
}); });
@@ -99,5 +99,3 @@ where
} }
} }
} }

View File

@@ -1,10 +1,3 @@
extern crate futures;
extern crate libflate;
extern crate reqwest;
extern crate hyper;
extern crate tokio;
extern crate bytes;
#[macro_use] #[macro_use]
mod support; mod support;
@@ -14,8 +7,8 @@ use std::time::Duration;
use futures::{Future, Stream}; use futures::{Future, Stream};
use tokio::runtime::current_thread::Runtime; use tokio::runtime::current_thread::Runtime;
use reqwest::r#async::{Chunk, Client};
use reqwest::r#async::multipart::{Form, Part}; use reqwest::r#async::multipart::{Form, Part};
use reqwest::r#async::{Chunk, Client};
use bytes::Bytes; use bytes::Bytes;
@@ -54,7 +47,8 @@ fn response_text() {
let client = Client::new(); let client = Client::new();
let res_future = client.get(&format!("http://{}/text", server.addr())) let res_future = client
.get(&format!("http://{}/text", server.addr()))
.send() .send()
.and_then(|mut res| res.text()) .and_then(|mut res| res.text())
.and_then(|text| { .and_then(|text| {
@@ -90,7 +84,8 @@ fn response_json() {
let client = Client::new(); let client = Client::new();
let res_future = client.get(&format!("http://{}/json", server.addr())) let res_future = client
.get(&format!("http://{}/json", server.addr()))
.send() .send()
.and_then(|mut res| res.json::<String>()) .and_then(|mut res| res.json::<String>())
.and_then(|text| { .and_then(|text| {
@@ -105,34 +100,36 @@ fn response_json() {
fn multipart() { fn multipart() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let stream = futures::stream::once::<_, hyper::Error>(Ok(Chunk::from("part1 part2".to_owned()))); let stream =
futures::stream::once::<_, hyper::Error>(Ok(Chunk::from("part1 part2".to_owned())));
let part = Part::stream(stream); let part = Part::stream(stream);
let form = Form::new() let form = Form::new().text("foo", "bar").part("part_stream", part);
.text("foo", "bar")
.part("part_stream", part);
let expected_body = format!("\ let expected_body = format!(
24\r\n\ "\
--{0}\r\n\r\n\ 24\r\n\
2E\r\n\ --{0}\r\n\r\n\
Content-Disposition: form-data; name=\"foo\"\r\n\r\n\r\n\ 2E\r\n\
3\r\n\ Content-Disposition: form-data; name=\"foo\"\r\n\r\n\r\n\
bar\r\n\ 3\r\n\
2\r\n\ bar\r\n\
\r\n\r\n\ 2\r\n\
24\r\n\ \r\n\r\n\
--{0}\r\n\r\n\ 24\r\n\
36\r\n\ --{0}\r\n\r\n\
Content-Disposition: form-data; name=\"part_stream\"\r\n\r\n\r\n\ 36\r\n\
B\r\n\ Content-Disposition: form-data; name=\"part_stream\"\r\n\r\n\r\n\
part1 part2\r\n\ B\r\n\
2\r\n\ part1 part2\r\n\
\r\n\r\n\ 2\r\n\
26\r\n\ \r\n\r\n\
--{0}--\r\n\r\n\ 26\r\n\
0\r\n\r\n\ --{0}--\r\n\r\n\
", form.boundary()); 0\r\n\r\n\
",
form.boundary()
);
let server = server! { let server = server! {
request: format!("\ request: format!("\
@@ -160,15 +157,12 @@ fn multipart() {
let client = Client::new(); let client = Client::new();
let res_future = client.post(&url) let res_future = client.post(&url).multipart(form).send().and_then(|res| {
.multipart(form) assert_eq!(res.url().as_str(), &url);
.send() assert_eq!(res.status(), reqwest::StatusCode::OK);
.and_then(|res| {
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
Ok(()) Ok(())
}); });
rt.block_on(res_future).unwrap(); rt.block_on(res_future).unwrap();
} }
@@ -203,9 +197,7 @@ fn request_timeout() {
.unwrap(); .unwrap();
let url = format!("http://{}/slow", server.addr()); let url = format!("http://{}/slow", server.addr());
let fut = client let fut = client.get(&url).send();
.get(&url)
.send();
let err = rt.block_on(fut).unwrap_err(); let err = rt.block_on(fut).unwrap_err();
@@ -254,7 +246,10 @@ fn response_timeout() {
} }
fn gzip_case(response_size: usize, chunk_size: usize) { fn gzip_case(response_size: usize, chunk_size: usize) {
let content: String = (0..response_size).into_iter().map(|i| format!("test {}", i)).collect(); let content: String = (0..response_size)
.into_iter()
.map(|i| format!("test {}", i))
.collect();
let mut encoder = libflate::gzip::Encoder::new(Vec::new()).unwrap(); let mut encoder = libflate::gzip::Encoder::new(Vec::new()).unwrap();
match encoder.write(content.as_bytes()) { match encoder.write(content.as_bytes()) {
Ok(n) => assert!(n > 0, "Failed to write to encoder."), Ok(n) => assert!(n > 0, "Failed to write to encoder."),
@@ -263,13 +258,16 @@ fn gzip_case(response_size: usize, chunk_size: usize) {
let gzipped_content = encoder.finish().into_result().unwrap(); let gzipped_content = encoder.finish().into_result().unwrap();
let mut response = format!("\ let mut response = format!(
HTTP/1.1 200 OK\r\n\ "\
Server: test-accept\r\n\ HTTP/1.1 200 OK\r\n\
Content-Encoding: gzip\r\n\ Server: test-accept\r\n\
Content-Length: {}\r\n\ Content-Encoding: gzip\r\n\
\r\n", &gzipped_content.len()) Content-Length: {}\r\n\
.into_bytes(); \r\n",
&gzipped_content.len()
)
.into_bytes();
response.extend(&gzipped_content); response.extend(&gzipped_content);
let server = server! { let server = server! {
@@ -290,7 +288,8 @@ fn gzip_case(response_size: usize, chunk_size: usize) {
let client = Client::new(); let client = Client::new();
let res_future = client.get(&format!("http://{}/gzip", server.addr())) let res_future = client
.get(&format!("http://{}/gzip", server.addr()))
.send() .send()
.and_then(|res| { .and_then(|res| {
let body = res.into_body(); let body = res.into_body();
@@ -311,9 +310,11 @@ fn gzip_case(response_size: usize, chunk_size: usize) {
fn body_stream() { fn body_stream() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let source: Box<dyn Stream<Item = Bytes, Error = io::Error> + Send> let source: Box<dyn Stream<Item = Bytes, Error = io::Error> + Send> =
= Box::new(futures::stream::iter_ok::<_, io::Error>( Box::new(futures::stream::iter_ok::<_, io::Error>(vec![
vec![Bytes::from_static(b"123"), Bytes::from_static(b"4567")])); Bytes::from_static(b"123"),
Bytes::from_static(b"4567"),
]));
let expected_body = "3\r\n123\r\n4\r\n4567\r\n0\r\n\r\n"; let expected_body = "3\r\n123\r\n4\r\n4567\r\n0\r\n\r\n";
@@ -342,15 +343,12 @@ fn body_stream() {
let client = Client::new(); let client = Client::new();
let res_future = client.post(&url) let res_future = client.post(&url).body(source).send().and_then(|res| {
.body(source) assert_eq!(res.url().as_str(), &url);
.send() assert_eq!(res.status(), reqwest::StatusCode::OK);
.and_then(|res| {
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
Ok(()) Ok(())
}); });
rt.block_on(res_future).unwrap(); rt.block_on(res_future).unwrap();
} }

View File

@@ -1,11 +1,10 @@
extern crate reqwest;
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
#[test] #[test]
fn test_badssl_modern() { fn test_badssl_modern() {
let text = reqwest::get("https://mozilla-modern.badssl.com/").unwrap() let text = reqwest::get("https://mozilla-modern.badssl.com/")
.text().unwrap(); .unwrap()
.text()
.unwrap();
assert!(text.contains("<title>mozilla-modern.badssl.com</title>")); assert!(text.contains("<title>mozilla-modern.badssl.com</title>"));
} }
@@ -15,10 +14,13 @@ fn test_badssl_modern() {
fn test_rustls_badssl_modern() { fn test_rustls_badssl_modern() {
let text = reqwest::Client::builder() let text = reqwest::Client::builder()
.use_rustls_tls() .use_rustls_tls()
.build().unwrap() .build()
.unwrap()
.get("https://mozilla-modern.badssl.com/") .get("https://mozilla-modern.badssl.com/")
.send().unwrap() .send()
.text().unwrap(); .unwrap()
.text()
.unwrap();
assert!(text.contains("<title>mozilla-modern.badssl.com</title>")); assert!(text.contains("<title>mozilla-modern.badssl.com</title>"));
} }
@@ -28,10 +30,13 @@ fn test_rustls_badssl_modern() {
fn test_badssl_self_signed() { fn test_badssl_self_signed() {
let text = reqwest::Client::builder() let text = reqwest::Client::builder()
.danger_accept_invalid_certs(true) .danger_accept_invalid_certs(true)
.build().unwrap() .build()
.unwrap()
.get("https://self-signed.badssl.com/") .get("https://self-signed.badssl.com/")
.send().unwrap() .send()
.text().unwrap(); .unwrap()
.text()
.unwrap();
assert!(text.contains("<title>self-signed.badssl.com</title>")); assert!(text.contains("<title>self-signed.badssl.com</title>"));
} }
@@ -41,17 +46,20 @@ fn test_badssl_self_signed() {
fn test_badssl_wrong_host() { fn test_badssl_wrong_host() {
let text = reqwest::Client::builder() let text = reqwest::Client::builder()
.danger_accept_invalid_hostnames(true) .danger_accept_invalid_hostnames(true)
.build().unwrap() .build()
.unwrap()
.get("https://wrong.host.badssl.com/") .get("https://wrong.host.badssl.com/")
.send().unwrap() .send()
.text().unwrap(); .unwrap()
.text()
.unwrap();
assert!(text.contains("<title>wrong.host.badssl.com</title>")); assert!(text.contains("<title>wrong.host.badssl.com</title>"));
let result = reqwest::Client::builder() let result = reqwest::Client::builder()
.danger_accept_invalid_hostnames(true) .danger_accept_invalid_hostnames(true)
.build().unwrap() .build()
.unwrap()
.get("https://self-signed.badssl.com/") .get("https://self-signed.badssl.com/")
.send(); .send();

View File

@@ -1,5 +1,3 @@
extern crate reqwest;
#[macro_use] #[macro_use]
mod support; mod support;
@@ -30,7 +28,10 @@ fn test_response_text() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"5"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"5"
);
let body = res.text().unwrap(); let body = res.text().unwrap();
assert_eq!(b"Hello", body.as_bytes()); assert_eq!(b"Hello", body.as_bytes());
@@ -62,11 +63,14 @@ fn test_response_non_utf_8_text() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"4"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"4"
);
let body = res.text().unwrap(); let body = res.text().unwrap();
assert_eq!("你好", &body); assert_eq!("你好", &body);
assert_eq!(b"\xe4\xbd\xa0\xe5\xa5\xbd", body.as_bytes()); // Now it's utf-8 assert_eq!(b"\xe4\xbd\xa0\xe5\xa5\xbd", body.as_bytes()); // Now it's utf-8
} }
#[test] #[test]
@@ -94,7 +98,10 @@ fn test_response_json() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"7"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"7"
);
let body = res.json::<String>().unwrap(); let body = res.json::<String>().unwrap();
assert_eq!("Hello", body); assert_eq!("Hello", body);
@@ -125,7 +132,10 @@ fn test_response_copy_to() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"5"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"5"
);
let mut buf: Vec<u8> = vec![]; let mut buf: Vec<u8> = vec![];
res.copy_to(&mut buf).unwrap(); res.copy_to(&mut buf).unwrap();
@@ -157,7 +167,10 @@ fn test_get() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"0"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
assert_eq!(res.remote_addr(), Some(server.addr())); assert_eq!(res.remote_addr(), Some(server.addr()));
let mut buf = [0; 1024]; let mut buf = [0; 1024];
@@ -196,7 +209,10 @@ fn test_post() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"post"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"post");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"0"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
let mut buf = [0; 1024]; let mut buf = [0; 1024];
let n = res.read(&mut buf).unwrap(); let n = res.read(&mut buf).unwrap();
@@ -293,7 +309,10 @@ fn test_error_for_status_5xx() {
let err = res.error_for_status().err().unwrap(); let err = res.error_for_status().err().unwrap();
assert!(err.is_server_error()); assert!(err.is_server_error());
assert_eq!(err.status(), Some(reqwest::StatusCode::INTERNAL_SERVER_ERROR)); assert_eq!(
err.status(),
Some(reqwest::StatusCode::INTERNAL_SERVER_ERROR)
);
} }
#[test] #[test]
@@ -303,7 +322,8 @@ fn test_default_headers() {
headers.insert(header::COOKIE, header::HeaderValue::from_static("a=b;c=d")); headers.insert(header::COOKIE, header::HeaderValue::from_static("a=b;c=d"));
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
.default_headers(headers) .default_headers(headers)
.build().unwrap(); .build()
.unwrap();
let server = server! { let server = server! {
request: b"\ request: b"\
@@ -329,7 +349,10 @@ fn test_default_headers() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"0"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
let server = server! { let server = server! {
request: b"\ request: b"\
@@ -355,17 +378,24 @@ fn test_default_headers() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"0"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
} }
#[test] #[test]
fn test_override_default_headers() { fn test_override_default_headers() {
use reqwest::header; use reqwest::header;
let mut headers = header::HeaderMap::with_capacity(1); let mut headers = header::HeaderMap::with_capacity(1);
headers.insert(header::AUTHORIZATION, header::HeaderValue::from_static("iamatoken")); headers.insert(
header::AUTHORIZATION,
header::HeaderValue::from_static("iamatoken"),
);
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
.default_headers(headers) .default_headers(headers)
.build().unwrap(); .build()
.unwrap();
let server = server! { let server = server! {
request: b"\ request: b"\
@@ -386,13 +416,22 @@ fn test_override_default_headers() {
}; };
let url = format!("http://{}/3", server.addr()); let url = format!("http://{}/3", server.addr());
let res = client.get(&url).header(header::AUTHORIZATION, header::HeaderValue::from_static("secret")).send().unwrap(); let res = client
.get(&url)
.header(
header::AUTHORIZATION,
header::HeaderValue::from_static("secret"),
)
.send()
.unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"0"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
} }
#[test] #[test]
@@ -418,20 +457,32 @@ fn test_appended_headers_not_overwritten() {
}; };
let url = format!("http://{}/4", server.addr()); let url = format!("http://{}/4", server.addr());
let res = client.get(&url).header(header::ACCEPT, "application/json").header(header::ACCEPT, "application/json+hal").send().unwrap(); let res = client
.get(&url)
.header(header::ACCEPT, "application/json")
.header(header::ACCEPT, "application/json+hal")
.send()
.unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"0"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
// make sure this also works with default headers // make sure this also works with default headers
use reqwest::header; use reqwest::header;
let mut headers = header::HeaderMap::with_capacity(1); let mut headers = header::HeaderMap::with_capacity(1);
headers.insert(header::ACCEPT, header::HeaderValue::from_static("text/html")); headers.insert(
header::ACCEPT,
header::HeaderValue::from_static("text/html"),
);
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
.default_headers(headers) .default_headers(headers)
.build().unwrap(); .build()
.unwrap();
let server = server! { let server = server! {
request: b"\ request: b"\
@@ -452,10 +503,18 @@ fn test_appended_headers_not_overwritten() {
}; };
let url = format!("http://{}/4", server.addr()); let url = format!("http://{}/4", server.addr());
let res = client.get(&url).header(header::ACCEPT, "application/json").header(header::ACCEPT, "application/json+hal").send().unwrap(); let res = client
.get(&url)
.header(header::ACCEPT, "application/json")
.header(header::ACCEPT, "application/json+hal")
.send()
.unwrap();
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test");
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"0"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"0"
);
} }

View File

@@ -1,5 +1,3 @@
extern crate reqwest;
#[macro_use] #[macro_use]
mod support; mod support;
@@ -45,7 +43,7 @@ fn cookie_response_accessor() {
// expires // expires
assert_eq!(cookies[1].name(), "expires"); assert_eq!(cookies[1].name(), "expires");
assert_eq!( assert_eq!(
cookies[1].expires().unwrap(), cookies[1].expires().unwrap(),
std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1445412480) std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1445412480)
); );
@@ -55,7 +53,10 @@ fn cookie_response_accessor() {
// max-age // max-age
assert_eq!(cookies[3].name(), "maxage"); assert_eq!(cookies[3].name(), "maxage");
assert_eq!(cookies[3].max_age().unwrap(), std::time::Duration::from_secs(100)); assert_eq!(
cookies[3].max_age().unwrap(),
std::time::Duration::from_secs(100)
);
// domain // domain
assert_eq!(cookies[4].name(), "domain"); assert_eq!(cookies[4].name(), "domain");
@@ -81,7 +82,10 @@ fn cookie_response_accessor() {
#[test] #[test]
fn cookie_store_simple() { fn cookie_store_simple() {
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 client = reqwest::r#async::Client::builder().cookie_store(true).build().unwrap(); let client = reqwest::r#async::Client::builder()
.cookie_store(true)
.build()
.unwrap();
let server = server! { let server = server! {
request: b"\ request: b"\
@@ -125,7 +129,10 @@ fn cookie_store_simple() {
#[test] #[test]
fn cookie_store_overwrite_existing() { fn cookie_store_overwrite_existing() {
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 client = reqwest::r#async::Client::builder().cookie_store(true).build().unwrap(); let client = reqwest::r#async::Client::builder()
.cookie_store(true)
.build()
.unwrap();
let server = server! { let server = server! {
request: b"\ request: b"\
@@ -189,7 +196,10 @@ fn cookie_store_overwrite_existing() {
#[test] #[test]
fn cookie_store_max_age() { fn cookie_store_max_age() {
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 client = reqwest::r#async::Client::builder().cookie_store(true).build().unwrap(); let client = reqwest::r#async::Client::builder()
.cookie_store(true)
.build()
.unwrap();
let server = server! { let server = server! {
request: b"\ request: b"\
@@ -232,7 +242,10 @@ fn cookie_store_max_age() {
#[test] #[test]
fn cookie_store_expires() { fn cookie_store_expires() {
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 client = reqwest::r#async::Client::builder().cookie_store(true).build().unwrap(); let client = reqwest::r#async::Client::builder()
.cookie_store(true)
.build()
.unwrap();
let server = server! { let server = server! {
request: b"\ request: b"\
@@ -275,7 +288,10 @@ fn cookie_store_expires() {
#[test] #[test]
fn cookie_store_path() { fn cookie_store_path() {
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 client = reqwest::r#async::Client::builder().cookie_store(true).build().unwrap(); let client = reqwest::r#async::Client::builder()
.cookie_store(true)
.build()
.unwrap();
let server = server! { let server = server! {
request: b"\ request: b"\

View File

@@ -1,11 +1,8 @@
extern crate reqwest;
extern crate libflate;
#[macro_use] #[macro_use]
mod support; mod support;
use std::time::Duration;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::time::Duration;
#[test] #[test]
fn test_gzip_response() { fn test_gzip_response() {
@@ -19,13 +16,16 @@ fn test_gzip_response() {
let gzipped_content = encoder.finish().into_result().unwrap(); let gzipped_content = encoder.finish().into_result().unwrap();
let mut response = format!("\ let mut response = format!(
HTTP/1.1 200 OK\r\n\ "\
Server: test-accept\r\n\ HTTP/1.1 200 OK\r\n\
Content-Encoding: gzip\r\n\ Server: test-accept\r\n\
Content-Length: {}\r\n\ Content-Encoding: gzip\r\n\
\r\n", &gzipped_content.len()) Content-Length: {}\r\n\
.into_bytes(); \r\n",
&gzipped_content.len()
)
.into_bytes();
response.extend(&gzipped_content); response.extend(&gzipped_content);
let server = server! { let server = server! {
@@ -130,7 +130,10 @@ fn test_accept_header_is_not_changed_if_set() {
let res = client let res = client
.get(&format!("http://{}/accept", server.addr())) .get(&format!("http://{}/accept", server.addr()))
.header(reqwest::header::ACCEPT, reqwest::header::HeaderValue::from_static("application/json")) .header(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
)
.send() .send()
.unwrap(); .unwrap();
@@ -157,8 +160,12 @@ fn test_accept_encoding_header_is_not_changed_if_set() {
}; };
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let res = client.get(&format!("http://{}/accept-encoding", server.addr())) let res = client
.header(reqwest::header::ACCEPT_ENCODING, reqwest::header::HeaderValue::from_static("identity")) .get(&format!("http://{}/accept-encoding", server.addr()))
.header(
reqwest::header::ACCEPT_ENCODING,
reqwest::header::HeaderValue::from_static("identity"),
)
.send() .send()
.unwrap(); .unwrap();

View File

@@ -1,6 +1,3 @@
extern crate env_logger;
extern crate reqwest;
#[macro_use] #[macro_use]
mod support; mod support;
@@ -8,15 +5,17 @@ mod support;
fn text_part() { fn text_part() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let form = reqwest::multipart::Form::new() let form = reqwest::multipart::Form::new().text("foo", "bar");
.text("foo", "bar");
let expected_body = format!("\ let expected_body = format!(
--{0}\r\n\ "\
Content-Disposition: form-data; name=\"foo\"\r\n\r\n\ --{0}\r\n\
bar\r\n\ Content-Disposition: form-data; name=\"foo\"\r\n\r\n\
--{0}--\r\n\ bar\r\n\
", form.boundary()); --{0}--\r\n\
",
form.boundary()
);
let server = server! { let server = server! {
request: format!("\ request: format!("\
@@ -55,17 +54,22 @@ fn file() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let form = reqwest::multipart::Form::new() let form = reqwest::multipart::Form::new()
.file("foo", "Cargo.lock").unwrap(); .file("foo", "Cargo.lock")
.unwrap();
let fcontents = std::fs::read_to_string("Cargo.lock").unwrap(); let fcontents = std::fs::read_to_string("Cargo.lock").unwrap();
let expected_body = format!("\ let expected_body = format!(
--{0}\r\n\ "\
Content-Disposition: form-data; name=\"foo\"; filename=\"Cargo.lock\"\r\n\ --{0}\r\n\
Content-Type: application/octet-stream\r\n\r\n\ Content-Disposition: form-data; name=\"foo\"; filename=\"Cargo.lock\"\r\n\
{1}\r\n\ Content-Type: application/octet-stream\r\n\r\n\
--{0}--\r\n\ {1}\r\n\
", form.boundary(), fcontents); --{0}--\r\n\
",
form.boundary(),
fcontents
);
let server = server! { let server = server! {
request: format!("\ request: format!("\

View File

@@ -1,5 +1,3 @@
extern crate reqwest;
#[macro_use] #[macro_use]
mod support; mod support;
@@ -37,7 +35,10 @@ fn http_proxy() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"proxied"); assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
} }
#[test] #[test]
@@ -66,8 +67,8 @@ fn http_proxy_basic_auth() {
let res = reqwest::Client::builder() let res = reqwest::Client::builder()
.proxy( .proxy(
reqwest::Proxy::http(&proxy) reqwest::Proxy::http(&proxy)
.unwrap() .unwrap()
.basic_auth("Aladdin", "open sesame") .basic_auth("Aladdin", "open sesame"),
) )
.build() .build()
.unwrap() .unwrap()
@@ -77,7 +78,10 @@ fn http_proxy_basic_auth() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"proxied"); assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
} }
#[test] #[test]
@@ -104,9 +108,7 @@ fn http_proxy_basic_auth_parsed() {
let url = "http://hyper.rs/prox"; let url = "http://hyper.rs/prox";
let res = reqwest::Client::builder() let res = reqwest::Client::builder()
.proxy( .proxy(reqwest::Proxy::http(&proxy).unwrap())
reqwest::Proxy::http(&proxy).unwrap()
)
.build() .build()
.unwrap() .unwrap()
.get(url) .get(url)
@@ -115,7 +117,10 @@ fn http_proxy_basic_auth_parsed() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"proxied"); assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
} }
#[test] #[test]
@@ -141,9 +146,7 @@ fn test_no_proxy() {
// set up proxy and use no_proxy to clear up client builder proxies. // set up proxy and use no_proxy to clear up client builder proxies.
let res = reqwest::Client::builder() let res = reqwest::Client::builder()
.proxy( .proxy(reqwest::Proxy::http(&proxy).unwrap())
reqwest::Proxy::http(&proxy).unwrap()
)
.no_proxy() .no_proxy()
.build() .build()
.unwrap() .unwrap()
@@ -189,11 +192,14 @@ fn test_using_system_proxy() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"proxied"); assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"proxied"
);
// reset user setting. // reset user setting.
match system_proxy { match system_proxy {
Err(_) => env::remove_var("http_proxy"), Err(_) => env::remove_var("http_proxy"),
Ok(proxy) => env::set_var("http_proxy", proxy) Ok(proxy) => env::set_var("http_proxy", proxy),
} }
} }

View File

@@ -1,5 +1,3 @@
extern crate reqwest;
#[macro_use] #[macro_use]
mod support; mod support;
@@ -47,12 +45,13 @@ fn test_redirect_301_and_302_and_303_changes_post_to_get() {
let url = format!("http://{}/{}", redirect.addr(), code); let url = format!("http://{}/{}", redirect.addr(), code);
let dst = format!("http://{}/{}", redirect.addr(), "dst"); let dst = format!("http://{}/{}", redirect.addr(), "dst");
let res = client.post(&url) let res = client.post(&url).send().unwrap();
.send()
.unwrap();
assert_eq!(res.url().as_str(), dst); assert_eq!(res.url().as_str(), dst);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test-dst"); assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-dst"
);
} }
} }
@@ -99,12 +98,13 @@ fn test_redirect_307_and_308_tries_to_get_again() {
let url = format!("http://{}/{}", redirect.addr(), code); let url = format!("http://{}/{}", redirect.addr(), code);
let dst = format!("http://{}/{}", redirect.addr(), "dst"); let dst = format!("http://{}/{}", redirect.addr(), "dst");
let res = client.get(&url) let res = client.get(&url).send().unwrap();
.send()
.unwrap();
assert_eq!(res.url().as_str(), dst); assert_eq!(res.url().as_str(), dst);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test-dst"); assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-dst"
);
} }
} }
@@ -155,13 +155,13 @@ fn test_redirect_307_and_308_tries_to_post_again() {
let url = format!("http://{}/{}", redirect.addr(), code); let url = format!("http://{}/{}", redirect.addr(), code);
let dst = format!("http://{}/{}", redirect.addr(), "dst"); let dst = format!("http://{}/{}", redirect.addr(), "dst");
let res = client.post(&url) let res = client.post(&url).body("Hello").send().unwrap();
.body("Hello")
.send()
.unwrap();
assert_eq!(res.url().as_str(), dst); assert_eq!(res.url().as_str(), dst);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test-dst"); assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-dst"
);
} }
} }
@@ -204,8 +204,6 @@ fn test_redirect_307_does_not_try_if_reader_cannot_reset() {
} }
} }
#[test] #[test]
fn test_redirect_removes_sensitive_headers() { fn test_redirect_removes_sensitive_headers() {
let end_server = server! { let end_server = server! {
@@ -249,7 +247,10 @@ fn test_redirect_removes_sensitive_headers() {
.build() .build()
.unwrap() .unwrap()
.get(&format!("http://{}/sensitive", mid_server.addr())) .get(&format!("http://{}/sensitive", mid_server.addr()))
.header(reqwest::header::COOKIE, reqwest::header::HeaderValue::from_static("foo=bar")) .header(
reqwest::header::COOKIE,
reqwest::header::HeaderValue::from_static("foo=bar"),
)
.send() .send()
.unwrap(); .unwrap();
} }
@@ -310,7 +311,10 @@ fn test_redirect_policy_can_stop_redirects_without_an_error() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::FOUND); assert_eq!(res.status(), reqwest::StatusCode::FOUND);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test-dont"); assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-dont"
);
} }
#[test] #[test]
@@ -385,58 +389,65 @@ fn test_invalid_location_stops_redirect_gh484() {
assert_eq!(res.url().as_str(), url); assert_eq!(res.url().as_str(), url);
assert_eq!(res.status(), reqwest::StatusCode::FOUND); assert_eq!(res.status(), reqwest::StatusCode::FOUND);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test-yikes"); assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-yikes"
);
} }
#[test] #[test]
fn test_redirect_302_with_set_cookies() { fn test_redirect_302_with_set_cookies() {
let code = 302; let code = 302;
let client = reqwest::ClientBuilder::new().cookie_store(true).build().unwrap(); let client = reqwest::ClientBuilder::new()
.cookie_store(true)
.build()
.unwrap();
let server = server! { let server = server! {
request: format!("\ request: format!("\
GET /{} HTTP/1.1\r\n\ GET /{} HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\ user-agent: $USERAGENT\r\n\
accept: */*\r\n\ accept: */*\r\n\
accept-encoding: gzip\r\n\ accept-encoding: gzip\r\n\
host: $HOST\r\n\ host: $HOST\r\n\
\r\n\ \r\n\
", code), ", code),
response: format!("\ response: format!("\
HTTP/1.1 {} reason\r\n\ HTTP/1.1 {} reason\r\n\
Server: test-redirect\r\n\ Server: test-redirect\r\n\
Content-Length: 0\r\n\ Content-Length: 0\r\n\
Location: /dst\r\n\ Location: /dst\r\n\
Connection: close\r\n\ Connection: close\r\n\
Set-Cookie: key=value\r\n\ Set-Cookie: key=value\r\n\
\r\n\ \r\n\
", code) ", code)
; ;
request: format!("\ request: format!("\
GET /dst HTTP/1.1\r\n\ GET /dst HTTP/1.1\r\n\
user-agent: $USERAGENT\r\n\ user-agent: $USERAGENT\r\n\
accept: */*\r\n\ accept: */*\r\n\
accept-encoding: gzip\r\n\ accept-encoding: gzip\r\n\
referer: http://$HOST/{}\r\n\ referer: http://$HOST/{}\r\n\
cookie: key=value\r\n\ cookie: key=value\r\n\
host: $HOST\r\n\ host: $HOST\r\n\
\r\n\ \r\n\
", code), ", code),
response: b"\ response: b"\
HTTP/1.1 200 OK\r\n\ HTTP/1.1 200 OK\r\n\
Server: test-dst\r\n\ Server: test-dst\r\n\
Content-Length: 0\r\n\ Content-Length: 0\r\n\
\r\n\ \r\n\
" "
}; };
let url = format!("http://{}/{}", server.addr(), code); let url = format!("http://{}/{}", server.addr(), code);
let dst = format!("http://{}/{}", server.addr(), "dst"); let dst = format!("http://{}/{}", server.addr(), "dst");
let res = client.get(&url) let res = client.get(&url).send().unwrap();
.send()
.unwrap();
assert_eq!(res.url().as_str(), dst); assert_eq!(res.url().as_str(), dst);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test-dst"); assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
&"test-dst"
);
} }

View File

@@ -2,9 +2,9 @@
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::net; use std::net;
use std::time::Duration;
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use std::time::Duration;
pub struct Server { pub struct Server {
addr: net::SocketAddr, addr: net::SocketAddr,
@@ -19,9 +19,8 @@ impl Server {
impl Drop for Server { impl Drop for Server {
fn drop(&mut self) { fn drop(&mut self) {
if !::std::thread::panicking() { if !thread::panicking() {
self self.panic_rx
.panic_rx
.recv_timeout(Duration::from_secs(3)) .recv_timeout(Duration::from_secs(3))
.expect("test server should not panic"); .expect("test server should not panic");
} }
@@ -47,7 +46,10 @@ pub fn spawn(txns: Vec<Txn>) -> Server {
let listener = net::TcpListener::bind("127.0.0.1:0").unwrap(); let listener = net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap(); let addr = listener.local_addr().unwrap();
let (panic_tx, panic_rx) = mpsc::channel(); let (panic_tx, panic_rx) = mpsc::channel();
let tname = format!("test({})-support-server", thread::current().name().unwrap_or("<unknown>")); let tname = format!(
"test({})-support-server",
thread::current().name().unwrap_or("<unknown>")
);
thread::Builder::new().name(tname).spawn(move || { thread::Builder::new().name(tname).spawn(move || {
'txns: for txn in txns { 'txns: for txn in txns {
let mut expected = txn.request; let mut expected = txn.request;
@@ -149,10 +151,7 @@ pub fn spawn(txns: Vec<Txn>) -> Server {
let _ = panic_tx.send(()); let _ = panic_tx.send(());
}).expect("server thread spawn"); }).expect("server thread spawn");
Server { Server { addr, panic_rx }
addr,
panic_rx,
}
} }
fn replace_expected_vars(bytes: &mut Vec<u8>, host: &[u8], ua: &[u8]) { fn replace_expected_vars(bytes: &mut Vec<u8>, host: &[u8], ua: &[u8]) {
@@ -210,16 +209,15 @@ macro_rules! __internal__txn {
) )
} }
#[macro_export] #[macro_export]
macro_rules! __internal__prop { macro_rules! __internal__prop {
(request: $val:expr) => ( (request: $val:expr) => {
From::from(&$val[..]) From::from(&$val[..])
); };
(response: $val:expr) => ( (response: $val:expr) => {
From::from(&$val[..]) From::from(&$val[..])
); };
($field:ident: $val:expr) => ( ($field:ident: $val:expr) => {
From::from($val) From::from($val)
) };
} }

View File

@@ -1,6 +1,3 @@
extern crate env_logger;
extern crate reqwest;
#[macro_use] #[macro_use]
mod support; mod support;
@@ -38,10 +35,7 @@ fn timeout_closes_connection() {
}; };
let url = format!("http://{}/closes", server.addr()); let url = format!("http://{}/closes", server.addr());
let err = client let err = client.get(&url).send().unwrap_err();
.get(&url)
.send()
.unwrap_err();
assert_eq!(err.get_ref().unwrap().to_string(), "timed out"); assert_eq!(err.get_ref().unwrap().to_string(), "timed out");
assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str()));
@@ -93,7 +87,6 @@ fn write_timeout_large_body() {
assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str()));
} }
#[test] #[test]
fn test_response_timeout() { fn test_response_timeout() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
@@ -158,7 +151,10 @@ fn test_read_timeout() {
assert_eq!(res.url().as_str(), &url); assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), &"5"); assert_eq!(
res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(),
&"5"
);
let mut buf = [0; 1024]; let mut buf = [0; 1024];
let err = res.read(&mut buf).unwrap_err(); let err = res.read(&mut buf).unwrap_err();