committed by
Sean McArthur
parent
81e0f1ff2a
commit
cf8944a0f0
@@ -1 +0,0 @@
|
||||
disable_all_formatting = true
|
||||
@@ -55,7 +55,9 @@ dist: trusty
|
||||
env:
|
||||
global:
|
||||
- REQWEST_TEST_BODY_FULL=1
|
||||
|
||||
before_script:
|
||||
- rustup component add rustfmt
|
||||
script:
|
||||
- cargo fmt -- --check
|
||||
- cargo build $FEATURES
|
||||
- cargo test -v $FEATURES -- --test-threads=1
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate reqwest;
|
||||
extern crate tokio;
|
||||
|
||||
use std::mem;
|
||||
use std::io::{self, Cursor};
|
||||
use futures::{Future, Stream};
|
||||
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()
|
||||
.get("https://hyper.rs")
|
||||
.send()
|
||||
@@ -23,10 +18,9 @@ fn fetch() -> impl Future<Item=(), Error=()> {
|
||||
.map_err(|err| println!("request error: {}", err))
|
||||
.map(|body| {
|
||||
let mut body = Cursor::new(body);
|
||||
let _ = io::copy(&mut body, &mut io::stdout())
|
||||
.map_err(|err| {
|
||||
println!("stdout error: {}", err);
|
||||
});
|
||||
let _ = io::copy(&mut body, &mut io::stdout()).map_err(|err| {
|
||||
println!("stdout error: {}", err);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate reqwest;
|
||||
extern crate tokio;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use futures::Future;
|
||||
use reqwest::r#async::{Client, Response};
|
||||
use serde::Deserialize;
|
||||
@@ -21,27 +15,18 @@ struct SlideshowContainer {
|
||||
slideshow: Slideshow,
|
||||
}
|
||||
|
||||
fn fetch() -> impl Future<Item=(), Error=()> {
|
||||
fn fetch() -> impl Future<Item = (), Error = ()> {
|
||||
let client = Client::new();
|
||||
|
||||
let json = |mut res : Response | {
|
||||
res.json::<SlideshowContainer>()
|
||||
};
|
||||
let json = |mut res: Response| res.json::<SlideshowContainer>();
|
||||
|
||||
let request1 =
|
||||
client
|
||||
.get("https://httpbin.org/json")
|
||||
.send()
|
||||
.and_then(json);
|
||||
let request1 = client.get("https://httpbin.org/json").send().and_then(json);
|
||||
|
||||
let request2 =
|
||||
client
|
||||
.get("https://httpbin.org/json")
|
||||
.send()
|
||||
.and_then(json);
|
||||
let request2 = client.get("https://httpbin.org/json").send().and_then(json);
|
||||
|
||||
request1.join(request2)
|
||||
.map(|(res1, res2)|{
|
||||
request1
|
||||
.join(request2)
|
||||
.map(|(res1, res2)| {
|
||||
println!("{:?}", res1);
|
||||
println!("{:?}", res2);
|
||||
})
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
#![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::mem;
|
||||
use std::path::Path;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use futures::{try_ready, Async, Future, Poll, Stream};
|
||||
use reqwest::r#async::{Client, Decoder};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncRead;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extern crate reqwest;
|
||||
|
||||
fn main() {
|
||||
reqwest::Client::new()
|
||||
.post("http://www.baidu.com")
|
||||
|
||||
@@ -3,19 +3,16 @@
|
||||
//! 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
|
||||
//! process it at runtime.
|
||||
extern crate reqwest;
|
||||
#[macro_use] extern crate serde_json;
|
||||
use serde_json::json;
|
||||
|
||||
fn main() -> Result<(), reqwest::Error> {
|
||||
let echo_json: serde_json::Value = reqwest::Client::new()
|
||||
.post("https://jsonplaceholder.typicode.com/posts")
|
||||
.json(
|
||||
&json!({
|
||||
"title": "Reqwest.rs",
|
||||
"body": "https://docs.rs/reqwest",
|
||||
"userId": 1
|
||||
})
|
||||
)
|
||||
.json(&json!({
|
||||
"title": "Reqwest.rs",
|
||||
"body": "https://docs.rs/reqwest",
|
||||
"userId": 1
|
||||
}))
|
||||
.send()?
|
||||
.json()?;
|
||||
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
//! 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
|
||||
//! more code.
|
||||
extern crate reqwest;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -23,7 +20,7 @@ fn main() -> Result<(), reqwest::Error> {
|
||||
id: None,
|
||||
title: "Reqwest.rs".into(),
|
||||
body: "https://docs.rs/reqwest".into(),
|
||||
user_id: 1
|
||||
user_id: 1,
|
||||
};
|
||||
let new_post: Post = reqwest::Client::new()
|
||||
.post("https://jsonplaceholder.typicode.com/posts")
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
//! `cargo run --example simple`
|
||||
|
||||
extern crate reqwest;
|
||||
extern crate env_logger;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::init();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pub use self::body::{Body, Chunk};
|
||||
pub use self::decoder::{Decoder, ReadableChunks};
|
||||
pub use self::client::{Client, ClientBuilder};
|
||||
pub use self::decoder::{Decoder, ReadableChunks};
|
||||
pub use self::request::{Request, RequestBuilder};
|
||||
pub use self::response::{Response, ResponseBuilderExt};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use futures::{Future, Stream, Poll, Async, try_ready};
|
||||
use bytes::{Buf, Bytes};
|
||||
use futures::{try_ready, Async, Future, Poll, Stream};
|
||||
use hyper::body::Payload;
|
||||
use tokio::timer::Delay;
|
||||
|
||||
@@ -15,7 +15,7 @@ enum Inner {
|
||||
Hyper {
|
||||
body: hyper::Body,
|
||||
timeout: Option<Delay>,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
impl Body {
|
||||
@@ -29,10 +29,7 @@ impl Body {
|
||||
#[inline]
|
||||
pub(crate) fn response(body: hyper::Body, timeout: Option<Delay>) -> Body {
|
||||
Body {
|
||||
inner: Inner::Hyper {
|
||||
body,
|
||||
timeout,
|
||||
},
|
||||
inner: Inner::Hyper { body, timeout },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +62,7 @@ impl Body {
|
||||
Inner::Hyper { body, timeout } => {
|
||||
debug_assert!(timeout.is_none());
|
||||
(None, body)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,14 +74,17 @@ impl Stream for Body {
|
||||
#[inline]
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
let opt = match self.inner {
|
||||
Inner::Hyper { ref mut body, ref mut timeout } => {
|
||||
Inner::Hyper {
|
||||
ref mut body,
|
||||
ref mut timeout,
|
||||
} => {
|
||||
if let Some(ref mut timeout) = timeout {
|
||||
if let Async::Ready(()) = try_!(timeout.poll()) {
|
||||
return Err(crate::error::timedout(None));
|
||||
}
|
||||
}
|
||||
try_ready!(body.poll_data().map_err(crate::error::from))
|
||||
},
|
||||
}
|
||||
Inner::Reusable(ref mut bytes) => {
|
||||
return if bytes.is_empty() {
|
||||
Ok(Async::Ready(None))
|
||||
@@ -93,12 +93,10 @@ impl Stream for Body {
|
||||
*bytes = Bytes::new();
|
||||
Ok(Async::Ready(Some(chunk)))
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Async::Ready(opt.map(|chunk| Chunk {
|
||||
inner: chunk,
|
||||
})))
|
||||
Ok(Async::Ready(opt.map(|chunk| Chunk { inner: chunk })))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +159,7 @@ impl Chunk {
|
||||
#[inline]
|
||||
pub(crate) fn from_chunk(chunk: Bytes) -> Chunk {
|
||||
Chunk {
|
||||
inner: hyper::Chunk::from(chunk)
|
||||
inner: hyper::Chunk::from(chunk),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +195,9 @@ impl std::ops::Deref for Chunk {
|
||||
|
||||
impl Extend<u8> for Chunk {
|
||||
fn extend<T>(&mut self, iter: T)
|
||||
where T: IntoIterator<Item=u8> {
|
||||
where
|
||||
T: IntoIterator<Item = u8>,
|
||||
{
|
||||
self.inner.extend(iter)
|
||||
}
|
||||
}
|
||||
@@ -219,7 +219,9 @@ impl From<Vec<u8>> for Chunk {
|
||||
|
||||
impl From<&'static [u8]> for Chunk {
|
||||
fn from(slice: &'static [u8]) -> Chunk {
|
||||
Chunk { inner: slice.into() }
|
||||
Chunk {
|
||||
inner: slice.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,13 +233,17 @@ impl From<String> for Chunk {
|
||||
|
||||
impl From<&'static str> for Chunk {
|
||||
fn from(slice: &'static str) -> Chunk {
|
||||
Chunk { inner: slice.into() }
|
||||
Chunk {
|
||||
inner: slice.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bytes> for Chunk {
|
||||
fn from(bytes: Bytes) -> Chunk {
|
||||
Chunk { inner: bytes.into() }
|
||||
Chunk {
|
||||
inner: bytes.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,8 +255,7 @@ impl From<Chunk> for hyper::Chunk {
|
||||
|
||||
impl fmt::Debug for Body {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Body")
|
||||
.finish()
|
||||
f.debug_struct("Body").finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
use std::{fmt, str};
|
||||
use std::net::IpAddr;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
use std::net::IpAddr;
|
||||
use std::{fmt, str};
|
||||
|
||||
use crate::header::{
|
||||
Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
|
||||
CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{Async, Future, Poll};
|
||||
use crate::header::{
|
||||
Entry,
|
||||
HeaderMap,
|
||||
HeaderValue,
|
||||
ACCEPT,
|
||||
ACCEPT_ENCODING,
|
||||
CONTENT_LENGTH,
|
||||
CONTENT_ENCODING,
|
||||
CONTENT_TYPE,
|
||||
LOCATION,
|
||||
PROXY_AUTHORIZATION,
|
||||
RANGE,
|
||||
REFERER,
|
||||
TRANSFER_ENCODING,
|
||||
USER_AGENT,
|
||||
};
|
||||
use http::Uri;
|
||||
use hyper::client::ResponseFuture;
|
||||
use mime;
|
||||
@@ -28,24 +16,22 @@ use mime;
|
||||
use native_tls::TlsConnector;
|
||||
use tokio::{clock, timer::Delay};
|
||||
|
||||
use log::{debug};
|
||||
|
||||
use log::debug;
|
||||
|
||||
use super::request::{Request, RequestBuilder};
|
||||
use super::response::Response;
|
||||
use crate::connect::Connector;
|
||||
use crate::into_url::{expect_uri, try_uri};
|
||||
use crate::cookie;
|
||||
use crate::redirect::{self, RedirectPolicy, remove_sensitive_headers};
|
||||
use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
|
||||
use crate::into_url::{expect_uri, try_uri};
|
||||
use crate::proxy::get_proxies;
|
||||
#[cfg(feature = "tls")]
|
||||
use crate::{Certificate, Identity};
|
||||
use crate::redirect::{self, remove_sensitive_headers, RedirectPolicy};
|
||||
#[cfg(feature = "tls")]
|
||||
use crate::tls::TlsBackend;
|
||||
#[cfg(feature = "tls")]
|
||||
use crate::{Certificate, Identity};
|
||||
use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
|
||||
|
||||
static DEFAULT_USER_AGENT: &str =
|
||||
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||
static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
/// An asynchronous `Client` to make Requests with.
|
||||
///
|
||||
@@ -98,7 +84,10 @@ impl ClientBuilder {
|
||||
pub fn new() -> ClientBuilder {
|
||||
let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2);
|
||||
headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_USER_AGENT));
|
||||
headers.insert(ACCEPT, HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime"));
|
||||
headers.insert(
|
||||
ACCEPT,
|
||||
HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime"),
|
||||
);
|
||||
|
||||
ClientBuilder {
|
||||
config: Config {
|
||||
@@ -156,8 +145,13 @@ impl ClientBuilder {
|
||||
id.add_to_native_tls(&mut tls)?;
|
||||
}
|
||||
|
||||
Connector::new_default_tls(tls, proxies.clone(), config.local_address, config.nodelay)?
|
||||
},
|
||||
Connector::new_default_tls(
|
||||
tls,
|
||||
proxies.clone(),
|
||||
config.local_address,
|
||||
config.nodelay,
|
||||
)?
|
||||
}
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
TlsBackend::Rustls => {
|
||||
use crate::tls::NoVerifier;
|
||||
@@ -166,15 +160,14 @@ impl ClientBuilder {
|
||||
if config.http2_only {
|
||||
tls.set_protocols(&["h2".into()]);
|
||||
} else {
|
||||
tls.set_protocols(&[
|
||||
"h2".into(),
|
||||
"http/1.1".into(),
|
||||
]);
|
||||
tls.set_protocols(&["h2".into(), "http/1.1".into()]);
|
||||
}
|
||||
tls.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||
tls.root_store
|
||||
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||
|
||||
if !config.certs_verification {
|
||||
tls.dangerous().set_certificate_verifier(Arc::new(NoVerifier));
|
||||
tls.dangerous()
|
||||
.set_certificate_verifier(Arc::new(NoVerifier));
|
||||
}
|
||||
|
||||
for cert in config.root_certs {
|
||||
@@ -185,7 +178,12 @@ impl ClientBuilder {
|
||||
id.add_to_rustls(&mut tls)?;
|
||||
}
|
||||
|
||||
Connector::new_rustls_tls(tls, proxies.clone(), config.local_address, config.nodelay)?
|
||||
Connector::new_rustls_tls(
|
||||
tls,
|
||||
proxies.clone(),
|
||||
config.local_address,
|
||||
config.nodelay,
|
||||
)?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,9 +206,7 @@ impl ClientBuilder {
|
||||
|
||||
let hyper_client = builder.build(connector);
|
||||
|
||||
let proxies_maybe_http_auth = proxies
|
||||
.iter()
|
||||
.any(|p| p.maybe_has_http_auth());
|
||||
let proxies_maybe_http_auth = proxies.iter().any(|p| p.maybe_has_http_auth());
|
||||
|
||||
let cookie_store = config.cookie_store.map(RwLock::new);
|
||||
|
||||
@@ -277,7 +273,10 @@ impl ClientBuilder {
|
||||
/// site will be trusted for use from any other. This introduces a
|
||||
/// significant vulnerability to man-in-the-middle attacks.
|
||||
#[cfg(feature = "default-tls")]
|
||||
pub fn danger_accept_invalid_hostnames(mut self, accept_invalid_hostname: bool) -> ClientBuilder {
|
||||
pub fn danger_accept_invalid_hostnames(
|
||||
mut self,
|
||||
accept_invalid_hostname: bool,
|
||||
) -> ClientBuilder {
|
||||
self.config.hostname_verification = !accept_invalid_hostname;
|
||||
self
|
||||
}
|
||||
@@ -299,7 +298,6 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
/// Sets the default headers for every request.
|
||||
pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
|
||||
for (key, value) in headers.iter() {
|
||||
@@ -349,7 +347,6 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
/// Set a `RedirectPolicy` for this client.
|
||||
///
|
||||
/// Default will follow redirects up to a maximum of 10.
|
||||
@@ -454,9 +451,7 @@ impl Client {
|
||||
/// Use `Client::builder()` if you wish to handle the failure as an `Error`
|
||||
/// instead of panicking.
|
||||
pub fn new() -> Client {
|
||||
ClientBuilder::new()
|
||||
.build()
|
||||
.expect("Client::new()")
|
||||
ClientBuilder::new().build().expect("Client::new()")
|
||||
}
|
||||
|
||||
/// Creates a `ClientBuilder` to configure a `Client`.
|
||||
@@ -529,9 +524,7 @@ impl Client {
|
||||
///
|
||||
/// This method fails whenever supplied `Url` cannot be parsed.
|
||||
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
|
||||
let req = url
|
||||
.into_url()
|
||||
.map(move |url| Request::new(method, url));
|
||||
let req = url.into_url().map(move |url| Request::new(method, url));
|
||||
RequestBuilder::new(self.clone(), req)
|
||||
}
|
||||
|
||||
@@ -551,14 +544,8 @@ impl Client {
|
||||
self.execute_request(request)
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn execute_request(&self, req: Request) -> Pending {
|
||||
let (
|
||||
method,
|
||||
url,
|
||||
mut headers,
|
||||
body
|
||||
) = req.pieces();
|
||||
let (method, url, mut headers, body) = req.pieces();
|
||||
|
||||
// insert default headers in the request headers
|
||||
// without overwriting already appended headers.
|
||||
@@ -576,9 +563,8 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
if self.inner.gzip &&
|
||||
!headers.contains_key(ACCEPT_ENCODING) &&
|
||||
!headers.contains_key(RANGE) {
|
||||
if self.inner.gzip && !headers.contains_key(ACCEPT_ENCODING) && !headers.contains_key(RANGE)
|
||||
{
|
||||
headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip"));
|
||||
}
|
||||
|
||||
@@ -588,10 +574,8 @@ impl Client {
|
||||
Some(body) => {
|
||||
let (reusable, body) = body.into_hyper();
|
||||
(Some(reusable), body)
|
||||
},
|
||||
None => {
|
||||
(None, hyper::Body::empty())
|
||||
}
|
||||
None => (None, hyper::Body::empty()),
|
||||
};
|
||||
|
||||
self.proxy_auth(&uri, &mut headers);
|
||||
@@ -606,9 +590,10 @@ impl Client {
|
||||
|
||||
let in_flight = self.inner.hyper.request(req);
|
||||
|
||||
let timeout = self.inner.request_timeout.map(|dur| {
|
||||
Delay::new(clock::now() + dur)
|
||||
});
|
||||
let timeout = self
|
||||
.inner
|
||||
.request_timeout
|
||||
.map(|dur| Delay::new(clock::now() + dur));
|
||||
|
||||
Pending {
|
||||
inner: PendingInner::Request(PendingRequest {
|
||||
@@ -643,14 +628,10 @@ impl Client {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for proxy in self.inner.proxies.iter() {
|
||||
if proxy.is_match(dst) {
|
||||
if let Some(header) = proxy.http_basic_auth(dst) {
|
||||
headers.insert(
|
||||
PROXY_AUTHORIZATION,
|
||||
header,
|
||||
);
|
||||
headers.insert(PROXY_AUTHORIZATION, header);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -671,8 +652,7 @@ impl fmt::Debug for Client {
|
||||
|
||||
impl fmt::Debug for ClientBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("ClientBuilder")
|
||||
.finish()
|
||||
f.debug_struct("ClientBuilder").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,7 +706,9 @@ impl Future for Pending {
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.inner {
|
||||
PendingInner::Request(ref mut req) => req.poll(),
|
||||
PendingInner::Error(ref mut err) => Err(err.take().expect("Pending error polled more than once")),
|
||||
PendingInner::Error(ref mut err) => {
|
||||
Err(err.take().expect("Pending error polled more than once"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,56 +737,58 @@ impl Future for PendingRequest {
|
||||
store.0.store_response_cookies(cookies, &self.url);
|
||||
}
|
||||
let should_redirect = match res.status() {
|
||||
StatusCode::MOVED_PERMANENTLY |
|
||||
StatusCode::FOUND |
|
||||
StatusCode::SEE_OTHER => {
|
||||
StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => {
|
||||
self.body = None;
|
||||
for header in &[TRANSFER_ENCODING, CONTENT_ENCODING, CONTENT_TYPE, CONTENT_LENGTH] {
|
||||
for header in &[
|
||||
TRANSFER_ENCODING,
|
||||
CONTENT_ENCODING,
|
||||
CONTENT_TYPE,
|
||||
CONTENT_LENGTH,
|
||||
] {
|
||||
self.headers.remove(header);
|
||||
}
|
||||
|
||||
match self.method {
|
||||
Method::GET | Method::HEAD => {},
|
||||
Method::GET | Method::HEAD => {}
|
||||
_ => {
|
||||
self.method = Method::GET;
|
||||
}
|
||||
}
|
||||
true
|
||||
},
|
||||
StatusCode::TEMPORARY_REDIRECT |
|
||||
StatusCode::PERMANENT_REDIRECT => match self.body {
|
||||
Some(Some(_)) | None => true,
|
||||
Some(None) => false,
|
||||
},
|
||||
}
|
||||
StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {
|
||||
match self.body {
|
||||
Some(Some(_)) | None => true,
|
||||
Some(None) => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if should_redirect {
|
||||
let loc = res.headers()
|
||||
.get(LOCATION)
|
||||
.and_then(|val| {
|
||||
let loc = (|| -> Option<Url> {
|
||||
// Some sites may send a utf-8 Location header,
|
||||
// even though we're supposed to treat those bytes
|
||||
// as opaque, we'll check specifically for utf8.
|
||||
self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok()
|
||||
})();
|
||||
let loc = res.headers().get(LOCATION).and_then(|val| {
|
||||
let loc = (|| -> Option<Url> {
|
||||
// Some sites may send a utf-8 Location header,
|
||||
// even though we're supposed to treat those bytes
|
||||
// as opaque, we'll check specifically for utf8.
|
||||
self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok()
|
||||
})();
|
||||
|
||||
// Check that the `url` is also a valid `http::Uri`.
|
||||
//
|
||||
// If not, just log it and skip the redirect.
|
||||
let loc = loc.and_then(|url| {
|
||||
if try_uri(&url).is_some() {
|
||||
Some(url)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if loc.is_none() {
|
||||
debug!("Location header had invalid URI: {:?}", val);
|
||||
// Check that the `url` is also a valid `http::Uri`.
|
||||
//
|
||||
// If not, just log it and skip the redirect.
|
||||
let loc = loc.and_then(|url| {
|
||||
if try_uri(&url).is_some() {
|
||||
Some(url)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
loc
|
||||
});
|
||||
|
||||
if loc.is_none() {
|
||||
debug!("Location header had invalid URI: {:?}", val);
|
||||
}
|
||||
loc
|
||||
});
|
||||
if let Some(loc) = loc {
|
||||
if self.client.referer {
|
||||
if let Some(referer) = make_referer(&loc, &self.url) {
|
||||
@@ -812,11 +796,10 @@ impl Future for PendingRequest {
|
||||
}
|
||||
}
|
||||
self.urls.push(self.url.clone());
|
||||
let action = self.client.redirect_policy.check(
|
||||
res.status(),
|
||||
&loc,
|
||||
&self.urls,
|
||||
);
|
||||
let action = self
|
||||
.client
|
||||
.redirect_policy
|
||||
.check(res.status(), &loc, &self.urls);
|
||||
|
||||
match action {
|
||||
redirect::Action::Follow => {
|
||||
@@ -844,13 +827,13 @@ impl Future for PendingRequest {
|
||||
*req.headers_mut() = self.headers.clone();
|
||||
self.in_flight = self.client.hyper.request(req);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
redirect::Action::Stop => {
|
||||
debug!("redirect_policy disallowed redirection to '{}'", loc);
|
||||
},
|
||||
}
|
||||
redirect::Action::LoopDetected => {
|
||||
return Err(crate::error::loop_detected(self.url.clone()));
|
||||
},
|
||||
}
|
||||
redirect::Action::TooManyRedirects => {
|
||||
return Err(crate::error::too_many_redirects(self.url.clone()));
|
||||
}
|
||||
@@ -866,17 +849,12 @@ impl Future for PendingRequest {
|
||||
impl fmt::Debug for Pending {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.inner {
|
||||
PendingInner::Request(ref req) => {
|
||||
f.debug_struct("Pending")
|
||||
.field("method", &req.method)
|
||||
.field("url", &req.url)
|
||||
.finish()
|
||||
},
|
||||
PendingInner::Error(ref err) => {
|
||||
f.debug_struct("Pending")
|
||||
.field("error", err)
|
||||
.finish()
|
||||
}
|
||||
PendingInner::Request(ref req) => f
|
||||
.debug_struct("Pending")
|
||||
.field("method", &req.method)
|
||||
.field("url", &req.url)
|
||||
.finish(),
|
||||
PendingInner::Error(ref err) => f.debug_struct("Pending").field("error", err).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -903,7 +881,7 @@ fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &cookie::CookieStore
|
||||
if !header.is_empty() {
|
||||
headers.insert(
|
||||
crate::header::COOKIE,
|
||||
HeaderValue::from_bytes(header.as_bytes()).unwrap()
|
||||
HeaderValue::from_bytes(header.as_bytes()).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,18 +20,18 @@ The following types directly support the gzip compression case:
|
||||
- `Pending` is a non-blocking constructor for a `Decoder` in case the body needs to be checked for EOF
|
||||
*/
|
||||
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::io::{self, Read};
|
||||
use std::mem;
|
||||
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use flate2::read::GzDecoder;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use hyper::{HeaderMap};
|
||||
use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
|
||||
use hyper::HeaderMap;
|
||||
|
||||
use log::{warn};
|
||||
use log::warn;
|
||||
|
||||
use super::{Body, Chunk};
|
||||
use crate::error;
|
||||
@@ -42,7 +42,7 @@ const INIT_BUFFER_SIZE: usize = 8192;
|
||||
///
|
||||
/// The inner decoder may be constructed asynchronously.
|
||||
pub struct Decoder {
|
||||
inner: Inner
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
enum Inner {
|
||||
@@ -51,7 +51,7 @@ enum Inner {
|
||||
/// A `Gzip` decoder will uncompress the gzipped response content before returning it.
|
||||
Gzip(Gzip),
|
||||
/// A decoder that doesn't have a value yet.
|
||||
Pending(Pending)
|
||||
Pending(Pending),
|
||||
}
|
||||
|
||||
/// A future attempt to poll the response body for EOF so we know whether to use gzip or not.
|
||||
@@ -68,8 +68,7 @@ struct Gzip {
|
||||
|
||||
impl fmt::Debug for Decoder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Decoder")
|
||||
.finish()
|
||||
f.debug_struct("Decoder").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +79,7 @@ impl Decoder {
|
||||
#[inline]
|
||||
pub fn empty() -> Decoder {
|
||||
Decoder {
|
||||
inner: Inner::PlainText(Body::empty())
|
||||
inner: Inner::PlainText(Body::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +89,7 @@ impl Decoder {
|
||||
#[inline]
|
||||
fn plain_text(body: Body) -> Decoder {
|
||||
Decoder {
|
||||
inner: Inner::PlainText(body)
|
||||
inner: Inner::PlainText(body),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +99,9 @@ impl Decoder {
|
||||
#[inline]
|
||||
fn gzip(body: Body) -> Decoder {
|
||||
Decoder {
|
||||
inner: Inner::Pending(Pending { body: ReadableChunks::new(body) })
|
||||
inner: Inner::Pending(Pending {
|
||||
body: ReadableChunks::new(body),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,11 +121,11 @@ impl Decoder {
|
||||
.get_all(CONTENT_ENCODING)
|
||||
.iter()
|
||||
.any(|enc| enc == "gzip");
|
||||
content_encoding_gzip ||
|
||||
headers
|
||||
.get_all(TRANSFER_ENCODING)
|
||||
.iter()
|
||||
.any(|enc| enc == "gzip")
|
||||
content_encoding_gzip
|
||||
|| headers
|
||||
.get_all(TRANSFER_ENCODING)
|
||||
.iter()
|
||||
.any(|enc| enc == "gzip")
|
||||
};
|
||||
if is_gzip {
|
||||
if let Some(content_length) = headers.get(CONTENT_LENGTH) {
|
||||
@@ -153,15 +154,13 @@ impl Stream for Decoder {
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
// Do a read or poll for a pendidng decoder value.
|
||||
let new_value = match self.inner {
|
||||
Inner::Pending(ref mut future) => {
|
||||
match future.poll() {
|
||||
Ok(Async::Ready(inner)) => inner,
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(e) => return Err(e)
|
||||
}
|
||||
Inner::Pending(ref mut future) => match future.poll() {
|
||||
Ok(Async::Ready(inner)) => inner,
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
Inner::PlainText(ref mut body) => return body.poll(),
|
||||
Inner::Gzip(ref mut decoder) => return decoder.poll()
|
||||
Inner::Gzip(ref mut decoder) => return decoder.poll(),
|
||||
};
|
||||
|
||||
self.inner = new_value;
|
||||
@@ -177,13 +176,13 @@ impl Future for Pending {
|
||||
let body_state = match self.body.poll_stream() {
|
||||
Ok(Async::Ready(state)) => state,
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(e) => return Err(e)
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
let body = mem::replace(&mut self.body, ReadableChunks::new(Body::empty()));
|
||||
match body_state {
|
||||
StreamState::Eof => Ok(Async::Ready(Inner::PlainText(Body::empty()))),
|
||||
StreamState::HasMore => Ok(Async::Ready(Inner::Gzip(Gzip::new(body))))
|
||||
StreamState::HasMore => Ok(Async::Ready(Inner::Gzip(Gzip::new(body)))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,7 +257,7 @@ enum StreamState {
|
||||
/// More bytes can be read from the stream.
|
||||
HasMore,
|
||||
/// No more bytes can be read from the stream.
|
||||
Eof
|
||||
Eof,
|
||||
}
|
||||
|
||||
impl<S> ReadableChunks<S> {
|
||||
@@ -273,8 +272,7 @@ impl<S> ReadableChunks<S> {
|
||||
|
||||
impl<S> fmt::Debug for ReadableChunks<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("ReadableChunks")
|
||||
.finish()
|
||||
f.debug_struct("ReadableChunks").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,20 +294,12 @@ where
|
||||
} else {
|
||||
return Ok(len);
|
||||
}
|
||||
},
|
||||
ReadState::NotReady => {
|
||||
match self.poll_stream() {
|
||||
Ok(Async::Ready(StreamState::HasMore)) => continue,
|
||||
Ok(Async::Ready(StreamState::Eof)) => {
|
||||
return Ok(0)
|
||||
},
|
||||
Ok(Async::NotReady) => {
|
||||
return Err(io::ErrorKind::WouldBlock.into())
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(error::into_io(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
ReadState::NotReady => match self.poll_stream() {
|
||||
Ok(Async::Ready(StreamState::HasMore)) => continue,
|
||||
Ok(Async::Ready(StreamState::Eof)) => return Ok(0),
|
||||
Ok(Async::NotReady) => return Err(io::ErrorKind::WouldBlock.into()),
|
||||
Err(e) => return Err(error::into_io(e)),
|
||||
},
|
||||
ReadState::Eof => return Ok(0),
|
||||
}
|
||||
@@ -320,7 +310,8 @@ where
|
||||
}
|
||||
|
||||
impl<S> ReadableChunks<S>
|
||||
where S: Stream<Item = Chunk, Error = error::Error>
|
||||
where
|
||||
S: Stream<Item = Chunk, Error = error::Error>,
|
||||
{
|
||||
/// Poll the readiness of the inner reader.
|
||||
///
|
||||
@@ -332,16 +323,14 @@ impl<S> ReadableChunks<S>
|
||||
self.state = ReadState::Ready(chunk);
|
||||
|
||||
Ok(Async::Ready(StreamState::HasMore))
|
||||
},
|
||||
}
|
||||
Ok(Async::Ready(None)) => {
|
||||
self.state = ReadState::Eof;
|
||||
|
||||
Ok(Async::Ready(StreamState::Eof))
|
||||
},
|
||||
Ok(Async::NotReady) => {
|
||||
Ok(Async::NotReady)
|
||||
},
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
use http::HeaderMap;
|
||||
use mime_guess::Mime;
|
||||
use url::percent_encoding::{self, EncodeSet, PATH_SEGMENT_ENCODE_SET};
|
||||
use uuid::Uuid;
|
||||
use http::HeaderMap;
|
||||
|
||||
use futures::Stream;
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Form {
|
||||
|
||||
/// Consume this instance and transform into an instance of hyper::Body for use in a request.
|
||||
pub(crate) fn stream(mut self) -> hyper::Body {
|
||||
if self.inner.fields.is_empty(){
|
||||
if self.inner.fields.is_empty() {
|
||||
return hyper::Body::empty();
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ impl Form {
|
||||
hyper::Body::wrap_stream(stream.chain(last))
|
||||
}
|
||||
|
||||
/// Generate a hyper::Body stream for a single Part instance of a Form request.
|
||||
/// Generate a hyper::Body stream for a single Part instance of a Form request.
|
||||
pub(crate) fn part_stream<T>(&mut self, name: T, part: Part) -> hyper::Body
|
||||
where
|
||||
T: Into<Cow<'static, str>>,
|
||||
@@ -126,12 +126,20 @@ impl Form {
|
||||
let boundary = hyper::Body::from(format!("--{}\r\n", self.boundary()));
|
||||
// append headers
|
||||
let header = hyper::Body::from({
|
||||
let mut h = self.inner.percent_encoding.encode_headers(&name.into(), &part.meta);
|
||||
let mut h = self
|
||||
.inner
|
||||
.percent_encoding
|
||||
.encode_headers(&name.into(), &part.meta);
|
||||
h.extend_from_slice(b"\r\n\r\n");
|
||||
h
|
||||
});
|
||||
// then append form data followed by terminating CRLF
|
||||
hyper::Body::wrap_stream(boundary.chain(header).chain(hyper::Body::wrap_stream(part.value)).chain(hyper::Body::from("\r\n".to_owned())))
|
||||
hyper::Body::wrap_stream(
|
||||
boundary
|
||||
.chain(header)
|
||||
.chain(hyper::Body::wrap_stream(part.value))
|
||||
.chain(hyper::Body::from("\r\n".to_owned())),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn compute_length(&mut self) -> Option<u64> {
|
||||
@@ -188,7 +196,9 @@ impl Part {
|
||||
T::Item: Into<Chunk>,
|
||||
T::Error: std::error::Error + Send + Sync,
|
||||
{
|
||||
Part::new(Body::wrap(hyper::Body::wrap_stream(value.map(|chunk| chunk.into()))))
|
||||
Part::new(Body::wrap(hyper::Body::wrap_stream(
|
||||
value.map(|chunk| chunk.into()),
|
||||
)))
|
||||
}
|
||||
|
||||
fn new(value: Body) -> Part {
|
||||
@@ -306,7 +316,13 @@ impl<P: PartProps> FormParts<P> {
|
||||
// in Reader. Not the cleanest solution because if that format string is
|
||||
// ever changed then this formula needs to be changed too which is not an
|
||||
// obvious dependency in the code.
|
||||
length += 2 + self.boundary().len() as u64 + 2 + header_length as u64 + 4 + value_length + 2
|
||||
length += 2
|
||||
+ self.boundary().len() as u64
|
||||
+ 2
|
||||
+ header_length as u64
|
||||
+ 4
|
||||
+ value_length
|
||||
+ 2
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
@@ -340,7 +356,7 @@ impl PartMetadata {
|
||||
PartMetadata {
|
||||
mime: None,
|
||||
file_name: None,
|
||||
headers: HeaderMap::default()
|
||||
headers: HeaderMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,11 +374,10 @@ impl PartMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl PartMetadata {
|
||||
pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
|
||||
&self,
|
||||
debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>
|
||||
debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
|
||||
) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
|
||||
debug_struct
|
||||
.field("mime", &self.mime)
|
||||
@@ -377,22 +392,24 @@ pub(crate) struct AttrCharEncodeSet;
|
||||
impl EncodeSet for AttrCharEncodeSet {
|
||||
fn contains(&self, ch: u8) -> bool {
|
||||
match ch as char {
|
||||
'!' => false,
|
||||
'#' => false,
|
||||
'$' => false,
|
||||
'&' => false,
|
||||
'+' => false,
|
||||
'-' => false,
|
||||
'.' => false,
|
||||
'^' => false,
|
||||
'_' => false,
|
||||
'`' => false,
|
||||
'|' => false,
|
||||
'~' => false,
|
||||
_ => {
|
||||
let is_alpha_numeric = ch >= 0x41 && ch <= 0x5a || ch >= 0x61 && ch <= 0x7a || ch >= 0x30 && ch <= 0x39;
|
||||
!is_alpha_numeric
|
||||
}
|
||||
'!' => false,
|
||||
'#' => false,
|
||||
'$' => false,
|
||||
'&' => false,
|
||||
'+' => false,
|
||||
'-' => false,
|
||||
'.' => false,
|
||||
'^' => false,
|
||||
'_' => false,
|
||||
'`' => false,
|
||||
'|' => false,
|
||||
'~' => false,
|
||||
_ => {
|
||||
let is_alpha_numeric = ch >= 0x41 && ch <= 0x5a
|
||||
|| ch >= 0x61 && ch <= 0x7a
|
||||
|| ch >= 0x30 && ch <= 0x39;
|
||||
!is_alpha_numeric
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,36 +434,38 @@ impl PercentEncoding {
|
||||
None => "".to_string(),
|
||||
},
|
||||
);
|
||||
field.headers.iter().fold(s.into_bytes(), |mut header, (k,v)| {
|
||||
header.extend_from_slice(b"\r\n");
|
||||
header.extend_from_slice(k.as_str().as_bytes());
|
||||
header.extend_from_slice(b": ");
|
||||
header.extend_from_slice(v.as_bytes());
|
||||
header
|
||||
})
|
||||
field
|
||||
.headers
|
||||
.iter()
|
||||
.fold(s.into_bytes(), |mut header, (k, v)| {
|
||||
header.extend_from_slice(b"\r\n");
|
||||
header.extend_from_slice(k.as_str().as_bytes());
|
||||
header.extend_from_slice(b": ");
|
||||
header.extend_from_slice(v.as_bytes());
|
||||
header
|
||||
})
|
||||
}
|
||||
|
||||
// According to RFC7578 Section 4.2, `filename*=` syntax is invalid.
|
||||
// See https://github.com/seanmonstar/reqwest/issues/419.
|
||||
fn format_filename(&self, filename: &str) -> String {
|
||||
let legal_filename = filename.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\r", "\\\r")
|
||||
.replace("\n", "\\\n");
|
||||
let legal_filename = filename
|
||||
.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\r", "\\\r")
|
||||
.replace("\n", "\\\n");
|
||||
format!("filename=\"{}\"", legal_filename)
|
||||
}
|
||||
|
||||
fn format_parameter(&self, name: &str, value: &str) -> String {
|
||||
let legal_value = match *self {
|
||||
PercentEncoding::PathSegment => {
|
||||
percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET)
|
||||
.to_string()
|
||||
},
|
||||
percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string()
|
||||
}
|
||||
PercentEncoding::AttrChar => {
|
||||
percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet)
|
||||
.to_string()
|
||||
},
|
||||
PercentEncoding::NoOp => { value.to_string() },
|
||||
percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet).to_string()
|
||||
}
|
||||
PercentEncoding::NoOp => value.to_string(),
|
||||
};
|
||||
if value.len() == legal_value.len() {
|
||||
// nothing has been percent encoded
|
||||
@@ -477,38 +496,45 @@ mod tests {
|
||||
#[test]
|
||||
fn stream_to_end() {
|
||||
let mut form = Form::new()
|
||||
.part("reader1", Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from("part1".to_owned())))))
|
||||
.part("key1", Part::text("value1"))
|
||||
.part(
|
||||
"key2",
|
||||
Part::text("value2").mime(mime::IMAGE_BMP),
|
||||
"reader1",
|
||||
Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from(
|
||||
"part1".to_owned(),
|
||||
)))),
|
||||
)
|
||||
.part("reader2", Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from("part2".to_owned())))))
|
||||
.part("key1", Part::text("value1"))
|
||||
.part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
|
||||
.part(
|
||||
"key3",
|
||||
Part::text("value3").file_name("filename"),
|
||||
);
|
||||
"reader2",
|
||||
Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from(
|
||||
"part2".to_owned(),
|
||||
)))),
|
||||
)
|
||||
.part("key3", Part::text("value3").file_name("filename"));
|
||||
form.inner.boundary = "boundary".to_string();
|
||||
let expected = "--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
|
||||
part1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
|
||||
value1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key2\"\r\n\
|
||||
Content-Type: image/bmp\r\n\r\n\
|
||||
value2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
|
||||
part2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
|
||||
value3\r\n--boundary--\r\n";
|
||||
let expected =
|
||||
"--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
|
||||
part1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
|
||||
value1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key2\"\r\n\
|
||||
Content-Type: image/bmp\r\n\r\n\
|
||||
value2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
|
||||
part2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
|
||||
value3\r\n--boundary--\r\n";
|
||||
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
|
||||
let body_ft = form.stream();
|
||||
|
||||
let out = rt.block_on(body_ft.map(|c| c.into_bytes()).concat2()).unwrap();
|
||||
let out = rt
|
||||
.block_on(body_ft.map(|c| c.into_bytes()).concat2())
|
||||
.unwrap();
|
||||
// These prints are for debug purposes in case the test fails
|
||||
println!(
|
||||
"START REAL\n{}\nEND REAL",
|
||||
@@ -534,7 +560,9 @@ mod tests {
|
||||
let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt");
|
||||
let body_ft = form.stream();
|
||||
|
||||
let out = rt.block_on(body_ft.map(|c| c.into_bytes()).concat2()).unwrap();
|
||||
let out = rt
|
||||
.block_on(body_ft.map(|c| c.into_bytes()).concat2())
|
||||
.unwrap();
|
||||
// These prints are for debug purposes in case the test fails
|
||||
println!(
|
||||
"START REAL\n{}\nEND REAL",
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use std::fmt;
|
||||
|
||||
use base64::{encode};
|
||||
use base64::encode;
|
||||
use futures::Future;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use serde_urlencoded;
|
||||
|
||||
use super::body::{Body};
|
||||
use super::body::Body;
|
||||
use super::client::{Client, Pending};
|
||||
use super::multipart;
|
||||
use super::response::Response;
|
||||
use crate::header::{CONTENT_LENGTH, CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue};
|
||||
use http::HttpTryFrom;
|
||||
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use crate::{Method, Url};
|
||||
use http::HttpTryFrom;
|
||||
|
||||
/// A request which can be executed with `Client::execute()`.
|
||||
pub struct Request {
|
||||
@@ -95,10 +95,7 @@ impl Request {
|
||||
|
||||
impl RequestBuilder {
|
||||
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
|
||||
RequestBuilder {
|
||||
client,
|
||||
request,
|
||||
}
|
||||
RequestBuilder { client, request }
|
||||
}
|
||||
|
||||
/// Add a `Header` to this Request.
|
||||
@@ -110,11 +107,11 @@ impl RequestBuilder {
|
||||
let mut error = None;
|
||||
if let Ok(ref mut req) = self.request {
|
||||
match <HeaderName as HttpTryFrom<K>>::try_from(key) {
|
||||
Ok(key) => {
|
||||
match <HeaderValue as HttpTryFrom<V>>::try_from(value) {
|
||||
Ok(value) => { req.headers_mut().append(key, value); }
|
||||
Err(e) => error = Some(crate::error::from(e.into())),
|
||||
Ok(key) => match <HeaderValue as HttpTryFrom<V>>::try_from(value) {
|
||||
Ok(value) => {
|
||||
req.headers_mut().append(key, value);
|
||||
}
|
||||
Err(e) => error = Some(crate::error::from(e.into())),
|
||||
},
|
||||
Err(e) => error = Some(crate::error::from(e.into())),
|
||||
};
|
||||
@@ -168,7 +165,7 @@ impl RequestBuilder {
|
||||
{
|
||||
let auth = match password {
|
||||
Some(password) => format!("{}:{}", username, password),
|
||||
None => format!("{}:", username)
|
||||
None => format!("{}:", username),
|
||||
};
|
||||
let header_value = format!("Basic {}", encode(&auth));
|
||||
self.header(crate::header::AUTHORIZATION, &*header_value)
|
||||
@@ -221,10 +218,7 @@ impl RequestBuilder {
|
||||
pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder {
|
||||
let mut builder = self.header(
|
||||
CONTENT_TYPE,
|
||||
format!(
|
||||
"multipart/form-data; boundary={}",
|
||||
multipart.boundary()
|
||||
).as_str()
|
||||
format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
|
||||
);
|
||||
|
||||
builder = match multipart.compute_length() {
|
||||
@@ -286,10 +280,10 @@ impl RequestBuilder {
|
||||
Ok(body) => {
|
||||
req.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/x-www-form-urlencoded")
|
||||
HeaderValue::from_static("application/x-www-form-urlencoded"),
|
||||
);
|
||||
*req.body_mut() = Some(body.into());
|
||||
},
|
||||
}
|
||||
Err(err) => error = Some(crate::error::from(err)),
|
||||
}
|
||||
}
|
||||
@@ -310,12 +304,10 @@ impl RequestBuilder {
|
||||
if let Ok(ref mut req) = self.request {
|
||||
match serde_json::to_vec(json) {
|
||||
Ok(body) => {
|
||||
req.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/json")
|
||||
);
|
||||
req.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||
*req.body_mut() = Some(body.into());
|
||||
},
|
||||
}
|
||||
Err(err) => error = Some(crate::error::from(err)),
|
||||
}
|
||||
}
|
||||
@@ -368,8 +360,7 @@ impl RequestBuilder {
|
||||
|
||||
impl fmt::Debug for Request {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt_request_fields(&mut f.debug_struct("Request"), self)
|
||||
.finish()
|
||||
fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,27 +368,22 @@ impl fmt::Debug for RequestBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut builder = f.debug_struct("RequestBuilder");
|
||||
match self.request {
|
||||
Ok(ref req) => {
|
||||
fmt_request_fields(&mut builder, req)
|
||||
.finish()
|
||||
},
|
||||
Err(ref err) => {
|
||||
builder
|
||||
.field("error", err)
|
||||
.finish()
|
||||
}
|
||||
Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
|
||||
Err(ref err) => builder.field("error", err).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_request_fields<'a, 'b>(f: &'a mut fmt::DebugStruct<'a, 'b>, req: &Request) -> &'a mut fmt::DebugStruct<'a, 'b> {
|
||||
fn fmt_request_fields<'a, 'b>(
|
||||
f: &'a mut fmt::DebugStruct<'a, 'b>,
|
||||
req: &Request,
|
||||
) -> &'a mut fmt::DebugStruct<'a, 'b> {
|
||||
f.field("method", &req.method)
|
||||
.field("url", &req.url)
|
||||
.field("headers", &req.headers)
|
||||
}
|
||||
|
||||
pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
|
||||
|
||||
// IntoIter of HeaderMap yields (Option<HeaderName>, HeaderValue).
|
||||
// The first time a name is yielded, it will be Some(name), and if
|
||||
// there are more values with the same name, the next yield will be
|
||||
@@ -413,11 +399,11 @@ pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
|
||||
Some(key) => {
|
||||
dst.insert(key.clone(), value);
|
||||
prev_name = Some(key);
|
||||
},
|
||||
}
|
||||
None => match prev_name {
|
||||
Some(ref key) => {
|
||||
dst.append(key.clone(), value);
|
||||
},
|
||||
}
|
||||
None => unreachable!("HeaderMap::into_iter yielded None first"),
|
||||
},
|
||||
}
|
||||
@@ -427,8 +413,8 @@ pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Client;
|
||||
use std::collections::BTreeMap;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn add_query_append() {
|
||||
@@ -467,7 +453,10 @@ mod tests {
|
||||
let some_url = "https://google.com/";
|
||||
let r = client.get(some_url);
|
||||
|
||||
let params = Params { foo: "bar".into(), qux: 3 };
|
||||
let params = Params {
|
||||
foo: "bar".into(),
|
||||
qux: 3,
|
||||
};
|
||||
|
||||
let r = r.query(¶ms);
|
||||
|
||||
@@ -510,11 +499,7 @@ mod tests {
|
||||
|
||||
assert_eq!(req.headers()["im-a"], "keeper");
|
||||
|
||||
let foo = req
|
||||
.headers()
|
||||
.get_all("foo")
|
||||
.iter()
|
||||
.collect::<Vec<_>>();
|
||||
let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
|
||||
assert_eq!(foo.len(), 2);
|
||||
assert_eq!(foo[0], "bar");
|
||||
assert_eq!(foo[1], "baz");
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use futures::{Async, Future, Poll, Stream, try_ready};
|
||||
use futures::stream::Concat2;
|
||||
use futures::{try_ready, Async, Future, Poll, Stream};
|
||||
use http;
|
||||
use hyper::{HeaderMap, StatusCode, Version};
|
||||
use hyper::client::connect::HttpInfo;
|
||||
use hyper::header::{CONTENT_LENGTH};
|
||||
use hyper::header::CONTENT_LENGTH;
|
||||
use hyper::{HeaderMap, StatusCode, Version};
|
||||
use log::debug;
|
||||
use mime::Mime;
|
||||
use tokio::timer::Delay;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json;
|
||||
use tokio::timer::Delay;
|
||||
use url::Url;
|
||||
use log::{debug};
|
||||
|
||||
|
||||
use crate::cookie;
|
||||
use super::Decoder;
|
||||
use super::body::Body;
|
||||
|
||||
use super::Decoder;
|
||||
use crate::cookie;
|
||||
|
||||
/// A Response to a submitted `Request`.
|
||||
pub struct Response {
|
||||
@@ -37,7 +35,12 @@ pub struct Response {
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub(super) fn new(res: hyper::Response<hyper::Body>, url: Url, gzip: bool, timeout: Option<Delay>) -> Response {
|
||||
pub(super) fn new(
|
||||
res: hyper::Response<hyper::Body>,
|
||||
url: Url,
|
||||
gzip: bool,
|
||||
timeout: Option<Delay>,
|
||||
) -> Response {
|
||||
let (parts, body) = res.into_parts();
|
||||
let status = parts.status;
|
||||
let version = parts.version;
|
||||
@@ -57,7 +60,6 @@ impl Response {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Get the `StatusCode` of this `Response`.
|
||||
#[inline]
|
||||
pub fn status(&self) -> StatusCode {
|
||||
@@ -77,11 +79,10 @@ impl Response {
|
||||
}
|
||||
|
||||
/// Retrieve the cookies contained in the response.
|
||||
///
|
||||
///
|
||||
/// Note that invalid 'Set-Cookie' headers will be ignored.
|
||||
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
|
||||
cookie::extract_response_cookies(&self.headers)
|
||||
.filter_map(Result::ok)
|
||||
cookie::extract_response_cookies(&self.headers).filter_map(Result::ok)
|
||||
}
|
||||
|
||||
/// Get the final `Url` of this `Response`.
|
||||
@@ -92,8 +93,7 @@ impl Response {
|
||||
|
||||
/// Get the remote address used to get this `Response`.
|
||||
pub fn remote_addr(&self) -> Option<SocketAddr> {
|
||||
self
|
||||
.extensions
|
||||
self.extensions
|
||||
.get::<HttpInfo>()
|
||||
.map(|info| info.remote_addr())
|
||||
}
|
||||
@@ -106,8 +106,7 @@ impl Response {
|
||||
/// - The response is gzipped and automatically decoded (thus changing
|
||||
/// the actual decoded length).
|
||||
pub fn content_length(&self) -> Option<u64> {
|
||||
self
|
||||
.headers()
|
||||
self.headers()
|
||||
.get(CONTENT_LENGTH)
|
||||
.and_then(|ct_len| ct_len.to_str().ok())
|
||||
.and_then(|ct_len| ct_len.parse().ok())
|
||||
@@ -145,27 +144,24 @@ impl Response {
|
||||
}
|
||||
|
||||
/// Get the response text given a specific encoding
|
||||
pub fn text_with_charset(&mut self, default_encoding: &str) -> impl Future<Item = String, Error = crate::Error> {
|
||||
pub fn text_with_charset(
|
||||
&mut self,
|
||||
default_encoding: &str,
|
||||
) -> impl Future<Item = String, Error = crate::Error> {
|
||||
let body = mem::replace(&mut self.body, Decoder::empty());
|
||||
let content_type = self.headers.get(crate::header::CONTENT_TYPE)
|
||||
.and_then(|value| {
|
||||
value.to_str().ok()
|
||||
})
|
||||
.and_then(|value| {
|
||||
value.parse::<Mime>().ok()
|
||||
});
|
||||
let content_type = self
|
||||
.headers
|
||||
.get(crate::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<Mime>().ok());
|
||||
let encoding_name = content_type
|
||||
.as_ref()
|
||||
.and_then(|mime| {
|
||||
mime
|
||||
.get_param("charset")
|
||||
.map(|charset| charset.as_str())
|
||||
})
|
||||
.and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
|
||||
.unwrap_or(default_encoding);
|
||||
let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
|
||||
Text {
|
||||
concat: body.concat2(),
|
||||
encoding
|
||||
encoding,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +252,8 @@ impl<T: Into<Body>> From<http::Response<T>> for Response {
|
||||
let (mut parts, body) = r.into_parts();
|
||||
let body = body.into();
|
||||
let body = Decoder::detect(&mut parts.headers, body, false);
|
||||
let url = parts.extensions
|
||||
let url = parts
|
||||
.extensions
|
||||
.remove::<ResponseUrl>()
|
||||
.unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap()));
|
||||
let url = url.0;
|
||||
@@ -289,8 +286,7 @@ impl<T: DeserializeOwned> Future for Json<T> {
|
||||
|
||||
impl<T> fmt::Debug for Json<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Json")
|
||||
.finish()
|
||||
f.debug_struct("Json").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +304,9 @@ impl Future for Text {
|
||||
// a block because of borrow checker
|
||||
{
|
||||
let (text, _, _) = self.encoding.decode(&bytes);
|
||||
if let Cow::Owned(s) = text { return Ok(Async::Ready(s)) }
|
||||
if let Cow::Owned(s) = text {
|
||||
return Ok(Async::Ready(s));
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
// decoding returned Cow::Borrowed, meaning these bytes
|
||||
@@ -357,9 +355,9 @@ impl ResponseBuilderExt for http::response::Builder {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use url::Url;
|
||||
use super::{Response, ResponseBuilderExt, ResponseUrl};
|
||||
use http::response::Builder;
|
||||
use super::{Response, ResponseUrl, ResponseBuilderExt};
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn test_response_builder_ext() {
|
||||
@@ -370,7 +368,10 @@ mod tests {
|
||||
.body(())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.extensions().get::<ResponseUrl>(), Some(&ResponseUrl(url)));
|
||||
assert_eq!(
|
||||
response.extensions().get::<ResponseUrl>(),
|
||||
Some(&ResponseUrl(url))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
39
src/body.rs
39
src/body.rs
@@ -1,12 +1,12 @@
|
||||
use std::fs::File;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Cursor, Read};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{Future, try_ready};
|
||||
use futures::{try_ready, Future};
|
||||
use hyper::{self};
|
||||
|
||||
use crate::{async_impl};
|
||||
use crate::async_impl;
|
||||
|
||||
/// The body of a `Request`.
|
||||
///
|
||||
@@ -102,7 +102,7 @@ impl Body {
|
||||
tx,
|
||||
};
|
||||
(Some(tx), async_impl::Body::wrap(rx), len)
|
||||
},
|
||||
}
|
||||
Kind::Bytes(chunk) => {
|
||||
let len = chunk.len() as u64;
|
||||
(None, async_impl::Body::reusable(chunk), Some(len))
|
||||
@@ -111,12 +111,10 @@ impl Body {
|
||||
}
|
||||
|
||||
pub(crate) fn try_clone(&self) -> Option<Body> {
|
||||
self.kind.try_clone()
|
||||
.map(|kind| Body { kind })
|
||||
self.kind.try_clone().map(|kind| Body { kind })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum Kind {
|
||||
Reader(Box<dyn Read + Send>, Option<u64>),
|
||||
Bytes(Bytes),
|
||||
@@ -147,7 +145,6 @@ impl From<String> for Body {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<&'static [u8]> for Body {
|
||||
#[inline]
|
||||
fn from(s: &'static [u8]) -> Body {
|
||||
@@ -177,7 +174,8 @@ impl From<File> for Body {
|
||||
impl fmt::Debug for Kind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Kind::Reader(_, ref v) => f.debug_struct("Reader")
|
||||
Kind::Reader(_, ref v) => f
|
||||
.debug_struct("Reader")
|
||||
.field("length", &DebugLength(v))
|
||||
.finish(),
|
||||
Kind::Bytes(ref v) => fmt::Debug::fmt(v, f),
|
||||
@@ -218,10 +216,10 @@ pub(crate) struct Sender {
|
||||
impl Sender {
|
||||
// A `Future` that may do blocking read calls.
|
||||
// As a `Future`, this integrates easily with `wait::timeout`.
|
||||
pub(crate) fn send(self) -> impl Future<Item=(), Error=crate::Error> {
|
||||
use std::cmp;
|
||||
pub(crate) fn send(self) -> impl Future<Item = (), Error = crate::Error> {
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::future;
|
||||
use std::cmp;
|
||||
|
||||
let con_len = self.body.1;
|
||||
let cap = cmp::min(self.body.1.unwrap_or(8192), 8192);
|
||||
@@ -261,15 +259,12 @@ impl Sender {
|
||||
// read. Return.
|
||||
return Ok(().into());
|
||||
}
|
||||
Ok(n) => {
|
||||
unsafe { buf.advance_mut(n); }
|
||||
}
|
||||
Ok(n) => unsafe {
|
||||
buf.advance_mut(n);
|
||||
},
|
||||
Err(e) => {
|
||||
let ret = io::Error::new(e.kind(), e.to_string());
|
||||
tx
|
||||
.take()
|
||||
.expect("tx only taken on error")
|
||||
.abort();
|
||||
tx.take().expect("tx only taken on error").abort();
|
||||
return Err(crate::error::from(ret));
|
||||
}
|
||||
}
|
||||
@@ -297,8 +292,8 @@ impl Sender {
|
||||
pub(crate) fn read_to_string(mut body: Body) -> io::Result<String> {
|
||||
let mut s = String::new();
|
||||
match body.kind {
|
||||
Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s),
|
||||
Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s),
|
||||
}
|
||||
.map(|_| s)
|
||||
Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s),
|
||||
Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s),
|
||||
}
|
||||
.map(|_| s)
|
||||
}
|
||||
|
||||
159
src/client.rs
159
src/client.rs
@@ -1,18 +1,18 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::thread;
|
||||
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::sync::{mpsc, oneshot};
|
||||
use futures::{Async, Future, Stream};
|
||||
|
||||
use log::{trace};
|
||||
use log::trace;
|
||||
|
||||
use crate::request::{Request, RequestBuilder};
|
||||
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")]
|
||||
use crate::{Certificate, Identity};
|
||||
|
||||
@@ -81,9 +81,7 @@ impl ClientBuilder {
|
||||
/// This method fails if TLS backend cannot be initialized, or the resolver
|
||||
/// cannot load the system configuration.
|
||||
pub fn build(self) -> crate::Result<Client> {
|
||||
ClientHandle::new(self).map(|handle| Client {
|
||||
inner: handle,
|
||||
})
|
||||
ClientHandle::new(self).map(|handle| Client { inner: handle })
|
||||
}
|
||||
|
||||
/// Disable proxy setting.
|
||||
@@ -184,7 +182,6 @@ impl ClientBuilder {
|
||||
self.with_inner(move |inner| inner.identity(identity))
|
||||
}
|
||||
|
||||
|
||||
/// Controls the use of hostname verification.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
@@ -399,7 +396,6 @@ impl ClientBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Client {
|
||||
/// Constructs a new `Client`.
|
||||
///
|
||||
@@ -411,9 +407,7 @@ impl Client {
|
||||
/// Use `Client::builder()` if you wish to handle the failure as an `Error`
|
||||
/// instead of panicking.
|
||||
pub fn new() -> Client {
|
||||
ClientBuilder::new()
|
||||
.build()
|
||||
.expect("Client::new()")
|
||||
ClientBuilder::new().build().expect("Client::new()")
|
||||
}
|
||||
|
||||
/// Creates a `ClientBuilder` to configure a `Client`.
|
||||
@@ -486,9 +480,7 @@ impl Client {
|
||||
///
|
||||
/// This method fails whenever supplied `Url` cannot be parsed.
|
||||
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
|
||||
let req = url
|
||||
.into_url()
|
||||
.map(move |url| Request::new(method, url));
|
||||
let req = url.into_url().map(move |url| Request::new(method, url));
|
||||
RequestBuilder::new(self.clone(), req)
|
||||
}
|
||||
|
||||
@@ -521,22 +513,24 @@ impl fmt::Debug for Client {
|
||||
|
||||
impl fmt::Debug for ClientBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("ClientBuilder")
|
||||
.finish()
|
||||
f.debug_struct("ClientBuilder").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ClientHandle {
|
||||
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 {
|
||||
tx: Option<ThreadSender>,
|
||||
thread: Option<thread::JoinHandle<()>>
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Drop for InnerClientHandle {
|
||||
@@ -552,66 +546,64 @@ impl ClientHandle {
|
||||
let builder = builder.inner;
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let (spawn_tx, spawn_rx) = oneshot::channel::<crate::Result<()>>();
|
||||
let handle = try_!(thread::Builder::new().name("reqwest-internal-sync-runtime".into()).spawn(move || {
|
||||
use tokio::runtime::current_thread::Runtime;
|
||||
let handle = try_!(thread::Builder::new()
|
||||
.name("reqwest-internal-sync-runtime".into())
|
||||
.spawn(move || {
|
||||
use tokio::runtime::current_thread::Runtime;
|
||||
|
||||
let built = (|| {
|
||||
let rt = try_!(Runtime::new());
|
||||
let client = builder.build()?;
|
||||
Ok((rt, client))
|
||||
})();
|
||||
let built = (|| {
|
||||
let rt = try_!(Runtime::new());
|
||||
let client = builder.build()?;
|
||||
Ok((rt, client))
|
||||
})();
|
||||
|
||||
let (mut rt, client) = match built {
|
||||
Ok((rt, c)) => {
|
||||
if spawn_tx.send(Ok(())).is_err() {
|
||||
let (mut rt, client) = match built {
|
||||
Ok((rt, c)) => {
|
||||
if spawn_tx.send(Ok(())).is_err() {
|
||||
return;
|
||||
}
|
||||
(rt, c)
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = spawn_tx.send(Err(e));
|
||||
return;
|
||||
}
|
||||
(rt, c)
|
||||
},
|
||||
Err(e) => {
|
||||
let _ = spawn_tx.send(Err(e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let work = rx.for_each(move |(req, tx)| {
|
||||
let mut tx_opt: Option<oneshot::Sender<crate::Result<async_impl::Response>>> = Some(tx);
|
||||
let mut res_fut = client.execute(req);
|
||||
let work = rx.for_each(move |(req, tx)| {
|
||||
let mut tx_opt: Option<oneshot::Sender<crate::Result<async_impl::Response>>> =
|
||||
Some(tx);
|
||||
let mut res_fut = client.execute(req);
|
||||
|
||||
let task = future::poll_fn(move || {
|
||||
let canceled = tx_opt
|
||||
.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()
|
||||
let task = future::poll_fn(move || {
|
||||
let canceled = tx_opt
|
||||
.as_mut()
|
||||
.expect("polled after complete")
|
||||
.send(result);
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
.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").send(result);
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
});
|
||||
tokio::spawn(task);
|
||||
Ok(())
|
||||
});
|
||||
tokio::spawn(task);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
|
||||
// work is Future<(), ()>, and our closure will never return Err
|
||||
rt.block_on(work)
|
||||
.expect("runtime unexpected error");
|
||||
}));
|
||||
// work is Future<(), ()>, and our closure will never return Err
|
||||
rt.block_on(work).expect("runtime unexpected error");
|
||||
}));
|
||||
|
||||
// Wait for the runtime thread to start up...
|
||||
match spawn_rx.wait() {
|
||||
@@ -620,13 +612,11 @@ impl ClientHandle {
|
||||
Err(_canceled) => event_loop_panicked(),
|
||||
}
|
||||
|
||||
|
||||
let inner_handle = Arc::new(InnerClientHandle {
|
||||
tx: Some(tx),
|
||||
thread: Some(handle)
|
||||
thread: Some(handle),
|
||||
});
|
||||
|
||||
|
||||
Ok(ClientHandle {
|
||||
timeout,
|
||||
inner: inner_handle,
|
||||
@@ -637,7 +627,8 @@ impl ClientHandle {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let (req, body) = req.into_async();
|
||||
let url = req.url().clone();
|
||||
self.inner.tx
|
||||
self.inner
|
||||
.tx
|
||||
.as_ref()
|
||||
.expect("core thread exited early")
|
||||
.unbounded_send((req, tx))
|
||||
@@ -645,7 +636,7 @@ impl ClientHandle {
|
||||
|
||||
let write = if let Some(body) = body {
|
||||
Either::A(body.send())
|
||||
//try_!(body.send(self.timeout.0), &url);
|
||||
//try_!(body.send(self.timeout.0), &url);
|
||||
} else {
|
||||
Either::B(future::ok(()))
|
||||
};
|
||||
@@ -657,15 +648,17 @@ impl ClientHandle {
|
||||
let res = match wait::timeout(fut, self.timeout.0) {
|
||||
Ok(res) => res,
|
||||
Err(wait::Waited::TimedOut) => return Err(crate::error::timedout(Some(url))),
|
||||
Err(wait::Waited::Executor(err)) => {
|
||||
return Err(crate::error::from(err).with_url(url))
|
||||
},
|
||||
Err(wait::Waited::Executor(err)) => return Err(crate::error::from(err).with_url(url)),
|
||||
Err(wait::Waited::Inner(err)) => {
|
||||
return Err(err.with_url(url));
|
||||
},
|
||||
}
|
||||
};
|
||||
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())),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
493
src/connect.rs
493
src/connect.rs
@@ -4,16 +4,16 @@ use hyper::client::connect::{Connect, Connected, Destination};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_timer::Timeout;
|
||||
|
||||
#[cfg(feature = "default-tls")]
|
||||
use native_tls::{TlsConnector, TlsConnectorBuilder};
|
||||
#[cfg(feature = "tls")]
|
||||
use futures::Poll;
|
||||
#[cfg(feature = "tls")]
|
||||
use bytes::BufMut;
|
||||
#[cfg(feature = "tls")]
|
||||
use futures::Poll;
|
||||
#[cfg(feature = "default-tls")]
|
||||
use native_tls::{TlsConnector, TlsConnectorBuilder};
|
||||
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "trust-dns")]
|
||||
@@ -25,13 +25,12 @@ type HttpConnector = hyper::client::HttpConnector<TrustDnsResolver>;
|
||||
#[cfg(not(feature = "trust-dns"))]
|
||||
type HttpConnector = hyper::client::HttpConnector;
|
||||
|
||||
|
||||
pub(crate) struct Connector {
|
||||
inner: Inner,
|
||||
proxies: Arc<Vec<Proxy>>,
|
||||
timeout: Option<Duration>,
|
||||
#[cfg(feature = "tls")]
|
||||
nodelay: bool
|
||||
nodelay: bool,
|
||||
}
|
||||
|
||||
enum Inner {
|
||||
@@ -43,17 +42,20 @@ enum Inner {
|
||||
RustlsTls {
|
||||
http: HttpConnector,
|
||||
tls: Arc<rustls::ClientConfig>,
|
||||
tls_proxy: Arc<rustls::ClientConfig>
|
||||
}
|
||||
tls_proxy: Arc<rustls::ClientConfig>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Connector {
|
||||
#[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
|
||||
T: Into<Option<IpAddr>>
|
||||
T: Into<Option<IpAddr>>,
|
||||
{
|
||||
|
||||
let mut http = http_connector()?;
|
||||
http.set_local_address(local_addr.into());
|
||||
http.set_nodelay(nodelay);
|
||||
@@ -69,9 +71,10 @@ impl Connector {
|
||||
tls: TlsConnectorBuilder,
|
||||
proxies: Arc<Vec<Proxy>>,
|
||||
local_addr: T,
|
||||
nodelay: bool) -> crate::Result<Connector>
|
||||
where
|
||||
T: Into<Option<IpAddr>>,
|
||||
nodelay: bool,
|
||||
) -> crate::Result<Connector>
|
||||
where
|
||||
T: Into<Option<IpAddr>>,
|
||||
{
|
||||
let tls = try_!(tls.build());
|
||||
|
||||
@@ -83,7 +86,7 @@ impl Connector {
|
||||
inner: Inner::DefaultTls(http, tls),
|
||||
proxies,
|
||||
timeout: None,
|
||||
nodelay
|
||||
nodelay,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -92,9 +95,10 @@ impl Connector {
|
||||
tls: rustls::ClientConfig,
|
||||
proxies: Arc<Vec<Proxy>>,
|
||||
local_addr: T,
|
||||
nodelay: bool) -> crate::Result<Connector>
|
||||
where
|
||||
T: Into<Option<IpAddr>>,
|
||||
nodelay: bool,
|
||||
) -> crate::Result<Connector>
|
||||
where
|
||||
T: Into<Option<IpAddr>>,
|
||||
{
|
||||
let mut http = http_connector()?;
|
||||
http.set_local_address(local_addr.into());
|
||||
@@ -110,10 +114,14 @@ impl Connector {
|
||||
};
|
||||
|
||||
Ok(Connector {
|
||||
inner: Inner::RustlsTls { http, tls, tls_proxy },
|
||||
inner: Inner::RustlsTls {
|
||||
http,
|
||||
tls,
|
||||
tls_proxy,
|
||||
},
|
||||
proxies,
|
||||
timeout: None,
|
||||
nodelay
|
||||
nodelay,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -138,56 +146,62 @@ impl Connector {
|
||||
} else {
|
||||
Box::new($future)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let dns = match proxy {
|
||||
ProxyScheme::Socks5 { remote_dns: false, .. } => socks::DnsResolve::Local,
|
||||
ProxyScheme::Socks5 { remote_dns: true, .. } => socks::DnsResolve::Proxy,
|
||||
ProxyScheme::Socks5 {
|
||||
remote_dns: false, ..
|
||||
} => socks::DnsResolve::Local,
|
||||
ProxyScheme::Socks5 {
|
||||
remote_dns: true, ..
|
||||
} => socks::DnsResolve::Proxy,
|
||||
ProxyScheme::Http { .. } => {
|
||||
unreachable!("connect_socks is only called for socks proxies");
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
match &self.inner {
|
||||
#[cfg(feature = "default-tls")]
|
||||
Inner::DefaultTls(_http, tls) => if dst.scheme() == "https" {
|
||||
use self::native_tls_async::TlsConnectorExt;
|
||||
Inner::DefaultTls(_http, tls) => {
|
||||
if dst.scheme() == "https" {
|
||||
use self::native_tls_async::TlsConnectorExt;
|
||||
|
||||
let tls = tls.clone();
|
||||
let host = dst.host().to_owned();
|
||||
let socks_connecting = socks::connect(proxy, dst, dns);
|
||||
return timeout!(socks_connecting.and_then(move |(conn, connected)| {
|
||||
tls.connect_async(&host, conn)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
.map(move |io| (Box::new(io) as Conn, connected))
|
||||
}));
|
||||
},
|
||||
let tls = tls.clone();
|
||||
let host = dst.host().to_owned();
|
||||
let socks_connecting = socks::connect(proxy, dst, dns);
|
||||
return timeout!(socks_connecting.and_then(move |(conn, connected)| {
|
||||
tls.connect_async(&host, conn)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
.map(move |io| (Box::new(io) as Conn, connected))
|
||||
}));
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
Inner::RustlsTls { tls_proxy, .. } => if dst.scheme() == "https" {
|
||||
use tokio_rustls::TlsConnector as RustlsConnector;
|
||||
use tokio_rustls::webpki::DNSNameRef;
|
||||
Inner::RustlsTls { tls_proxy, .. } => {
|
||||
if dst.scheme() == "https" {
|
||||
use tokio_rustls::webpki::DNSNameRef;
|
||||
use tokio_rustls::TlsConnector as RustlsConnector;
|
||||
|
||||
let tls = tls_proxy.clone();
|
||||
let host = dst.host().to_owned();
|
||||
let socks_connecting = socks::connect(proxy, dst, dns);
|
||||
return timeout!(socks_connecting.and_then(move |(conn, connected)| {
|
||||
let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host)
|
||||
.map(|dnsname| dnsname.to_owned())
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name"));
|
||||
futures::future::result(maybe_dnsname)
|
||||
.and_then(move |dnsname| {
|
||||
RustlsConnector::from(tls).connect(dnsname.as_ref(), conn)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
})
|
||||
.map(move |io| {
|
||||
(Box::new(io) as Conn, connected)
|
||||
})
|
||||
}));
|
||||
},
|
||||
let tls = tls_proxy.clone();
|
||||
let host = dst.host().to_owned();
|
||||
let socks_connecting = socks::connect(proxy, dst, dns);
|
||||
return timeout!(socks_connecting.and_then(move |(conn, connected)| {
|
||||
let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host)
|
||||
.map(|dnsname| dnsname.to_owned())
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name"));
|
||||
futures::future::result(maybe_dnsname)
|
||||
.and_then(move |dnsname| {
|
||||
RustlsConnector::from(tls)
|
||||
.connect(dnsname.as_ref(), conn)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
})
|
||||
.map(move |io| (Box::new(io) as Conn, connected))
|
||||
}));
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "tls"))]
|
||||
Inner::Http(_) => ()
|
||||
Inner::Http(_) => (),
|
||||
}
|
||||
|
||||
// else no TLS
|
||||
@@ -231,12 +245,13 @@ impl Connect for Connector {
|
||||
} else {
|
||||
Box::new($future)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! connect {
|
||||
( $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))))
|
||||
};
|
||||
( $dst:expr, $proxy:expr ) => {
|
||||
@@ -250,17 +265,16 @@ impl Connect for Connector {
|
||||
http.set_nodelay(nodelay || ($dst.scheme() == "https"));
|
||||
|
||||
let http = hyper_tls::HttpsConnector::from((http, tls.clone()));
|
||||
timeout!(http.connect($dst)
|
||||
.and_then(move |(io, connected)| {
|
||||
if let hyper_tls::MaybeHttpsStream::Https(stream) = &io {
|
||||
if !nodelay {
|
||||
stream.get_ref().get_ref().set_nodelay(false)?;
|
||||
}
|
||||
timeout!(http.connect($dst).and_then(move |(io, connected)| {
|
||||
if let hyper_tls::MaybeHttpsStream::Https(stream) = &io {
|
||||
if !nodelay {
|
||||
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")]
|
||||
Inner::RustlsTls { http, tls, .. } => {
|
||||
let mut http = http.clone();
|
||||
@@ -271,17 +285,16 @@ impl Connect for Connector {
|
||||
http.set_nodelay(nodelay || ($dst.scheme() == "https"));
|
||||
|
||||
let http = hyper_rustls::HttpsConnector::from((http, tls.clone()));
|
||||
timeout!(http.connect($dst)
|
||||
.and_then(move |(io, connected)| {
|
||||
if let hyper_rustls::MaybeHttpsStream::Https(stream) = &io {
|
||||
if !nodelay {
|
||||
let (io, _) = stream.get_ref();
|
||||
io.set_nodelay(false)?;
|
||||
}
|
||||
timeout!(http.connect($dst).and_then(move |(io, connected)| {
|
||||
if let hyper_rustls::MaybeHttpsStream::Https(stream) = &io {
|
||||
if !nodelay {
|
||||
let (io, _) = stream.get_ref();
|
||||
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 new_scheme = puri
|
||||
.scheme_part()
|
||||
.map(Scheme::as_str)
|
||||
.unwrap_or("http");
|
||||
let new_scheme = puri.scheme_part().map(Scheme::as_str).unwrap_or("http");
|
||||
ndst.set_scheme(new_scheme)
|
||||
.expect("proxy target scheme should be valid");
|
||||
|
||||
@@ -316,60 +326,81 @@ impl Connect for Connector {
|
||||
|
||||
match &self.inner {
|
||||
#[cfg(feature = "default-tls")]
|
||||
Inner::DefaultTls(http, tls) => if dst.scheme() == "https" {
|
||||
use self::native_tls_async::TlsConnectorExt;
|
||||
Inner::DefaultTls(http, tls) => {
|
||||
if dst.scheme() == "https" {
|
||||
use self::native_tls_async::TlsConnectorExt;
|
||||
|
||||
let host = dst.host().to_owned();
|
||||
let port = dst.port().unwrap_or(443);
|
||||
let mut http = http.clone();
|
||||
http.set_nodelay(nodelay);
|
||||
let http = hyper_tls::HttpsConnector::from((http, tls.clone()));
|
||||
let tls = tls.clone();
|
||||
return timeout!(http.connect(ndst).and_then(move |(conn, connected)| {
|
||||
log::trace!("tunneling HTTPS over proxy");
|
||||
tunnel(conn, host.clone(), port, auth)
|
||||
.and_then(move |tunneled| {
|
||||
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)))
|
||||
}));
|
||||
},
|
||||
let host = dst.host().to_owned();
|
||||
let port = dst.port().unwrap_or(443);
|
||||
let mut http = http.clone();
|
||||
http.set_nodelay(nodelay);
|
||||
let http = hyper_tls::HttpsConnector::from((http, tls.clone()));
|
||||
let tls = tls.clone();
|
||||
return timeout!(http.connect(ndst).and_then(
|
||||
move |(conn, connected)| {
|
||||
log::trace!("tunneling HTTPS over proxy");
|
||||
tunnel(conn, host.clone(), port, auth)
|
||||
.and_then(move |tunneled| {
|
||||
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)))
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
Inner::RustlsTls { http, tls, tls_proxy } => if dst.scheme() == "https" {
|
||||
use rustls::Session;
|
||||
use tokio_rustls::TlsConnector as RustlsConnector;
|
||||
use tokio_rustls::webpki::DNSNameRef;
|
||||
Inner::RustlsTls {
|
||||
http,
|
||||
tls,
|
||||
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 port = dst.port().unwrap_or(443);
|
||||
let mut http = http.clone();
|
||||
http.set_nodelay(nodelay);
|
||||
let http = hyper_rustls::HttpsConnector::from((http, tls_proxy.clone()));
|
||||
let tls = tls.clone();
|
||||
return timeout!(http.connect(ndst).and_then(move |(conn, connected)| {
|
||||
log::trace!("tunneling HTTPS over proxy");
|
||||
let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host)
|
||||
.map(|dnsname| dnsname.to_owned())
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name"));
|
||||
tunnel(conn, host, port, auth)
|
||||
.and_then(move |tunneled| Ok((maybe_dnsname?, tunneled)))
|
||||
.and_then(move |(dnsname, tunneled)| {
|
||||
RustlsConnector::from(tls).connect(dnsname.as_ref(), tunneled)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
})
|
||||
.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))
|
||||
})
|
||||
}));
|
||||
},
|
||||
let host = dst.host().to_owned();
|
||||
let port = dst.port().unwrap_or(443);
|
||||
let mut http = http.clone();
|
||||
http.set_nodelay(nodelay);
|
||||
let http =
|
||||
hyper_rustls::HttpsConnector::from((http, tls_proxy.clone()));
|
||||
let tls = tls.clone();
|
||||
return timeout!(http.connect(ndst).and_then(
|
||||
move |(conn, connected)| {
|
||||
log::trace!("tunneling HTTPS over proxy");
|
||||
let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host)
|
||||
.map(|dnsname| dnsname.to_owned())
|
||||
.map_err(|_| {
|
||||
io::Error::new(io::ErrorKind::Other, "Invalid DNS Name")
|
||||
});
|
||||
tunnel(conn, host, port, auth)
|
||||
.and_then(move |tunneled| Ok((maybe_dnsname?, tunneled)))
|
||||
.and_then(move |(dnsname, tunneled)| {
|
||||
RustlsConnector::from(tls)
|
||||
.connect(dnsname.as_ref(), tunneled)
|
||||
.map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
})
|
||||
})
|
||||
.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"))]
|
||||
Inner::Http(_) => ()
|
||||
Inner::Http(_) => (),
|
||||
}
|
||||
|
||||
return connect!(ndst, true);
|
||||
@@ -384,21 +415,30 @@ pub(crate) trait AsyncConn: AsyncRead + AsyncWrite {}
|
||||
impl<T: AsyncRead + AsyncWrite> AsyncConn for T {}
|
||||
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")]
|
||||
fn tunnel<T>(conn: T, host: String, port: u16, 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();
|
||||
fn tunnel<T>(
|
||||
conn: T,
|
||||
host: String,
|
||||
port: u16,
|
||||
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 {
|
||||
log::debug!("tunnel to {}:{} using basic auth", host, port);
|
||||
buf.extend_from_slice(b"Proxy-Authorization: ");
|
||||
buf.extend_from_slice(value.as_bytes());
|
||||
buf.extend_from_slice(b"\r\n");
|
||||
}
|
||||
if let Some(value) = auth {
|
||||
log::debug!("tunnel to {}:{} using basic auth", host, port);
|
||||
buf.extend_from_slice(b"Proxy-Authorization: ");
|
||||
buf.extend_from_slice(value.as_bytes());
|
||||
buf.extend_from_slice(b"\r\n");
|
||||
}
|
||||
|
||||
// headers end
|
||||
buf.extend_from_slice(b"\r\n");
|
||||
@@ -420,7 +460,7 @@ struct Tunnel<T> {
|
||||
#[cfg(feature = "tls")]
|
||||
enum TunnelState {
|
||||
Writing,
|
||||
Reading
|
||||
Reading,
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
@@ -442,7 +482,11 @@ where
|
||||
return Err(tunnel_eof());
|
||||
}
|
||||
} 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()[..];
|
||||
if n == 0 {
|
||||
return Err(tunnel_eof());
|
||||
@@ -451,9 +495,12 @@ where
|
||||
if read.ends_with(b"\r\n\r\n") {
|
||||
return Ok(self.conn.take().unwrap().into());
|
||||
}
|
||||
// else read more
|
||||
// else read more
|
||||
} 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") {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
@@ -477,7 +524,7 @@ where
|
||||
fn tunnel_eof() -> io::Error {
|
||||
io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"unexpected eof while tunneling"
|
||||
"unexpected eof while tunneling",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -485,9 +532,9 @@ fn tunnel_eof() -> io::Error {
|
||||
mod native_tls_async {
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use futures::{Poll, Future, Async};
|
||||
use native_tls::{self, HandshakeError, Error, TlsConnector};
|
||||
use tokio_io::{AsyncRead, AsyncWrite, try_nb};
|
||||
use futures::{Async, Future, Poll};
|
||||
use native_tls::{self, Error, HandshakeError, TlsConnector};
|
||||
use tokio_io::{try_nb, AsyncRead, AsyncWrite};
|
||||
|
||||
/// A wrapper around an underlying raw stream which implements the TLS or SSL
|
||||
/// protocol.
|
||||
@@ -533,7 +580,8 @@ mod native_tls_async {
|
||||
/// and `AsyncWrite` traits as well, otherwise this function will not work
|
||||
/// properly.
|
||||
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 {
|
||||
@@ -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> {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
@@ -569,7 +615,8 @@ mod native_tls_async {
|
||||
|
||||
impl TlsConnectorExt for TlsConnector {
|
||||
fn connect_async<S>(&self, domain: &str, stream: S) -> ConnectAsync<S>
|
||||
where S: Read + Write,
|
||||
where
|
||||
S: Read + Write,
|
||||
{
|
||||
ConnectAsync {
|
||||
inner: MidHandshake {
|
||||
@@ -600,51 +647,42 @@ mod native_tls_async {
|
||||
match self.inner.take().expect("cannot poll MidHandshake twice") {
|
||||
Ok(stream) => Ok(TlsStream { inner: stream }.into()),
|
||||
Err(HandshakeError::Failure(e)) => Err(e),
|
||||
Err(HandshakeError::WouldBlock(s)) => {
|
||||
match s.handshake() {
|
||||
Ok(stream) => Ok(TlsStream { inner: stream }.into()),
|
||||
Err(HandshakeError::Failure(e)) => Err(e),
|
||||
Err(HandshakeError::WouldBlock(s)) => {
|
||||
self.inner = Some(Err(HandshakeError::WouldBlock(s)));
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
Err(HandshakeError::WouldBlock(s)) => match s.handshake() {
|
||||
Ok(stream) => Ok(TlsStream { inner: stream }.into()),
|
||||
Err(HandshakeError::Failure(e)) => Err(e),
|
||||
Err(HandshakeError::WouldBlock(s)) => {
|
||||
self.inner = Some(Err(HandshakeError::WouldBlock(s)));
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "socks")]
|
||||
mod socks {
|
||||
use std::io;
|
||||
|
||||
use futures::{Future, future};
|
||||
use futures::{future, Future};
|
||||
use hyper::client::connect::{Connected, Destination};
|
||||
use socks::Socks5Stream;
|
||||
use std::net::ToSocketAddrs;
|
||||
use tokio::{net::TcpStream, reactor};
|
||||
|
||||
use super::{Connecting};
|
||||
use crate::proxy::{ProxyScheme};
|
||||
use super::Connecting;
|
||||
use crate::proxy::ProxyScheme;
|
||||
|
||||
pub(super) enum DnsResolve {
|
||||
Local,
|
||||
Proxy,
|
||||
}
|
||||
|
||||
pub(super) fn connect(
|
||||
proxy: ProxyScheme,
|
||||
dst: Destination,
|
||||
dns: DnsResolve,
|
||||
) -> Connecting {
|
||||
pub(super) fn connect(proxy: ProxyScheme, dst: Destination, dns: DnsResolve) -> Connecting {
|
||||
let https = dst.scheme() == "https";
|
||||
let original_host = dst.host().to_owned();
|
||||
let mut host = original_host.clone();
|
||||
let port = dst.port().unwrap_or_else(|| {
|
||||
if https { 443 } else { 80 }
|
||||
});
|
||||
let port = dst.port().unwrap_or_else(|| if https { 443 } else { 80 });
|
||||
|
||||
if let DnsResolve::Local = dns {
|
||||
let maybe_new_target = match (host.as_str(), port).to_socket_addrs() {
|
||||
@@ -664,20 +702,24 @@ mod socks {
|
||||
};
|
||||
|
||||
// Get a Tokio TcpStream
|
||||
let stream = future::result(if let Some((username, password)) = auth {
|
||||
Socks5Stream::connect_with_password(
|
||||
socket_addr, (host.as_str(), port), &username, &password
|
||||
)
|
||||
} 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))
|
||||
}));
|
||||
let stream = future::result(
|
||||
if let Some((username, password)) = auth {
|
||||
Socks5Stream::connect_with_password(
|
||||
socket_addr,
|
||||
(host.as_str(), port),
|
||||
&username,
|
||||
&password,
|
||||
)
|
||||
} 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(
|
||||
stream.map(|s| (Box::new(s) as super::Conn, Connected::new()))
|
||||
)
|
||||
Box::new(stream.map(|s| (Box::new(s) as super::Conn, Connected::new())))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,14 +728,14 @@ mod socks {
|
||||
mod tests {
|
||||
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 super::tunnel;
|
||||
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"\
|
||||
HTTP/1.1 200 OK\r\n\
|
||||
@@ -701,21 +743,27 @@ mod tests {
|
||||
";
|
||||
|
||||
macro_rules! mock_tunnel {
|
||||
() => ({
|
||||
() => {{
|
||||
mock_tunnel!(TUNNEL_OK)
|
||||
});
|
||||
($write:expr) => ({
|
||||
}};
|
||||
($write:expr) => {{
|
||||
mock_tunnel!($write, "")
|
||||
});
|
||||
($write:expr, $auth:expr) => ({
|
||||
}};
|
||||
($write:expr, $auth:expr) => {{
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let connect_expected = format!("\
|
||||
CONNECT {0}:{1} HTTP/1.1\r\n\
|
||||
Host: {0}:{1}\r\n\
|
||||
{2}\
|
||||
\r\n\
|
||||
", addr.ip(), addr.port(), $auth).into_bytes();
|
||||
let connect_expected = format!(
|
||||
"\
|
||||
CONNECT {0}:{1} HTTP/1.1\r\n\
|
||||
Host: {0}:{1}\r\n\
|
||||
{2}\
|
||||
\r\n\
|
||||
",
|
||||
addr.ip(),
|
||||
addr.port(),
|
||||
$auth
|
||||
)
|
||||
.into_bytes();
|
||||
|
||||
thread::spawn(move || {
|
||||
let (mut sock, _) = listener.accept().unwrap();
|
||||
@@ -726,7 +774,7 @@ mod tests {
|
||||
sock.write_all($write).unwrap();
|
||||
});
|
||||
addr
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -737,9 +785,7 @@ mod tests {
|
||||
let work = TcpStream::connect(&addr);
|
||||
let host = addr.ip().to_string();
|
||||
let port = addr.port();
|
||||
let work = work.and_then(|tcp| {
|
||||
tunnel(tcp, host, port, None)
|
||||
});
|
||||
let work = work.and_then(|tcp| tunnel(tcp, host, port, None));
|
||||
|
||||
rt.block_on(work).unwrap();
|
||||
}
|
||||
@@ -752,9 +798,7 @@ mod tests {
|
||||
let work = TcpStream::connect(&addr);
|
||||
let host = addr.ip().to_string();
|
||||
let port = addr.port();
|
||||
let work = work.and_then(|tcp| {
|
||||
tunnel(tcp, host, port, None)
|
||||
});
|
||||
let work = work.and_then(|tcp| tunnel(tcp, host, port, None));
|
||||
|
||||
rt.block_on(work).unwrap_err();
|
||||
}
|
||||
@@ -767,28 +811,26 @@ mod tests {
|
||||
let work = TcpStream::connect(&addr);
|
||||
let host = addr.ip().to_string();
|
||||
let port = addr.port();
|
||||
let work = work.and_then(|tcp| {
|
||||
tunnel(tcp, host, port, None)
|
||||
});
|
||||
let work = work.and_then(|tcp| tunnel(tcp, host, port, None));
|
||||
|
||||
rt.block_on(work).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tunnel_proxy_unauthorized() {
|
||||
let addr = mock_tunnel!(b"\
|
||||
let addr = mock_tunnel!(
|
||||
b"\
|
||||
HTTP/1.1 407 Proxy Authentication Required\r\n\
|
||||
Proxy-Authenticate: Basic realm=\"nope\"\r\n\
|
||||
\r\n\
|
||||
");
|
||||
"
|
||||
);
|
||||
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
let work = TcpStream::connect(&addr);
|
||||
let host = addr.ip().to_string();
|
||||
let port = addr.port();
|
||||
let work = work.and_then(|tcp| {
|
||||
tunnel(tcp, host, port, None)
|
||||
});
|
||||
let work = work.and_then(|tcp| tunnel(tcp, host, port, None));
|
||||
|
||||
let error = rt.block_on(work).unwrap_err();
|
||||
assert_eq!(error.to_string(), "proxy authentication required");
|
||||
@@ -806,7 +848,12 @@ mod tests {
|
||||
let host = addr.ip().to_string();
|
||||
let port = addr.port();
|
||||
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();
|
||||
|
||||
@@ -109,7 +109,9 @@ impl<'a> Cookie<'a> {
|
||||
|
||||
/// Get the Max-Age information.
|
||||
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.
|
||||
|
||||
22
src/dns.rs
22
src/dns.rs
@@ -1,6 +1,6 @@
|
||||
use std::{io, vec};
|
||||
use std::net::IpAddr;
|
||||
use std::sync::{Arc, Mutex, Once};
|
||||
use std::{io, vec};
|
||||
|
||||
use futures::{future, Future};
|
||||
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.
|
||||
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)]
|
||||
pub(crate) struct TrustDnsResolver {
|
||||
@@ -31,9 +31,7 @@ impl TrustDnsResolver {
|
||||
let (conf, opts) = system_conf::read_system_conf()?;
|
||||
let (resolver, bg) = AsyncResolver::new(conf, opts);
|
||||
|
||||
let resolver: ErasedResolver = Box::new(move |name| {
|
||||
resolver.lookup_ip(name.as_str())
|
||||
});
|
||||
let resolver: ErasedResolver = Box::new(move |name| resolver.lookup_ip(name.as_str()));
|
||||
let background = Mutex::new(Some(Box::new(bg) as Background));
|
||||
let once = Once::new();
|
||||
|
||||
@@ -49,7 +47,7 @@ impl TrustDnsResolver {
|
||||
|
||||
impl hyper_dns::Resolve for TrustDnsResolver {
|
||||
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 {
|
||||
let inner = self.inner.clone();
|
||||
@@ -70,16 +68,8 @@ impl hyper_dns::Resolve for TrustDnsResolver {
|
||||
});
|
||||
|
||||
(inner.resolver)(name)
|
||||
.map(|lookup| {
|
||||
lookup
|
||||
.iter()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
})
|
||||
.map_err(|err| {
|
||||
io::Error::new(io::ErrorKind::Other, err.to_string())
|
||||
})
|
||||
.map(|lookup| lookup.iter().collect::<Vec<_>>().into_iter())
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
107
src/error.rs
107
src/error.rs
@@ -62,17 +62,13 @@ struct Inner {
|
||||
url: Option<Url>,
|
||||
}
|
||||
|
||||
|
||||
/// A `Result` alias where the `Err` case is `reqwest::Error`.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl Error {
|
||||
fn new(kind: Kind, url: Option<Url>) -> Error {
|
||||
Error {
|
||||
inner: Box::new(Inner {
|
||||
kind,
|
||||
url,
|
||||
}),
|
||||
inner: Box::new(Inner { kind, url }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,13 +144,13 @@ impl Error {
|
||||
Kind::Io(ref e) => Some(e),
|
||||
Kind::UrlEncoded(ref e) => Some(e),
|
||||
Kind::Json(ref e) => Some(e),
|
||||
Kind::UrlBadScheme |
|
||||
Kind::TooManyRedirects |
|
||||
Kind::RedirectLoop |
|
||||
Kind::Status(_) |
|
||||
Kind::UnknownProxyScheme |
|
||||
Kind::Timer |
|
||||
Kind::BlockingClientInFutureContext => None,
|
||||
Kind::UrlBadScheme
|
||||
| Kind::TooManyRedirects
|
||||
| Kind::RedirectLoop
|
||||
| Kind::Status(_)
|
||||
| Kind::UnknownProxyScheme
|
||||
| Kind::Timer
|
||||
| Kind::BlockingClientInFutureContext => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,15 +168,11 @@ impl Error {
|
||||
pub fn is_timeout(&self) -> bool {
|
||||
match self.inner.kind {
|
||||
Kind::Io(ref io) => io.kind() == io::ErrorKind::TimedOut,
|
||||
Kind::Hyper(ref error) => {
|
||||
error
|
||||
.source()
|
||||
.and_then(|cause| {
|
||||
cause.downcast_ref::<io::Error>()
|
||||
})
|
||||
.map(|io| io.kind() == io::ErrorKind::TimedOut)
|
||||
.unwrap_or(false)
|
||||
},
|
||||
Kind::Hyper(ref error) => error
|
||||
.source()
|
||||
.and_then(|cause| cause.downcast_ref::<io::Error>())
|
||||
.map(|io| io.kind() == io::ErrorKind::TimedOut)
|
||||
.unwrap_or(false),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -189,8 +181,7 @@ impl Error {
|
||||
#[inline]
|
||||
pub fn is_serialization(&self) -> bool {
|
||||
match self.inner.kind {
|
||||
Kind::Json(_) |
|
||||
Kind::UrlEncoded(_) => true,
|
||||
Kind::Json(_) | Kind::UrlEncoded(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -199,8 +190,7 @@ impl Error {
|
||||
#[inline]
|
||||
pub fn is_redirect(&self) -> bool {
|
||||
match self.inner.kind {
|
||||
Kind::TooManyRedirects |
|
||||
Kind::RedirectLoop => true,
|
||||
Kind::TooManyRedirects | Kind::RedirectLoop => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -241,9 +231,7 @@ impl fmt::Debug for Error {
|
||||
.field(url)
|
||||
.finish()
|
||||
} else {
|
||||
f.debug_tuple("Error")
|
||||
.field(&self.inner.kind)
|
||||
.finish()
|
||||
f.debug_tuple("Error").field(&self.inner.kind).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,9 +257,7 @@ impl fmt::Display for Error {
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
Kind::Rustls(ref e) => fmt::Display::fmt(e, f),
|
||||
#[cfg(feature = "trust-dns")]
|
||||
Kind::DnsSystemConf(ref e) => {
|
||||
write!(f, "failed to load DNS system conf: {}", e)
|
||||
},
|
||||
Kind::DnsSystemConf(ref e) => write!(f, "failed to load DNS system conf: {}", e),
|
||||
Kind::Io(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),
|
||||
@@ -349,13 +335,13 @@ impl StdError for Error {
|
||||
Kind::Io(ref e) => e.cause(),
|
||||
Kind::UrlEncoded(ref e) => e.cause(),
|
||||
Kind::Json(ref e) => e.cause(),
|
||||
Kind::UrlBadScheme |
|
||||
Kind::TooManyRedirects |
|
||||
Kind::RedirectLoop |
|
||||
Kind::Status(_) |
|
||||
Kind::UnknownProxyScheme |
|
||||
Kind::Timer |
|
||||
Kind::BlockingClientInFutureContext => None,
|
||||
Kind::UrlBadScheme
|
||||
| Kind::TooManyRedirects
|
||||
| Kind::RedirectLoop
|
||||
| Kind::Status(_)
|
||||
| Kind::UnknownProxyScheme
|
||||
| Kind::Timer
|
||||
| Kind::BlockingClientInFutureContext => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,13 +362,13 @@ impl StdError for Error {
|
||||
Kind::Io(ref e) => e.source(),
|
||||
Kind::UrlEncoded(ref e) => e.source(),
|
||||
Kind::Json(ref e) => e.source(),
|
||||
Kind::UrlBadScheme |
|
||||
Kind::TooManyRedirects |
|
||||
Kind::RedirectLoop |
|
||||
Kind::Status(_) |
|
||||
Kind::UnknownProxyScheme |
|
||||
Kind::Timer |
|
||||
Kind::BlockingClientInFutureContext => None,
|
||||
Kind::UrlBadScheme
|
||||
| Kind::TooManyRedirects
|
||||
| Kind::RedirectLoop
|
||||
| Kind::Status(_)
|
||||
| Kind::UnknownProxyScheme
|
||||
| Kind::Timer
|
||||
| Kind::BlockingClientInFutureContext => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,7 +399,6 @@ pub(crate) enum Kind {
|
||||
BlockingClientInFutureContext,
|
||||
}
|
||||
|
||||
|
||||
impl From<http::Error> for Kind {
|
||||
#[inline]
|
||||
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
|
||||
where T: Into<Kind> {
|
||||
where
|
||||
T: Into<Kind>,
|
||||
{
|
||||
fn from(err: crate::wait::Waited<T>) -> Kind {
|
||||
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::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 {
|
||||
if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
|
||||
*e
|
||||
.into_inner()
|
||||
*e.into_inner()
|
||||
.expect("io::Error::get_ref was Some(_)")
|
||||
.downcast::<Error>()
|
||||
.expect("StdError::is() was true")
|
||||
@@ -552,28 +538,30 @@ pub(crate) fn from_io(e: io::Error) -> Error {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
macro_rules! try_ {
|
||||
($e:expr) => (
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return Err(crate::error::from(err));
|
||||
}
|
||||
}
|
||||
);
|
||||
($e:expr, $url:expr) => (
|
||||
};
|
||||
($e:expr, $url:expr) => {
|
||||
match $e {
|
||||
Ok(v) => v,
|
||||
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 {
|
||||
($e:expr) => (
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Ok(v) => v,
|
||||
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));
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn loop_detected(url: Url) -> Error {
|
||||
@@ -625,7 +613,7 @@ mod tests {
|
||||
#[derive(Debug)]
|
||||
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 {
|
||||
if let Some(ref link) = self.0 {
|
||||
write!(f, "chain: {}", link)
|
||||
@@ -658,7 +646,6 @@ mod tests {
|
||||
assert!(err.cause().is_none());
|
||||
assert_eq!(err.to_string(), "root");
|
||||
|
||||
|
||||
let root = std::io::Error::new(std::io::ErrorKind::Other, Chain(None::<Error>));
|
||||
let link = Chain(Some(root));
|
||||
let io = std::io::Error::new(std::io::ErrorKind::Other, link);
|
||||
|
||||
@@ -27,8 +27,7 @@ impl PolyfillTryInto for Url {
|
||||
|
||||
impl<'a> PolyfillTryInto for &'a str {
|
||||
fn into_url(self) -> crate::Result<Url> {
|
||||
try_!(Url::parse(self))
|
||||
.into_url()
|
||||
try_!(Url::parse(self)).into_url()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +38,9 @@ impl<'a> PolyfillTryInto for &'a String {
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -52,9 +53,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn into_url_file_scheme() {
|
||||
let err = "file:///etc/hosts"
|
||||
.into_url()
|
||||
.unwrap_err();
|
||||
assert_eq!(err.to_string(), "file:///etc/hosts: URL scheme is not allowed");
|
||||
let err = "file:///etc/hosts".into_url().unwrap_err();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"file:///etc/hosts: URL scheme is not allowed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
21
src/lib.rs
21
src/lib.rs
@@ -187,12 +187,12 @@ doctest!("../README.md");
|
||||
pub use hyper::header;
|
||||
pub use hyper::Method;
|
||||
pub use hyper::{StatusCode, Version};
|
||||
pub use url::Url;
|
||||
pub use url::ParseError as UrlError;
|
||||
pub use url::Url;
|
||||
|
||||
pub use self::body::Body;
|
||||
pub use self::client::{Client, ClientBuilder};
|
||||
pub use self::error::{Error, Result};
|
||||
pub use self::body::Body;
|
||||
pub use self::into_url::IntoUrl;
|
||||
pub use self::proxy::Proxy;
|
||||
pub use self::redirect::{RedirectAction, RedirectAttempt, RedirectPolicy};
|
||||
@@ -206,9 +206,9 @@ pub use self::tls::{Certificate, Identity};
|
||||
mod error;
|
||||
|
||||
mod async_impl;
|
||||
mod connect;
|
||||
mod body;
|
||||
mod client;
|
||||
mod connect;
|
||||
pub mod cookie;
|
||||
#[cfg(feature = "trust-dns")]
|
||||
mod dns;
|
||||
@@ -226,16 +226,8 @@ pub mod multipart;
|
||||
/// An 'async' implementation of the reqwest `Client`.
|
||||
pub mod r#async {
|
||||
pub use crate::async_impl::{
|
||||
Body,
|
||||
Chunk,
|
||||
Decoder,
|
||||
Client,
|
||||
ClientBuilder,
|
||||
Request,
|
||||
RequestBuilder,
|
||||
Response,
|
||||
multipart, Body, Chunk, Client, ClientBuilder, Decoder, Request, RequestBuilder, Response,
|
||||
ResponseBuilderExt,
|
||||
multipart
|
||||
};
|
||||
}
|
||||
|
||||
@@ -269,10 +261,7 @@ pub mod r#async {
|
||||
/// - redirect loop was detected
|
||||
/// - redirect limit was exhausted
|
||||
pub fn get<T: IntoUrl>(url: T) -> crate::Result<Response> {
|
||||
Client::builder()
|
||||
.build()?
|
||||
.get(url)
|
||||
.send()
|
||||
Client::builder().build()?.get(url).send()
|
||||
}
|
||||
|
||||
fn _assert_impls() {
|
||||
|
||||
123
src/multipart.rs
123
src/multipart.rs
@@ -45,7 +45,7 @@ use std::path::Path;
|
||||
use mime_guess::{self, Mime};
|
||||
|
||||
use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps};
|
||||
use crate::{Body};
|
||||
use crate::Body;
|
||||
|
||||
/// A multipart/form-data request.
|
||||
pub struct Form {
|
||||
@@ -82,8 +82,9 @@ impl Form {
|
||||
/// .text("password", "secret");
|
||||
/// ```
|
||||
pub fn text<T, U>(self, name: T, value: U) -> Form
|
||||
where T: Into<Cow<'static, str>>,
|
||||
U: Into<Cow<'static, str>>,
|
||||
where
|
||||
T: Into<Cow<'static, str>>,
|
||||
U: Into<Cow<'static, str>>,
|
||||
{
|
||||
self.part(name, Part::text(value))
|
||||
}
|
||||
@@ -106,8 +107,9 @@ impl Form {
|
||||
///
|
||||
/// Errors when the file cannot be opened.
|
||||
pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
|
||||
where T: Into<Cow<'static, str>>,
|
||||
U: AsRef<Path>
|
||||
where
|
||||
T: Into<Cow<'static, str>>,
|
||||
U: AsRef<Path>,
|
||||
{
|
||||
Ok(self.part(name, Part::file(path)?))
|
||||
}
|
||||
@@ -162,11 +164,11 @@ impl fmt::Debug for Form {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Part {
|
||||
/// Makes a text parameter.
|
||||
pub fn text<T>(value: T) -> Part
|
||||
where T: Into<Cow<'static, str>>,
|
||||
where
|
||||
T: Into<Cow<'static, str>>,
|
||||
{
|
||||
let body = match value.into() {
|
||||
Cow::Borrowed(slice) => Body::from(slice),
|
||||
@@ -177,7 +179,8 @@ impl Part {
|
||||
|
||||
/// Makes a new parameter from arbitrary bytes.
|
||||
pub fn bytes<T>(value: T) -> Part
|
||||
where T: Into<Cow<'static, [u8]>>
|
||||
where
|
||||
T: Into<Cow<'static, [u8]>>,
|
||||
{
|
||||
let body = match value.into() {
|
||||
Cow::Borrowed(slice) => Body::from(slice),
|
||||
@@ -207,16 +210,14 @@ impl Part {
|
||||
/// Errors when the file cannot be opened.
|
||||
pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
|
||||
let path = path.as_ref();
|
||||
let file_name = path.file_name().and_then(|filename| {
|
||||
Some(filename.to_string_lossy().into_owned())
|
||||
});
|
||||
let ext = path.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.unwrap_or("");
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.map(|filename| filename.to_string_lossy().into_owned());
|
||||
|
||||
let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
|
||||
let mime = mime_guess::from_ext(ext).first_or_octet_stream();
|
||||
let file = File::open(path)?;
|
||||
let field = Part::new(Body::from(file))
|
||||
.mime(mime);
|
||||
let field = Part::new(Body::from(file)).mime(mime);
|
||||
|
||||
Ok(if let Some(file_name) = file_name {
|
||||
field.file_name(file_name)
|
||||
@@ -287,9 +288,7 @@ pub(crate) struct Reader {
|
||||
|
||||
impl fmt::Debug for Reader {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Reader")
|
||||
.field("form", &self.form)
|
||||
.finish()
|
||||
f.debug_struct("Reader").field("form", &self.form).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +312,10 @@ impl Reader {
|
||||
let mut h = if !self.form.inner.computed_headers.is_empty() {
|
||||
self.form.inner.computed_headers.remove(0)
|
||||
} 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
|
||||
@@ -327,9 +329,10 @@ impl Reader {
|
||||
if !self.form.inner.fields.is_empty() {
|
||||
Some(Box::new(reader))
|
||||
} else {
|
||||
Some(Box::new(reader.chain(Cursor::new(
|
||||
format!("--{}--\r\n", self.form.boundary()),
|
||||
))))
|
||||
Some(Box::new(reader.chain(Cursor::new(format!(
|
||||
"--{}--\r\n",
|
||||
self.form.boundary()
|
||||
)))))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -379,33 +382,28 @@ mod tests {
|
||||
let mut form = Form::new()
|
||||
.part("reader1", Part::reader(std::io::empty()))
|
||||
.part("key1", Part::text("value1"))
|
||||
.part(
|
||||
"key2",
|
||||
Part::text("value2").mime(mime::IMAGE_BMP),
|
||||
)
|
||||
.part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
|
||||
.part("reader2", Part::reader(std::io::empty()))
|
||||
.part(
|
||||
"key3",
|
||||
Part::text("value3").file_name("filename"),
|
||||
);
|
||||
.part("key3", Part::text("value3").file_name("filename"));
|
||||
form.inner.boundary = "boundary".to_string();
|
||||
let length = form.compute_length();
|
||||
let expected = "--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
|
||||
\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
|
||||
value1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key2\"\r\n\
|
||||
Content-Type: image/bmp\r\n\r\n\
|
||||
value2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
|
||||
\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
|
||||
value3\r\n--boundary--\r\n";
|
||||
let expected =
|
||||
"--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
|
||||
\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
|
||||
value1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key2\"\r\n\
|
||||
Content-Type: image/bmp\r\n\r\n\
|
||||
value2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
|
||||
\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();
|
||||
// These prints are for debug purposes in case the test fails
|
||||
println!(
|
||||
@@ -422,26 +420,21 @@ mod tests {
|
||||
let mut output = Vec::new();
|
||||
let mut form = Form::new()
|
||||
.text("key1", "value1")
|
||||
.part(
|
||||
"key2",
|
||||
Part::text("value2").mime(mime::IMAGE_BMP),
|
||||
)
|
||||
.part(
|
||||
"key3",
|
||||
Part::text("value3").file_name("filename"),
|
||||
);
|
||||
.part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
|
||||
.part("key3", Part::text("value3").file_name("filename"));
|
||||
form.inner.boundary = "boundary".to_string();
|
||||
let length = form.compute_length();
|
||||
let expected = "--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
|
||||
value1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key2\"\r\n\
|
||||
Content-Type: image/bmp\r\n\r\n\
|
||||
value2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
|
||||
value3\r\n--boundary--\r\n";
|
||||
let expected =
|
||||
"--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
|
||||
value1\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key2\"\r\n\
|
||||
Content-Type: image/bmp\r\n\r\n\
|
||||
value2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
|
||||
value3\r\n--boundary--\r\n";
|
||||
form.reader().read_to_end(&mut output).unwrap();
|
||||
// These prints are for debug purposes in case the test fails
|
||||
println!(
|
||||
|
||||
123
src/proxy.rs
123
src/proxy.rs
@@ -1,16 +1,16 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "socks")]
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{IntoUrl, Url};
|
||||
use http::{header::HeaderValue, Uri};
|
||||
use hyper::client::connect::Destination;
|
||||
use url::percent_encoding::percent_decode;
|
||||
use crate::{IntoUrl, Url};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::error::Error;
|
||||
use url::percent_encoding::percent_decode;
|
||||
#[cfg(target_os = "windows")]
|
||||
use winreg::enums::HKEY_CURRENT_USER;
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -98,7 +98,7 @@ impl Proxy {
|
||||
/// ```
|
||||
pub fn http<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
|
||||
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> {
|
||||
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> {
|
||||
Ok(Proxy::new(Intercept::All(
|
||||
proxy_scheme.into_proxy_scheme()?
|
||||
proxy_scheme.into_proxy_scheme()?,
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -163,12 +163,12 @@ impl Proxy {
|
||||
/// # }
|
||||
/// # fn main() {}
|
||||
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 {
|
||||
auth: None,
|
||||
func: Arc::new(move |url| {
|
||||
fun(url).map(IntoProxyScheme::into_proxy_scheme)
|
||||
}),
|
||||
func: Arc::new(move |url| fun(url).map(IntoProxyScheme::into_proxy_scheme)),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -179,9 +179,7 @@ impl Proxy {
|
||||
*/
|
||||
|
||||
fn new(intercept: Intercept) -> Proxy {
|
||||
Proxy {
|
||||
intercept,
|
||||
}
|
||||
Proxy { intercept }
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
match self.intercept {
|
||||
Intercept::All(ProxyScheme::Http { ref auth, .. }) |
|
||||
Intercept::Http(ProxyScheme::Http { ref auth, .. }) => auth.clone(),
|
||||
Intercept::Custom(ref custom) => {
|
||||
custom.call(uri).and_then(|scheme| match scheme {
|
||||
ProxyScheme::Http { auth, .. } => auth,
|
||||
#[cfg(feature = "socks")]
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
Intercept::All(ProxyScheme::Http { ref auth, .. })
|
||||
| Intercept::Http(ProxyScheme::Http { ref auth, .. }) => auth.clone(),
|
||||
Intercept::Custom(ref custom) => custom.call(uri).and_then(|scheme| match scheme {
|
||||
ProxyScheme::Http { auth, .. } => auth,
|
||||
#[cfg(feature = "socks")]
|
||||
_ => None,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -236,14 +232,14 @@ impl Proxy {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
Intercept::Https(ref u) => {
|
||||
if uri.scheme() == "https" {
|
||||
Some(u.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
Intercept::Custom(ref custom) => custom.call(uri),
|
||||
}
|
||||
}
|
||||
@@ -251,12 +247,8 @@ impl Proxy {
|
||||
pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool {
|
||||
match self.intercept {
|
||||
Intercept::All(_) => true,
|
||||
Intercept::Http(_) => {
|
||||
uri.scheme() == "http"
|
||||
},
|
||||
Intercept::Https(_) => {
|
||||
uri.scheme() == "https"
|
||||
},
|
||||
Intercept::Http(_) => uri.scheme() == "http",
|
||||
Intercept::Https(_) => uri.scheme() == "https",
|
||||
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
|
||||
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
|
||||
}
|
||||
@@ -314,7 +310,7 @@ impl ProxyScheme {
|
||||
ProxyScheme::Http { ref mut auth, .. } => {
|
||||
let header = encode_basic_auth(&username.into(), &password.into());
|
||||
*auth = Some(header);
|
||||
},
|
||||
}
|
||||
#[cfg(feature = "socks")]
|
||||
ProxyScheme::Socks5 { ref mut auth, .. } => {
|
||||
*auth = Some((username.into(), password.into()));
|
||||
@@ -332,12 +328,10 @@ impl ProxyScheme {
|
||||
let to_addr = || {
|
||||
let host_and_port = try_!(url.with_default_port(|url| match url.scheme() {
|
||||
"socks5" | "socks5h" => Ok(1080),
|
||||
_ => Err(())
|
||||
_ => Err(()),
|
||||
}));
|
||||
let mut addr = try_!(host_and_port.to_socket_addrs());
|
||||
addr
|
||||
.next()
|
||||
.ok_or_else(crate::error::unknown_proxy_scheme)
|
||||
addr.next().ok_or_else(crate::error::unknown_proxy_scheme)
|
||||
};
|
||||
|
||||
let mut scheme = match url.scheme() {
|
||||
@@ -346,7 +340,7 @@ impl ProxyScheme {
|
||||
"socks5" => Self::socks5(to_addr()?)?,
|
||||
#[cfg(feature = "socks")]
|
||||
"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() {
|
||||
@@ -359,8 +353,6 @@ impl ProxyScheme {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Intercept {
|
||||
All(ProxyScheme),
|
||||
@@ -372,9 +364,9 @@ enum Intercept {
|
||||
impl Intercept {
|
||||
fn set_basic_auth(&mut self, username: &str, password: &str) {
|
||||
match self {
|
||||
Intercept::All(ref mut s) |
|
||||
Intercept::Http(ref mut s) |
|
||||
Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
|
||||
Intercept::All(ref mut s)
|
||||
| Intercept::Http(ref mut s)
|
||||
| Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
|
||||
Intercept::Custom(ref mut custom) => {
|
||||
let header = encode_basic_auth(username, password);
|
||||
custom.auth = Some(header);
|
||||
@@ -397,9 +389,10 @@ impl Custom {
|
||||
uri.scheme(),
|
||||
uri.host(),
|
||||
uri.port().map(|_| ":").unwrap_or(""),
|
||||
uri.port().map(|p| p.to_string()).unwrap_or_default())
|
||||
.parse()
|
||||
.expect("should be valid Url");
|
||||
uri.port().map(|p| p.to_string()).unwrap_or_default()
|
||||
)
|
||||
.parse()
|
||||
.expect("should be valid Url");
|
||||
|
||||
(self.func)(&url)
|
||||
.and_then(|result| result.ok())
|
||||
@@ -413,7 +406,7 @@ impl Custom {
|
||||
uri,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
#[cfg(feature = "socks")]
|
||||
socks => socks,
|
||||
})
|
||||
@@ -467,8 +460,7 @@ impl Dst for Uri {
|
||||
}
|
||||
|
||||
fn host(&self) -> &str {
|
||||
Uri::host(self)
|
||||
.expect("<Uri as Dst>::host should have a str")
|
||||
Uri::host(self).expect("<Uri as Dst>::host should have a str")
|
||||
}
|
||||
|
||||
fn port(&self) -> Option<u16> {
|
||||
@@ -499,8 +491,7 @@ pub fn get_proxies() -> HashMap<String, Url> {
|
||||
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) {
|
||||
proxies.insert(schema, valid_addr);
|
||||
}
|
||||
@@ -522,7 +513,6 @@ fn get_from_environment() -> HashMap<String, Url> {
|
||||
proxies
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn get_from_registry_impl() -> Result<HashMap<String, Url>, Box<dyn Error>> {
|
||||
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();
|
||||
match protocol_parts.as_slice() {
|
||||
[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
|
||||
@@ -558,9 +552,21 @@ fn get_from_registry_impl() -> Result<HashMap<String, Url>, Box<dyn Error>> {
|
||||
if proxy_server.starts_with("http:") {
|
||||
insert_proxy(&mut proxies, String::from("http"), proxy_server);
|
||||
} else {
|
||||
insert_proxy(&mut proxies, 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));
|
||||
insert_proxy(
|
||||
&mut proxies,
|
||||
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)
|
||||
@@ -581,8 +587,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn host(&self) -> &str {
|
||||
Url::host_str(self)
|
||||
.expect("<Url as Dst>::host should have a str")
|
||||
Url::host_str(self).expect("<Url as Dst>::host should have a str")
|
||||
}
|
||||
|
||||
fn port(&self) -> Option<u16> {
|
||||
@@ -594,7 +599,6 @@ mod tests {
|
||||
s.parse().unwrap()
|
||||
}
|
||||
|
||||
|
||||
fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
|
||||
match p.intercept(&url(s)).unwrap() {
|
||||
ProxyScheme::Http { uri, .. } => uri,
|
||||
@@ -641,7 +645,6 @@ mod tests {
|
||||
assert_eq!(intercepted_uri(&p, other), target);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_custom() {
|
||||
let target1 = "http://example.domain/";
|
||||
@@ -687,7 +690,7 @@ mod tests {
|
||||
// reset user setting.
|
||||
match system_proxy {
|
||||
Err(_) => env::remove_var("http_proxy"),
|
||||
Ok(proxy) => env::set_var("http_proxy", proxy)
|
||||
Ok(proxy) => env::set_var("http_proxy", proxy),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::header::{
|
||||
HeaderMap,
|
||||
AUTHORIZATION,
|
||||
COOKIE,
|
||||
PROXY_AUTHORIZATION,
|
||||
WWW_AUTHENTICATE,
|
||||
|
||||
};
|
||||
use crate::header::{HeaderMap, AUTHORIZATION, COOKIE, PROXY_AUTHORIZATION, WWW_AUTHENTICATE};
|
||||
use hyper::StatusCode;
|
||||
|
||||
use crate::Url;
|
||||
@@ -141,19 +134,13 @@ impl RedirectPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check(
|
||||
&self,
|
||||
status: StatusCode,
|
||||
next: &Url,
|
||||
previous: &[Url],
|
||||
) -> Action {
|
||||
self
|
||||
.redirect(RedirectAttempt {
|
||||
status,
|
||||
next,
|
||||
previous,
|
||||
})
|
||||
.inner
|
||||
pub(crate) fn check(&self, status: StatusCode, next: &Url, previous: &[Url]) -> Action {
|
||||
self.redirect(RedirectAttempt {
|
||||
status,
|
||||
next,
|
||||
previous,
|
||||
})
|
||||
.inner
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,11 +226,10 @@ pub(crate) enum Action {
|
||||
TooManyRedirects,
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, previous: &[Url]) {
|
||||
if let Some(previous) = previous.last() {
|
||||
let cross_host = next.host_str() != previous.host_str() ||
|
||||
next.port_or_known_default() != previous.port_or_known_default();
|
||||
let cross_host = next.host_str() != previous.host_str()
|
||||
|| next.port_or_known_default() != previous.port_or_known_default();
|
||||
if cross_host {
|
||||
headers.remove(AUTHORIZATION);
|
||||
headers.remove(COOKIE);
|
||||
@@ -304,21 +290,15 @@ fn test_redirect_policy_custom() {
|
||||
});
|
||||
|
||||
let next = Url::parse("http://bar/baz").unwrap();
|
||||
assert_eq!(
|
||||
policy.check(StatusCode::FOUND, &next, &[]),
|
||||
Action::Follow
|
||||
);
|
||||
assert_eq!(policy.check(StatusCode::FOUND, &next, &[]), Action::Follow);
|
||||
|
||||
let next = Url::parse("http://foo/baz").unwrap();
|
||||
assert_eq!(
|
||||
policy.check(StatusCode::FOUND, &next, &[]),
|
||||
Action::Stop
|
||||
);
|
||||
assert_eq!(policy.check(StatusCode::FOUND, &next, &[]), Action::Stop);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_sensitive_headers() {
|
||||
use hyper::header::{ACCEPT, AUTHORIZATION, COOKIE, HeaderValue};
|
||||
use hyper::header::{HeaderValue, ACCEPT, AUTHORIZATION, COOKIE};
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
|
||||
|
||||
@@ -7,8 +7,8 @@ use serde_urlencoded;
|
||||
|
||||
use crate::body::{self, Body};
|
||||
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
|
||||
use http::HttpTryFrom;
|
||||
use crate::{async_impl, Client, Method, Url};
|
||||
use http::HttpTryFrom;
|
||||
|
||||
/// A request which can be executed with `Client::execute()`.
|
||||
pub struct Request {
|
||||
@@ -119,10 +119,7 @@ impl Request {
|
||||
|
||||
impl RequestBuilder {
|
||||
pub(crate) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
|
||||
RequestBuilder {
|
||||
client,
|
||||
request,
|
||||
}
|
||||
RequestBuilder { client, request }
|
||||
}
|
||||
|
||||
/// Add a `Header` to this Request.
|
||||
@@ -146,11 +143,11 @@ impl RequestBuilder {
|
||||
let mut error = None;
|
||||
if let Ok(ref mut req) = self.request {
|
||||
match <HeaderName as HttpTryFrom<K>>::try_from(key) {
|
||||
Ok(key) => {
|
||||
match <HeaderValue as HttpTryFrom<V>>::try_from(value) {
|
||||
Ok(value) => { req.headers_mut().append(key, value); }
|
||||
Err(e) => error = Some(crate::error::from(e.into())),
|
||||
Ok(key) => match <HeaderValue as HttpTryFrom<V>>::try_from(value) {
|
||||
Ok(value) => {
|
||||
req.headers_mut().append(key, value);
|
||||
}
|
||||
Err(e) => error = Some(crate::error::from(e.into())),
|
||||
},
|
||||
Err(e) => error = Some(crate::error::from(e.into())),
|
||||
};
|
||||
@@ -236,7 +233,7 @@ impl RequestBuilder {
|
||||
{
|
||||
let auth = match password {
|
||||
Some(password) => format!("{}:{}", username, password),
|
||||
None => format!("{}:", username)
|
||||
None => format!("{}:", username),
|
||||
};
|
||||
let header_value = format!("Basic {}", encode(&auth));
|
||||
self.header(crate::header::AUTHORIZATION, &*header_value)
|
||||
@@ -397,10 +394,10 @@ impl RequestBuilder {
|
||||
Ok(body) => {
|
||||
req.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/x-www-form-urlencoded")
|
||||
HeaderValue::from_static("application/x-www-form-urlencoded"),
|
||||
);
|
||||
*req.body_mut() = Some(body.into());
|
||||
},
|
||||
}
|
||||
Err(err) => error = Some(crate::error::from(err)),
|
||||
}
|
||||
}
|
||||
@@ -440,12 +437,10 @@ impl RequestBuilder {
|
||||
if let Ok(ref mut req) = self.request {
|
||||
match serde_json::to_vec(json) {
|
||||
Ok(body) => {
|
||||
req.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/json")
|
||||
);
|
||||
req.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||
*req.body_mut() = Some(body.into());
|
||||
},
|
||||
}
|
||||
Err(err) => error = Some(crate::error::from(err)),
|
||||
}
|
||||
}
|
||||
@@ -477,10 +472,7 @@ impl RequestBuilder {
|
||||
pub fn multipart(self, mut multipart: crate::multipart::Form) -> RequestBuilder {
|
||||
let mut builder = self.header(
|
||||
CONTENT_TYPE,
|
||||
format!(
|
||||
"multipart/form-data; boundary={}",
|
||||
multipart.boundary()
|
||||
).as_str()
|
||||
format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
|
||||
);
|
||||
if let Ok(ref mut req) = builder.request {
|
||||
*req.body_mut() = Some(match multipart.compute_length() {
|
||||
@@ -552,26 +544,27 @@ impl RequestBuilder {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn try_clone(&self) -> Option<RequestBuilder> {
|
||||
self.request.as_ref()
|
||||
self.request
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(|req| req.try_clone())
|
||||
.map(|req| {
|
||||
RequestBuilder{
|
||||
client: self.client.clone(),
|
||||
request: Ok(req),
|
||||
}
|
||||
.map(|req| RequestBuilder {
|
||||
client: self.client.clone(),
|
||||
request: Ok(req),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Request {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt_request_fields(&mut f.debug_struct("Request"), self)
|
||||
.finish()
|
||||
fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_request_fields<'a, 'b>(f: &'a mut fmt::DebugStruct<'a, 'b>, req: &Request) -> &'a mut fmt::DebugStruct<'a, 'b> {
|
||||
fn fmt_request_fields<'a, 'b>(
|
||||
f: &'a mut fmt::DebugStruct<'a, 'b>,
|
||||
req: &Request,
|
||||
) -> &'a mut fmt::DebugStruct<'a, 'b> {
|
||||
f.field("method", req.method())
|
||||
.field("url", req.url())
|
||||
.field("headers", req.headers())
|
||||
@@ -579,12 +572,12 @@ fn fmt_request_fields<'a, 'b>(f: &'a mut fmt::DebugStruct<'a, 'b>, req: &Request
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, HOST};
|
||||
use crate::{body, Client, Method};
|
||||
use crate::header::{ACCEPT, HOST, HeaderMap, HeaderValue, CONTENT_TYPE};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use serde_urlencoded;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
#[test]
|
||||
fn basic_get_request() {
|
||||
@@ -755,7 +748,10 @@ mod tests {
|
||||
let some_url = "https://google.com/";
|
||||
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(¶ms);
|
||||
|
||||
@@ -791,7 +787,10 @@ mod tests {
|
||||
let mut r = r.form(&form_data).build().unwrap();
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -821,15 +820,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn add_json_fail() {
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde::ser::Error;
|
||||
use serde::{Serialize, Serializer};
|
||||
struct MyStruct;
|
||||
impl Serialize for MyStruct {
|
||||
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
Err(S::Error::custom("nope"))
|
||||
}
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
Err(S::Error::custom("nope"))
|
||||
}
|
||||
}
|
||||
|
||||
let client = Client::new();
|
||||
@@ -858,11 +858,7 @@ mod tests {
|
||||
|
||||
assert_eq!(req.headers()["im-a"], "keeper");
|
||||
|
||||
let foo = req
|
||||
.headers()
|
||||
.get_all("foo")
|
||||
.iter()
|
||||
.collect::<Vec<_>>();
|
||||
let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
|
||||
assert_eq!(foo.len(), 2);
|
||||
assert_eq!(foo[0], "bar");
|
||||
assert_eq!(foo[1], "baz");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::mem;
|
||||
use std::fmt;
|
||||
use std::io::{self, Read};
|
||||
use std::mem;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -8,10 +8,10 @@ use futures::{Async, Poll, Stream};
|
||||
use http;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::cookie;
|
||||
use crate::client::KeepCoreThreadAlive;
|
||||
use crate::cookie;
|
||||
use crate::{async_impl, wait, StatusCode, Url, Version};
|
||||
use hyper::header::HeaderMap;
|
||||
use crate::{async_impl, StatusCode, Url, Version, wait};
|
||||
|
||||
/// A Response to a submitted `Request`.
|
||||
pub struct Response {
|
||||
@@ -28,7 +28,11 @@ impl fmt::Debug for 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 {
|
||||
inner: res,
|
||||
body: None,
|
||||
@@ -116,12 +120,10 @@ impl Response {
|
||||
/// Retrieve the cookies contained in the response.
|
||||
///
|
||||
/// Note that invalid 'Set-Cookie' headers will be ignored.
|
||||
pub fn cookies<'a>(&'a self) -> impl Iterator< Item = cookie::Cookie<'a> > + 'a {
|
||||
cookie::extract_response_cookies(self.headers())
|
||||
.filter_map(Result::ok)
|
||||
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
|
||||
cookie::extract_response_cookies(self.headers()).filter_map(Result::ok)
|
||||
}
|
||||
|
||||
|
||||
/// Get the HTTP `Version` of this `Response`.
|
||||
#[inline]
|
||||
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
|
||||
#[inline]
|
||||
pub fn json<T: DeserializeOwned>(&mut self) -> crate::Result<T> {
|
||||
wait::timeout(self.inner.json(), self.timeout).map_err(|e| {
|
||||
match e {
|
||||
wait::Waited::TimedOut => crate::error::timedout(None),
|
||||
wait::Waited::Executor(e) => crate::error::from(e),
|
||||
wait::Waited::Inner(e) => e,
|
||||
}
|
||||
wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e {
|
||||
wait::Waited::TimedOut => crate::error::timedout(None),
|
||||
wait::Waited::Executor(e) => crate::error::from(e),
|
||||
wait::Waited::Inner(e) => e,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -291,7 +291,8 @@ impl Response {
|
||||
/// ```
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
@@ -314,14 +315,17 @@ impl Response {
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn error_for_status(self) -> crate::Result<Self> {
|
||||
let Response { body, inner, timeout, _thread_handle } = self;
|
||||
inner.error_for_status().map(move |inner| {
|
||||
Response {
|
||||
inner,
|
||||
body,
|
||||
timeout,
|
||||
_thread_handle,
|
||||
}
|
||||
let Response {
|
||||
body,
|
||||
inner,
|
||||
timeout,
|
||||
_thread_handle,
|
||||
} = self;
|
||||
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() {
|
||||
let body = mem::replace(self.inner.body_mut(), async_impl::Decoder::empty());
|
||||
let body = async_impl::ReadableChunks::new(WaitBody {
|
||||
inner: wait::stream(body, self.timeout)
|
||||
inner: wait::stream(body, self.timeout),
|
||||
});
|
||||
self.body = Some(body);
|
||||
}
|
||||
@@ -365,7 +369,7 @@ impl Read for Response {
|
||||
}
|
||||
|
||||
struct WaitBody {
|
||||
inner: wait::WaitStream<async_impl::Decoder>
|
||||
inner: wait::WaitStream<async_impl::Decoder>,
|
||||
}
|
||||
|
||||
impl Stream for WaitBody {
|
||||
@@ -383,7 +387,7 @@ impl Stream for WaitBody {
|
||||
};
|
||||
|
||||
Err(req_err)
|
||||
},
|
||||
}
|
||||
None => Ok(Async::Ready(None)),
|
||||
}
|
||||
}
|
||||
|
||||
81
src/tls.rs
81
src/tls.rs
@@ -1,6 +1,6 @@
|
||||
use std::fmt;
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
use rustls::{TLSError, ServerCertVerifier, RootCertStore, ServerCertVerified};
|
||||
use rustls::{RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError};
|
||||
use std::fmt;
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
use tokio_rustls::webpki::DNSNameRef;
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct Certificate {
|
||||
#[derive(Clone)]
|
||||
enum Cert {
|
||||
Der(Vec<u8>),
|
||||
Pem(Vec<u8>)
|
||||
Pem(Vec<u8>),
|
||||
}
|
||||
|
||||
/// Represent a private key and X509 cert as a client certificate.
|
||||
@@ -32,7 +32,7 @@ enum ClientCert {
|
||||
Pem {
|
||||
key: rustls::PrivateKey,
|
||||
certs: Vec<rustls::Certificate>,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
impl Certificate {
|
||||
@@ -61,7 +61,6 @@ impl Certificate {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/// Create a `Certificate` from a PEM encoded certificate
|
||||
///
|
||||
/// # Examples
|
||||
@@ -83,36 +82,32 @@ impl Certificate {
|
||||
#[cfg(feature = "default-tls")]
|
||||
native: try_!(native_tls::Certificate::from_pem(pem)),
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
original: Cert::Pem(pem.to_owned())
|
||||
original: Cert::Pem(pem.to_owned()),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "default-tls")]
|
||||
pub(crate) fn add_to_native_tls(
|
||||
self,
|
||||
tls: &mut native_tls::TlsConnectorBuilder,
|
||||
) {
|
||||
pub(crate) fn add_to_native_tls(self, tls: &mut native_tls::TlsConnectorBuilder) {
|
||||
tls.add_root_certificate(self.native);
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
pub(crate) fn add_to_rustls(
|
||||
self,
|
||||
tls: &mut rustls::ClientConfig,
|
||||
) -> crate::Result<()> {
|
||||
use std::io::Cursor;
|
||||
pub(crate) fn add_to_rustls(self, tls: &mut rustls::ClientConfig) -> crate::Result<()> {
|
||||
use rustls::internal::pemfile;
|
||||
use std::io::Cursor;
|
||||
|
||||
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)),
|
||||
Cert::Pem(buf) => {
|
||||
let mut pem = Cursor::new(buf);
|
||||
let certs = try_!(pemfile::certs(&mut pem)
|
||||
.map_err(|_| TLSError::General(String::from("No valid certificate was found"))));
|
||||
let certs = try_!(pemfile::certs(&mut pem).map_err(|_| TLSError::General(
|
||||
String::from("No valid certificate was found")
|
||||
)));
|
||||
for c in certs {
|
||||
try_!(tls.root_store.add(&c)
|
||||
.map_err(TLSError::WebPKIError));
|
||||
try_!(tls.root_store.add(&c).map_err(TLSError::WebPKIError));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,9 +146,7 @@ impl Identity {
|
||||
#[cfg(feature = "default-tls")]
|
||||
pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> {
|
||||
Ok(Identity {
|
||||
inner: ClientCert::Pkcs12(
|
||||
try_!(native_tls::Identity::from_pkcs12(der, password))
|
||||
),
|
||||
inner: ClientCert::Pkcs12(try_!(native_tls::Identity::from_pkcs12(der, password))),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -178,8 +171,8 @@ impl Identity {
|
||||
/// ```
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> {
|
||||
use std::io::Cursor;
|
||||
use rustls::internal::pemfile;
|
||||
use std::io::Cursor;
|
||||
|
||||
let (key, certs) = {
|
||||
let mut pem = Cursor::new(buf);
|
||||
@@ -202,15 +195,14 @@ impl Identity {
|
||||
if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
|
||||
(sk, certs)
|
||||
} 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 {
|
||||
inner: ClientCert::Pem {
|
||||
key,
|
||||
certs,
|
||||
},
|
||||
inner: ClientCert::Pem { key, certs },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -223,39 +215,34 @@ impl Identity {
|
||||
ClientCert::Pkcs12(id) => {
|
||||
tls.identity(id);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
#[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")]
|
||||
pub(crate) fn add_to_rustls(
|
||||
self,
|
||||
tls: &mut rustls::ClientConfig,
|
||||
) -> crate::Result<()> {
|
||||
pub(crate) fn add_to_rustls(self, tls: &mut rustls::ClientConfig) -> crate::Result<()> {
|
||||
match self.inner {
|
||||
ClientCert::Pem { key, certs } => {
|
||||
tls.set_single_client_cert(certs, key);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
#[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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Certificate")
|
||||
.finish()
|
||||
f.debug_struct("Certificate").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Identity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Identity")
|
||||
.finish()
|
||||
f.debug_struct("Identity").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,16 +250,20 @@ pub(crate) enum TlsBackend {
|
||||
#[cfg(feature = "default-tls")]
|
||||
Default,
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
Rustls
|
||||
Rustls,
|
||||
}
|
||||
|
||||
impl Default for TlsBackend {
|
||||
fn default() -> TlsBackend {
|
||||
#[cfg(feature = "default-tls")]
|
||||
{ TlsBackend::Default }
|
||||
{
|
||||
TlsBackend::Default
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "rustls-tls", not(feature = "default-tls")))]
|
||||
{ TlsBackend::Rustls }
|
||||
{
|
||||
TlsBackend::Rustls
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +277,7 @@ impl ServerCertVerifier for NoVerifier {
|
||||
_roots: &RootCertStore,
|
||||
_presented_certs: &[rustls::Certificate],
|
||||
_dns_name: DNSNameRef,
|
||||
_ocsp_response: &[u8]
|
||||
_ocsp_response: &[u8],
|
||||
) -> Result<ServerCertVerified, TLSError> {
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
20
src/wait.rs
20
src/wait.rs
@@ -2,8 +2,8 @@ use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use futures::executor::{self, Notify};
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use tokio_executor::{enter, EnterError};
|
||||
|
||||
pub(crate) fn timeout<F>(fut: F, timeout: Option<Duration>) -> Result<F::Item, Waited<F::Error>>
|
||||
@@ -11,13 +11,13 @@ where
|
||||
F: Future,
|
||||
{
|
||||
let mut spawn = executor::spawn(fut);
|
||||
block_on(timeout, |notify| {
|
||||
spawn.poll_future_notify(notify, 0)
|
||||
})
|
||||
block_on(timeout, |notify| spawn.poll_future_notify(notify, 0))
|
||||
}
|
||||
|
||||
pub(crate) fn stream<S>(stream: S, timeout: Option<Duration>) -> WaitStream<S>
|
||||
where S: Stream {
|
||||
where
|
||||
S: Stream,
|
||||
{
|
||||
WaitStream {
|
||||
stream: executor::spawn(stream),
|
||||
timeout,
|
||||
@@ -43,7 +43,9 @@ pub(crate) struct WaitStream<S> {
|
||||
}
|
||||
|
||||
impl<S> Iterator for WaitStream<S>
|
||||
where S: Stream {
|
||||
where
|
||||
S: Stream,
|
||||
{
|
||||
type Item = Result<S::Item, Waited<S::Error>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -74,9 +76,7 @@ where
|
||||
F: FnMut(&Arc<ThreadNotify>) -> Poll<U, E>,
|
||||
{
|
||||
let _entered = enter().map_err(Waited::Executor)?;
|
||||
let deadline = timeout.map(|d| {
|
||||
Instant::now() + d
|
||||
});
|
||||
let deadline = timeout.map(|d| Instant::now() + d);
|
||||
let notify = Arc::new(ThreadNotify {
|
||||
thread: thread::current(),
|
||||
});
|
||||
@@ -99,5 +99,3 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
130
tests/async.rs
130
tests/async.rs
@@ -1,10 +1,3 @@
|
||||
extern crate futures;
|
||||
extern crate libflate;
|
||||
extern crate reqwest;
|
||||
extern crate hyper;
|
||||
extern crate tokio;
|
||||
extern crate bytes;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
@@ -14,8 +7,8 @@ use std::time::Duration;
|
||||
use futures::{Future, Stream};
|
||||
use tokio::runtime::current_thread::Runtime;
|
||||
|
||||
use reqwest::r#async::{Chunk, Client};
|
||||
use reqwest::r#async::multipart::{Form, Part};
|
||||
use reqwest::r#async::{Chunk, Client};
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
@@ -54,7 +47,8 @@ fn response_text() {
|
||||
|
||||
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()
|
||||
.and_then(|mut res| res.text())
|
||||
.and_then(|text| {
|
||||
@@ -90,7 +84,8 @@ fn response_json() {
|
||||
|
||||
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()
|
||||
.and_then(|mut res| res.json::<String>())
|
||||
.and_then(|text| {
|
||||
@@ -105,34 +100,36 @@ fn response_json() {
|
||||
fn multipart() {
|
||||
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 form = Form::new()
|
||||
.text("foo", "bar")
|
||||
.part("part_stream", part);
|
||||
let form = Form::new().text("foo", "bar").part("part_stream", part);
|
||||
|
||||
let expected_body = format!("\
|
||||
24\r\n\
|
||||
--{0}\r\n\r\n\
|
||||
2E\r\n\
|
||||
Content-Disposition: form-data; name=\"foo\"\r\n\r\n\r\n\
|
||||
3\r\n\
|
||||
bar\r\n\
|
||||
2\r\n\
|
||||
\r\n\r\n\
|
||||
24\r\n\
|
||||
--{0}\r\n\r\n\
|
||||
36\r\n\
|
||||
Content-Disposition: form-data; name=\"part_stream\"\r\n\r\n\r\n\
|
||||
B\r\n\
|
||||
part1 part2\r\n\
|
||||
2\r\n\
|
||||
\r\n\r\n\
|
||||
26\r\n\
|
||||
--{0}--\r\n\r\n\
|
||||
0\r\n\r\n\
|
||||
", form.boundary());
|
||||
let expected_body = format!(
|
||||
"\
|
||||
24\r\n\
|
||||
--{0}\r\n\r\n\
|
||||
2E\r\n\
|
||||
Content-Disposition: form-data; name=\"foo\"\r\n\r\n\r\n\
|
||||
3\r\n\
|
||||
bar\r\n\
|
||||
2\r\n\
|
||||
\r\n\r\n\
|
||||
24\r\n\
|
||||
--{0}\r\n\r\n\
|
||||
36\r\n\
|
||||
Content-Disposition: form-data; name=\"part_stream\"\r\n\r\n\r\n\
|
||||
B\r\n\
|
||||
part1 part2\r\n\
|
||||
2\r\n\
|
||||
\r\n\r\n\
|
||||
26\r\n\
|
||||
--{0}--\r\n\r\n\
|
||||
0\r\n\r\n\
|
||||
",
|
||||
form.boundary()
|
||||
);
|
||||
|
||||
let server = server! {
|
||||
request: format!("\
|
||||
@@ -160,15 +157,12 @@ fn multipart() {
|
||||
|
||||
let client = Client::new();
|
||||
|
||||
let res_future = client.post(&url)
|
||||
.multipart(form)
|
||||
.send()
|
||||
.and_then(|res| {
|
||||
assert_eq!(res.url().as_str(), &url);
|
||||
assert_eq!(res.status(), reqwest::StatusCode::OK);
|
||||
let res_future = client.post(&url).multipart(form).send().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();
|
||||
}
|
||||
@@ -203,9 +197,7 @@ fn request_timeout() {
|
||||
.unwrap();
|
||||
|
||||
let url = format!("http://{}/slow", server.addr());
|
||||
let fut = client
|
||||
.get(&url)
|
||||
.send();
|
||||
let fut = client.get(&url).send();
|
||||
|
||||
let err = rt.block_on(fut).unwrap_err();
|
||||
|
||||
@@ -254,7 +246,10 @@ fn response_timeout() {
|
||||
}
|
||||
|
||||
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();
|
||||
match encoder.write(content.as_bytes()) {
|
||||
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 mut response = format!("\
|
||||
HTTP/1.1 200 OK\r\n\
|
||||
Server: test-accept\r\n\
|
||||
Content-Encoding: gzip\r\n\
|
||||
Content-Length: {}\r\n\
|
||||
\r\n", &gzipped_content.len())
|
||||
.into_bytes();
|
||||
let mut response = format!(
|
||||
"\
|
||||
HTTP/1.1 200 OK\r\n\
|
||||
Server: test-accept\r\n\
|
||||
Content-Encoding: gzip\r\n\
|
||||
Content-Length: {}\r\n\
|
||||
\r\n",
|
||||
&gzipped_content.len()
|
||||
)
|
||||
.into_bytes();
|
||||
response.extend(&gzipped_content);
|
||||
|
||||
let server = server! {
|
||||
@@ -290,7 +288,8 @@ fn gzip_case(response_size: usize, chunk_size: usize) {
|
||||
|
||||
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()
|
||||
.and_then(|res| {
|
||||
let body = res.into_body();
|
||||
@@ -311,9 +310,11 @@ fn gzip_case(response_size: usize, chunk_size: usize) {
|
||||
fn body_stream() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let source: Box<dyn Stream<Item = Bytes, Error = io::Error> + Send>
|
||||
= Box::new(futures::stream::iter_ok::<_, io::Error>(
|
||||
vec![Bytes::from_static(b"123"), Bytes::from_static(b"4567")]));
|
||||
let source: Box<dyn Stream<Item = Bytes, Error = io::Error> + Send> =
|
||||
Box::new(futures::stream::iter_ok::<_, io::Error>(vec![
|
||||
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";
|
||||
|
||||
@@ -342,15 +343,12 @@ fn body_stream() {
|
||||
|
||||
let client = Client::new();
|
||||
|
||||
let res_future = client.post(&url)
|
||||
.body(source)
|
||||
.send()
|
||||
.and_then(|res| {
|
||||
assert_eq!(res.url().as_str(), &url);
|
||||
assert_eq!(res.status(), reqwest::StatusCode::OK);
|
||||
let res_future = client.post(&url).body(source).send().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();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
extern crate reqwest;
|
||||
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
#[test]
|
||||
fn test_badssl_modern() {
|
||||
let text = reqwest::get("https://mozilla-modern.badssl.com/").unwrap()
|
||||
.text().unwrap();
|
||||
let text = reqwest::get("https://mozilla-modern.badssl.com/")
|
||||
.unwrap()
|
||||
.text()
|
||||
.unwrap();
|
||||
|
||||
assert!(text.contains("<title>mozilla-modern.badssl.com</title>"));
|
||||
}
|
||||
@@ -15,10 +14,13 @@ fn test_badssl_modern() {
|
||||
fn test_rustls_badssl_modern() {
|
||||
let text = reqwest::Client::builder()
|
||||
.use_rustls_tls()
|
||||
.build().unwrap()
|
||||
.build()
|
||||
.unwrap()
|
||||
.get("https://mozilla-modern.badssl.com/")
|
||||
.send().unwrap()
|
||||
.text().unwrap();
|
||||
.send()
|
||||
.unwrap()
|
||||
.text()
|
||||
.unwrap();
|
||||
|
||||
assert!(text.contains("<title>mozilla-modern.badssl.com</title>"));
|
||||
}
|
||||
@@ -28,10 +30,13 @@ fn test_rustls_badssl_modern() {
|
||||
fn test_badssl_self_signed() {
|
||||
let text = reqwest::Client::builder()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.build().unwrap()
|
||||
.build()
|
||||
.unwrap()
|
||||
.get("https://self-signed.badssl.com/")
|
||||
.send().unwrap()
|
||||
.text().unwrap();
|
||||
.send()
|
||||
.unwrap()
|
||||
.text()
|
||||
.unwrap();
|
||||
|
||||
assert!(text.contains("<title>self-signed.badssl.com</title>"));
|
||||
}
|
||||
@@ -41,17 +46,20 @@ fn test_badssl_self_signed() {
|
||||
fn test_badssl_wrong_host() {
|
||||
let text = reqwest::Client::builder()
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.build().unwrap()
|
||||
.build()
|
||||
.unwrap()
|
||||
.get("https://wrong.host.badssl.com/")
|
||||
.send().unwrap()
|
||||
.text().unwrap();
|
||||
.send()
|
||||
.unwrap()
|
||||
.text()
|
||||
.unwrap();
|
||||
|
||||
assert!(text.contains("<title>wrong.host.badssl.com</title>"));
|
||||
|
||||
|
||||
let result = reqwest::Client::builder()
|
||||
.danger_accept_invalid_hostnames(true)
|
||||
.build().unwrap()
|
||||
.build()
|
||||
.unwrap()
|
||||
.get("https://self-signed.badssl.com/")
|
||||
.send();
|
||||
|
||||
|
||||
107
tests/client.rs
107
tests/client.rs
@@ -1,5 +1,3 @@
|
||||
extern crate reqwest;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
@@ -30,7 +28,10 @@ fn test_response_text() {
|
||||
assert_eq!(res.url().as_str(), &url);
|
||||
assert_eq!(res.status(), reqwest::StatusCode::OK);
|
||||
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();
|
||||
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.status(), reqwest::StatusCode::OK);
|
||||
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();
|
||||
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]
|
||||
@@ -94,7 +98,10 @@ fn test_response_json() {
|
||||
assert_eq!(res.url().as_str(), &url);
|
||||
assert_eq!(res.status(), reqwest::StatusCode::OK);
|
||||
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();
|
||||
assert_eq!("Hello", body);
|
||||
@@ -125,7 +132,10 @@ fn test_response_copy_to() {
|
||||
assert_eq!(res.url().as_str(), &url);
|
||||
assert_eq!(res.status(), reqwest::StatusCode::OK);
|
||||
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![];
|
||||
res.copy_to(&mut buf).unwrap();
|
||||
@@ -157,7 +167,10 @@ fn test_get() {
|
||||
assert_eq!(res.url().as_str(), &url);
|
||||
assert_eq!(res.status(), reqwest::StatusCode::OK);
|
||||
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()));
|
||||
|
||||
let mut buf = [0; 1024];
|
||||
@@ -196,7 +209,10 @@ fn test_post() {
|
||||
assert_eq!(res.url().as_str(), &url);
|
||||
assert_eq!(res.status(), reqwest::StatusCode::OK);
|
||||
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 n = res.read(&mut buf).unwrap();
|
||||
@@ -293,7 +309,10 @@ fn test_error_for_status_5xx() {
|
||||
|
||||
let err = res.error_for_status().err().unwrap();
|
||||
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]
|
||||
@@ -303,7 +322,8 @@ fn test_default_headers() {
|
||||
headers.insert(header::COOKIE, header::HeaderValue::from_static("a=b;c=d"));
|
||||
let client = reqwest::Client::builder()
|
||||
.default_headers(headers)
|
||||
.build().unwrap();
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let server = server! {
|
||||
request: b"\
|
||||
@@ -329,7 +349,10 @@ fn test_default_headers() {
|
||||
assert_eq!(res.url().as_str(), &url);
|
||||
assert_eq!(res.status(), reqwest::StatusCode::OK);
|
||||
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! {
|
||||
request: b"\
|
||||
@@ -355,17 +378,24 @@ fn test_default_headers() {
|
||||
assert_eq!(res.url().as_str(), &url);
|
||||
assert_eq!(res.status(), reqwest::StatusCode::OK);
|
||||
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]
|
||||
fn test_override_default_headers() {
|
||||
use reqwest::header;
|
||||
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()
|
||||
.default_headers(headers)
|
||||
.build().unwrap();
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let server = server! {
|
||||
request: b"\
|
||||
@@ -386,13 +416,22 @@ fn test_override_default_headers() {
|
||||
};
|
||||
|
||||
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.status(), reqwest::StatusCode::OK);
|
||||
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]
|
||||
@@ -418,20 +457,32 @@ fn test_appended_headers_not_overwritten() {
|
||||
};
|
||||
|
||||
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.status(), reqwest::StatusCode::OK);
|
||||
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
|
||||
use reqwest::header;
|
||||
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()
|
||||
.default_headers(headers)
|
||||
.build().unwrap();
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let server = server! {
|
||||
request: b"\
|
||||
@@ -452,10 +503,18 @@ fn test_appended_headers_not_overwritten() {
|
||||
};
|
||||
|
||||
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.status(), reqwest::StatusCode::OK);
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extern crate reqwest;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
@@ -45,7 +43,7 @@ fn cookie_response_accessor() {
|
||||
// expires
|
||||
assert_eq!(cookies[1].name(), "expires");
|
||||
assert_eq!(
|
||||
cookies[1].expires().unwrap(),
|
||||
cookies[1].expires().unwrap(),
|
||||
std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1445412480)
|
||||
);
|
||||
|
||||
@@ -55,7 +53,10 @@ fn cookie_response_accessor() {
|
||||
|
||||
// max-age
|
||||
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
|
||||
assert_eq!(cookies[4].name(), "domain");
|
||||
@@ -81,7 +82,10 @@ fn cookie_response_accessor() {
|
||||
#[test]
|
||||
fn cookie_store_simple() {
|
||||
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! {
|
||||
request: b"\
|
||||
@@ -125,7 +129,10 @@ fn cookie_store_simple() {
|
||||
#[test]
|
||||
fn cookie_store_overwrite_existing() {
|
||||
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! {
|
||||
request: b"\
|
||||
@@ -189,7 +196,10 @@ fn cookie_store_overwrite_existing() {
|
||||
#[test]
|
||||
fn cookie_store_max_age() {
|
||||
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! {
|
||||
request: b"\
|
||||
@@ -232,7 +242,10 @@ fn cookie_store_max_age() {
|
||||
#[test]
|
||||
fn cookie_store_expires() {
|
||||
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! {
|
||||
request: b"\
|
||||
@@ -275,7 +288,10 @@ fn cookie_store_expires() {
|
||||
#[test]
|
||||
fn cookie_store_path() {
|
||||
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! {
|
||||
request: b"\
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
extern crate reqwest;
|
||||
extern crate libflate;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
use std::time::Duration;
|
||||
use std::io::{Read, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_gzip_response() {
|
||||
@@ -19,13 +16,16 @@ fn test_gzip_response() {
|
||||
|
||||
let gzipped_content = encoder.finish().into_result().unwrap();
|
||||
|
||||
let mut response = format!("\
|
||||
HTTP/1.1 200 OK\r\n\
|
||||
Server: test-accept\r\n\
|
||||
Content-Encoding: gzip\r\n\
|
||||
Content-Length: {}\r\n\
|
||||
\r\n", &gzipped_content.len())
|
||||
.into_bytes();
|
||||
let mut response = format!(
|
||||
"\
|
||||
HTTP/1.1 200 OK\r\n\
|
||||
Server: test-accept\r\n\
|
||||
Content-Encoding: gzip\r\n\
|
||||
Content-Length: {}\r\n\
|
||||
\r\n",
|
||||
&gzipped_content.len()
|
||||
)
|
||||
.into_bytes();
|
||||
response.extend(&gzipped_content);
|
||||
|
||||
let server = server! {
|
||||
@@ -130,7 +130,10 @@ fn test_accept_header_is_not_changed_if_set() {
|
||||
|
||||
let res = client
|
||||
.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()
|
||||
.unwrap();
|
||||
|
||||
@@ -157,8 +160,12 @@ fn test_accept_encoding_header_is_not_changed_if_set() {
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let res = client.get(&format!("http://{}/accept-encoding", server.addr()))
|
||||
.header(reqwest::header::ACCEPT_ENCODING, reqwest::header::HeaderValue::from_static("identity"))
|
||||
let res = client
|
||||
.get(&format!("http://{}/accept-encoding", server.addr()))
|
||||
.header(
|
||||
reqwest::header::ACCEPT_ENCODING,
|
||||
reqwest::header::HeaderValue::from_static("identity"),
|
||||
)
|
||||
.send()
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
extern crate env_logger;
|
||||
extern crate reqwest;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
@@ -8,15 +5,17 @@ mod support;
|
||||
fn text_part() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let form = reqwest::multipart::Form::new()
|
||||
.text("foo", "bar");
|
||||
let form = reqwest::multipart::Form::new().text("foo", "bar");
|
||||
|
||||
let expected_body = format!("\
|
||||
--{0}\r\n\
|
||||
Content-Disposition: form-data; name=\"foo\"\r\n\r\n\
|
||||
bar\r\n\
|
||||
--{0}--\r\n\
|
||||
", form.boundary());
|
||||
let expected_body = format!(
|
||||
"\
|
||||
--{0}\r\n\
|
||||
Content-Disposition: form-data; name=\"foo\"\r\n\r\n\
|
||||
bar\r\n\
|
||||
--{0}--\r\n\
|
||||
",
|
||||
form.boundary()
|
||||
);
|
||||
|
||||
let server = server! {
|
||||
request: format!("\
|
||||
@@ -55,17 +54,22 @@ fn file() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
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 expected_body = format!("\
|
||||
--{0}\r\n\
|
||||
Content-Disposition: form-data; name=\"foo\"; filename=\"Cargo.lock\"\r\n\
|
||||
Content-Type: application/octet-stream\r\n\r\n\
|
||||
{1}\r\n\
|
||||
--{0}--\r\n\
|
||||
", form.boundary(), fcontents);
|
||||
let expected_body = format!(
|
||||
"\
|
||||
--{0}\r\n\
|
||||
Content-Disposition: form-data; name=\"foo\"; filename=\"Cargo.lock\"\r\n\
|
||||
Content-Type: application/octet-stream\r\n\r\n\
|
||||
{1}\r\n\
|
||||
--{0}--\r\n\
|
||||
",
|
||||
form.boundary(),
|
||||
fcontents
|
||||
);
|
||||
|
||||
let server = server! {
|
||||
request: format!("\
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extern crate reqwest;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
@@ -37,7 +35,10 @@ fn http_proxy() {
|
||||
|
||||
assert_eq!(res.url().as_str(), url);
|
||||
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]
|
||||
@@ -66,8 +67,8 @@ fn http_proxy_basic_auth() {
|
||||
let res = reqwest::Client::builder()
|
||||
.proxy(
|
||||
reqwest::Proxy::http(&proxy)
|
||||
.unwrap()
|
||||
.basic_auth("Aladdin", "open sesame")
|
||||
.unwrap()
|
||||
.basic_auth("Aladdin", "open sesame"),
|
||||
)
|
||||
.build()
|
||||
.unwrap()
|
||||
@@ -77,7 +78,10 @@ fn http_proxy_basic_auth() {
|
||||
|
||||
assert_eq!(res.url().as_str(), url);
|
||||
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]
|
||||
@@ -104,9 +108,7 @@ fn http_proxy_basic_auth_parsed() {
|
||||
|
||||
let url = "http://hyper.rs/prox";
|
||||
let res = reqwest::Client::builder()
|
||||
.proxy(
|
||||
reqwest::Proxy::http(&proxy).unwrap()
|
||||
)
|
||||
.proxy(reqwest::Proxy::http(&proxy).unwrap())
|
||||
.build()
|
||||
.unwrap()
|
||||
.get(url)
|
||||
@@ -115,7 +117,10 @@ fn http_proxy_basic_auth_parsed() {
|
||||
|
||||
assert_eq!(res.url().as_str(), url);
|
||||
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]
|
||||
@@ -141,9 +146,7 @@ fn test_no_proxy() {
|
||||
|
||||
// set up proxy and use no_proxy to clear up client builder proxies.
|
||||
let res = reqwest::Client::builder()
|
||||
.proxy(
|
||||
reqwest::Proxy::http(&proxy).unwrap()
|
||||
)
|
||||
.proxy(reqwest::Proxy::http(&proxy).unwrap())
|
||||
.no_proxy()
|
||||
.build()
|
||||
.unwrap()
|
||||
@@ -189,11 +192,14 @@ fn test_using_system_proxy() {
|
||||
|
||||
assert_eq!(res.url().as_str(), url);
|
||||
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.
|
||||
match system_proxy {
|
||||
Err(_) => env::remove_var("http_proxy"),
|
||||
Ok(proxy) => env::set_var("http_proxy", proxy)
|
||||
Ok(proxy) => env::set_var("http_proxy", proxy),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extern crate reqwest;
|
||||
|
||||
#[macro_use]
|
||||
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 dst = format!("http://{}/{}", redirect.addr(), "dst");
|
||||
let res = client.post(&url)
|
||||
.send()
|
||||
.unwrap();
|
||||
let res = client.post(&url).send().unwrap();
|
||||
assert_eq!(res.url().as_str(), dst);
|
||||
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 dst = format!("http://{}/{}", redirect.addr(), "dst");
|
||||
let res = client.get(&url)
|
||||
.send()
|
||||
.unwrap();
|
||||
let res = client.get(&url).send().unwrap();
|
||||
assert_eq!(res.url().as_str(), dst);
|
||||
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 dst = format!("http://{}/{}", redirect.addr(), "dst");
|
||||
let res = client.post(&url)
|
||||
.body("Hello")
|
||||
.send()
|
||||
.unwrap();
|
||||
let res = client.post(&url).body("Hello").send().unwrap();
|
||||
assert_eq!(res.url().as_str(), dst);
|
||||
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]
|
||||
fn test_redirect_removes_sensitive_headers() {
|
||||
let end_server = server! {
|
||||
@@ -249,7 +247,10 @@ fn test_redirect_removes_sensitive_headers() {
|
||||
.build()
|
||||
.unwrap()
|
||||
.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()
|
||||
.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.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]
|
||||
@@ -385,58 +389,65 @@ fn test_invalid_location_stops_redirect_gh484() {
|
||||
|
||||
assert_eq!(res.url().as_str(), url);
|
||||
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]
|
||||
fn test_redirect_302_with_set_cookies() {
|
||||
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! {
|
||||
request: format!("\
|
||||
GET /{} HTTP/1.1\r\n\
|
||||
user-agent: $USERAGENT\r\n\
|
||||
accept: */*\r\n\
|
||||
accept-encoding: gzip\r\n\
|
||||
host: $HOST\r\n\
|
||||
\r\n\
|
||||
", code),
|
||||
response: format!("\
|
||||
HTTP/1.1 {} reason\r\n\
|
||||
Server: test-redirect\r\n\
|
||||
Content-Length: 0\r\n\
|
||||
Location: /dst\r\n\
|
||||
Connection: close\r\n\
|
||||
Set-Cookie: key=value\r\n\
|
||||
\r\n\
|
||||
", code)
|
||||
;
|
||||
request: format!("\
|
||||
GET /{} HTTP/1.1\r\n\
|
||||
user-agent: $USERAGENT\r\n\
|
||||
accept: */*\r\n\
|
||||
accept-encoding: gzip\r\n\
|
||||
host: $HOST\r\n\
|
||||
\r\n\
|
||||
", code),
|
||||
response: format!("\
|
||||
HTTP/1.1 {} reason\r\n\
|
||||
Server: test-redirect\r\n\
|
||||
Content-Length: 0\r\n\
|
||||
Location: /dst\r\n\
|
||||
Connection: close\r\n\
|
||||
Set-Cookie: key=value\r\n\
|
||||
\r\n\
|
||||
", code)
|
||||
;
|
||||
|
||||
request: format!("\
|
||||
GET /dst HTTP/1.1\r\n\
|
||||
user-agent: $USERAGENT\r\n\
|
||||
accept: */*\r\n\
|
||||
accept-encoding: gzip\r\n\
|
||||
referer: http://$HOST/{}\r\n\
|
||||
cookie: key=value\r\n\
|
||||
host: $HOST\r\n\
|
||||
\r\n\
|
||||
", code),
|
||||
response: b"\
|
||||
HTTP/1.1 200 OK\r\n\
|
||||
Server: test-dst\r\n\
|
||||
Content-Length: 0\r\n\
|
||||
\r\n\
|
||||
"
|
||||
};
|
||||
request: format!("\
|
||||
GET /dst HTTP/1.1\r\n\
|
||||
user-agent: $USERAGENT\r\n\
|
||||
accept: */*\r\n\
|
||||
accept-encoding: gzip\r\n\
|
||||
referer: http://$HOST/{}\r\n\
|
||||
cookie: key=value\r\n\
|
||||
host: $HOST\r\n\
|
||||
\r\n\
|
||||
", code),
|
||||
response: b"\
|
||||
HTTP/1.1 200 OK\r\n\
|
||||
Server: test-dst\r\n\
|
||||
Content-Length: 0\r\n\
|
||||
\r\n\
|
||||
"
|
||||
};
|
||||
|
||||
let url = format!("http://{}/{}", server.addr(), code);
|
||||
let dst = format!("http://{}/{}", server.addr(), "dst");
|
||||
let res = client.get(&url)
|
||||
.send()
|
||||
.unwrap();
|
||||
let res = client.get(&url).send().unwrap();
|
||||
|
||||
assert_eq!(res.url().as_str(), dst);
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::net;
|
||||
use std::time::Duration;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Server {
|
||||
addr: net::SocketAddr,
|
||||
@@ -19,9 +19,8 @@ impl Server {
|
||||
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
if !::std::thread::panicking() {
|
||||
self
|
||||
.panic_rx
|
||||
if !thread::panicking() {
|
||||
self.panic_rx
|
||||
.recv_timeout(Duration::from_secs(3))
|
||||
.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 addr = listener.local_addr().unwrap();
|
||||
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 || {
|
||||
'txns: for txn in txns {
|
||||
let mut expected = txn.request;
|
||||
@@ -149,10 +151,7 @@ pub fn spawn(txns: Vec<Txn>) -> Server {
|
||||
let _ = panic_tx.send(());
|
||||
}).expect("server thread spawn");
|
||||
|
||||
Server {
|
||||
addr,
|
||||
panic_rx,
|
||||
}
|
||||
Server { addr, panic_rx }
|
||||
}
|
||||
|
||||
fn replace_expected_vars(bytes: &mut Vec<u8>, host: &[u8], ua: &[u8]) {
|
||||
@@ -210,16 +209,15 @@ macro_rules! __internal__txn {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! __internal__prop {
|
||||
(request: $val:expr) => (
|
||||
(request: $val:expr) => {
|
||||
From::from(&$val[..])
|
||||
);
|
||||
(response: $val:expr) => (
|
||||
};
|
||||
(response: $val:expr) => {
|
||||
From::from(&$val[..])
|
||||
);
|
||||
($field:ident: $val:expr) => (
|
||||
};
|
||||
($field:ident: $val:expr) => {
|
||||
From::from($val)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
extern crate env_logger;
|
||||
extern crate reqwest;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
@@ -38,10 +35,7 @@ fn timeout_closes_connection() {
|
||||
};
|
||||
|
||||
let url = format!("http://{}/closes", server.addr());
|
||||
let err = client
|
||||
.get(&url)
|
||||
.send()
|
||||
.unwrap_err();
|
||||
let err = client.get(&url).send().unwrap_err();
|
||||
|
||||
assert_eq!(err.get_ref().unwrap().to_string(), "timed out");
|
||||
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()));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_response_timeout() {
|
||||
let _ = env_logger::try_init();
|
||||
@@ -158,7 +151,10 @@ fn test_read_timeout() {
|
||||
|
||||
assert_eq!(res.url().as_str(), &url);
|
||||
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 err = res.read(&mut buf).unwrap_err();
|
||||
|
||||
Reference in New Issue
Block a user