1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-02-11 06:12:51 +01:00
actix-extras/awc/src/request.rs

1113 lines
31 KiB
Rust
Raw Normal View History

2019-03-29 21:13:39 -07:00
use std::fmt::Write as FmtWrite;
2019-03-25 21:58:01 -07:00
use std::io::Write;
use std::rc::Rc;
use std::time::{Duration, Instant};
use std::{fmt, net};
2019-03-25 21:58:01 -07:00
use bytes::{BufMut, Bytes, BytesMut};
use futures::{Async, Future, Poll, Stream, try_ready};
2019-08-13 10:48:11 -07:00
use percent_encoding::percent_encode;
2019-03-25 21:58:01 -07:00
use serde::Serialize;
use serde_json;
use tokio_timer::Delay;
use derive_more::From;
2019-03-25 21:58:01 -07:00
use actix_http::body::{Body, BodyStream};
2019-08-13 10:48:11 -07:00
use actix_http::cookie::{Cookie, CookieJar, USERINFO};
2019-03-26 20:45:00 -07:00
use actix_http::encoding::Decoder;
use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue};
2019-03-25 21:58:01 -07:00
use actix_http::http::{
2019-04-02 12:51:16 -07:00
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue,
HttpTryFrom, Method, Uri, Version,
2019-03-25 21:58:01 -07:00
};
use actix_http::{Error, Payload, PayloadStream, RequestHead};
2019-03-25 21:58:01 -07:00
use crate::error::{InvalidUrl, SendRequestError, FreezeRequestError};
use crate::response::ClientResponse;
2019-03-28 22:33:41 -07:00
use crate::ClientConfig;
2019-03-26 20:45:00 -07:00
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
const HTTPS_ENCODING: &str = "br, gzip, deflate";
#[cfg(all(
any(feature = "flate2-zlib", feature = "flate2-rust"),
not(feature = "brotli")
))]
const HTTPS_ENCODING: &str = "gzip, deflate";
2019-03-25 21:58:01 -07:00
/// An HTTP Client request builder
///
/// This type can be used to construct an instance of `ClientRequest` through a
/// builder-like pattern.
///
/// ```rust
/// use futures::future::{Future, lazy};
/// use actix_rt::System;
///
/// fn main() {
/// System::new("test").block_on(lazy(|| {
/// awc::Client::new()
/// .get("http://www.rust-lang.org") // <- Create request builder
2019-03-25 21:58:01 -07:00
/// .header("User-Agent", "Actix-web")
/// .send() // <- Send http request
2019-03-25 21:58:01 -07:00
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
2019-03-25 21:58:01 -07:00
/// println!("Response: {:?}", response);
/// Ok(())
/// })
/// }));
/// }
/// ```
pub struct ClientRequest {
2019-04-02 12:51:16 -07:00
pub(crate) head: RequestHead,
2019-03-25 21:58:01 -07:00
err: Option<HttpError>,
addr: Option<net::SocketAddr>,
2019-03-25 21:58:01 -07:00
cookies: Option<CookieJar>,
2019-03-26 20:45:00 -07:00
response_decompress: bool,
2019-03-29 14:07:37 -07:00
timeout: Option<Duration>,
2019-03-28 22:33:41 -07:00
config: Rc<ClientConfig>,
2019-03-25 21:58:01 -07:00
}
impl ClientRequest {
/// Create new client request builder.
2019-03-28 22:33:41 -07:00
pub(crate) fn new<U>(method: Method, uri: U, config: Rc<ClientConfig>) -> Self
2019-03-25 21:58:01 -07:00
where
Uri: HttpTryFrom<U>,
{
2019-04-02 12:51:16 -07:00
ClientRequest {
2019-03-28 22:33:41 -07:00
config,
2019-04-02 12:51:16 -07:00
head: RequestHead::default(),
2019-03-29 16:27:18 -07:00
err: None,
addr: None,
2019-03-25 21:58:01 -07:00
cookies: None,
2019-03-29 14:07:37 -07:00
timeout: None,
2019-03-26 20:45:00 -07:00
response_decompress: true,
2019-04-02 12:51:16 -07:00
}
.method(method)
.uri(uri)
2019-03-29 16:27:18 -07:00
}
/// Set HTTP URI of request.
#[inline]
2019-04-02 12:51:16 -07:00
pub fn uri<U>(mut self, uri: U) -> Self
2019-03-29 16:27:18 -07:00
where
Uri: HttpTryFrom<U>,
{
2019-04-02 12:51:16 -07:00
match Uri::try_from(uri) {
Ok(uri) => self.head.uri = uri,
Err(e) => self.err = Some(e.into()),
2019-03-29 16:27:18 -07:00
}
self
2019-03-25 21:58:01 -07:00
}
/// Get HTTP URI of request
pub fn get_uri(&self) -> &Uri {
&self.head.uri
}
/// Set socket address of the server.
///
/// This address is used for connection. If address is not
/// provided url's host name get resolved.
pub fn address(mut self, addr: net::SocketAddr) -> Self {
self.addr = Some(addr);
self
}
2019-03-25 21:58:01 -07:00
/// Set HTTP method of this request.
#[inline]
2019-04-02 12:51:16 -07:00
pub fn method(mut self, method: Method) -> Self {
self.head.method = method;
2019-03-25 21:58:01 -07:00
self
}
/// Get HTTP method of this request
pub fn get_method(&self) -> &Method {
&self.head.method
}
2019-03-25 21:58:01 -07:00
#[doc(hidden)]
/// Set HTTP version of this request.
///
/// By default requests's HTTP version depends on network stream
#[inline]
2019-04-02 12:51:16 -07:00
pub fn version(mut self, version: Version) -> Self {
self.head.version = version;
2019-03-25 21:58:01 -07:00
self
}
2019-04-02 12:51:16 -07:00
#[inline]
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head.headers
}
#[inline]
/// Returns request's mutable headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head.headers
}
2019-03-25 21:58:01 -07:00
/// Set a header.
///
/// ```rust
/// fn main() {
/// # actix_rt::System::new("test").block_on(futures::future::lazy(|| {
/// let req = awc::Client::new()
/// .get("http://www.rust-lang.org")
/// .set(awc::http::header::Date::now())
/// .set(awc::http::header::ContentType(mime::TEXT_HTML));
/// # Ok::<_, ()>(())
/// # }));
/// }
/// ```
2019-04-02 12:51:16 -07:00
pub fn set<H: Header>(mut self, hdr: H) -> Self {
match hdr.try_into() {
Ok(value) => {
self.head.headers.insert(H::name(), value);
2019-03-25 21:58:01 -07:00
}
2019-04-02 12:51:16 -07:00
Err(e) => self.err = Some(e.into()),
2019-03-25 21:58:01 -07:00
}
self
}
/// Append a header.
///
/// Header gets appended to existing header.
/// To override header use `set_header()` method.
///
/// ```rust
2019-03-26 22:33:01 -07:00
/// use awc::{http, Client};
2019-03-25 21:58:01 -07:00
///
/// fn main() {
/// # actix_rt::System::new("test").block_on(futures::future::lazy(|| {
2019-03-26 22:33:01 -07:00
/// let req = Client::new()
/// .get("http://www.rust-lang.org")
2019-03-25 21:58:01 -07:00
/// .header("X-TEST", "value")
/// .header(http::header::CONTENT_TYPE, "application/json");
/// # Ok::<_, ()>(())
/// # }));
2019-03-25 21:58:01 -07:00
/// }
/// ```
2019-04-02 12:51:16 -07:00
pub fn header<K, V>(mut self, key: K, value: V) -> Self
2019-03-25 21:58:01 -07:00
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
2019-04-02 12:51:16 -07:00
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
2019-07-17 15:08:30 +06:00
Ok(value) => self.head.headers.append(key, value),
2019-03-25 21:58:01 -07:00
Err(e) => self.err = Some(e.into()),
2019-04-02 12:51:16 -07:00
},
Err(e) => self.err = Some(e.into()),
2019-03-25 21:58:01 -07:00
}
self
}
/// Insert a header, replaces existing header.
2019-04-02 12:51:16 -07:00
pub fn set_header<K, V>(mut self, key: K, value: V) -> Self
2019-03-25 21:58:01 -07:00
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
2019-04-02 12:51:16 -07:00
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
2019-07-17 15:08:30 +06:00
Ok(value) => self.head.headers.insert(key, value),
2019-03-25 21:58:01 -07:00
Err(e) => self.err = Some(e.into()),
2019-04-02 12:51:16 -07:00
},
Err(e) => self.err = Some(e.into()),
2019-03-25 21:58:01 -07:00
}
self
}
/// Insert a header only if it is not yet set.
2019-04-02 12:51:16 -07:00
pub fn set_header_if_none<K, V>(mut self, key: K, value: V) -> Self
2019-03-25 21:58:01 -07:00
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
2019-04-02 12:51:16 -07:00
match HeaderName::try_from(key) {
Ok(key) => {
if !self.head.headers.contains_key(&key) {
match value.try_into() {
2019-07-17 15:08:30 +06:00
Ok(value) => self.head.headers.insert(key, value),
2019-04-02 12:51:16 -07:00
Err(e) => self.err = Some(e.into()),
2019-03-25 21:58:01 -07:00
}
}
}
2019-04-02 12:51:16 -07:00
Err(e) => self.err = Some(e.into()),
2019-03-25 21:58:01 -07:00
}
self
}
/// Send headers in `Camel-Case` form.
#[inline]
pub fn camel_case(mut self) -> Self {
self.head.set_camel_case_headers(true);
self
}
2019-04-02 14:08:30 -07:00
/// Force close connection instead of returning it back to connections pool.
/// This setting affect only http/1 connections.
2019-03-25 21:58:01 -07:00
#[inline]
2019-04-02 14:08:30 -07:00
pub fn force_close(mut self) -> Self {
2019-04-02 12:51:16 -07:00
self.head.set_connection_type(ConnectionType::Close);
2019-03-25 21:58:01 -07:00
self
}
/// Set request's content type
#[inline]
2019-04-02 12:51:16 -07:00
pub fn content_type<V>(mut self, value: V) -> Self
2019-03-25 21:58:01 -07:00
where
HeaderValue: HttpTryFrom<V>,
{
2019-04-02 12:51:16 -07:00
match HeaderValue::try_from(value) {
2019-07-17 15:08:30 +06:00
Ok(value) => self.head.headers.insert(header::CONTENT_TYPE, value),
2019-04-02 12:51:16 -07:00
Err(e) => self.err = Some(e.into()),
2019-03-25 21:58:01 -07:00
}
self
}
/// Set content length
#[inline]
2019-04-02 12:51:16 -07:00
pub fn content_length(self, len: u64) -> Self {
2019-03-25 21:58:01 -07:00
let mut wrt = BytesMut::new().writer();
let _ = write!(wrt, "{}", len);
self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze())
}
2019-03-26 21:31:18 -07:00
/// Set HTTP basic authorization header
2019-04-02 12:51:16 -07:00
pub fn basic_auth<U>(self, username: U, password: Option<&str>) -> Self
where
U: fmt::Display,
{
let auth = match password {
Some(password) => format!("{}:{}", username, password),
None => format!("{}:", username),
};
self.header(
header::AUTHORIZATION,
format!("Basic {}", base64::encode(&auth)),
)
}
2019-03-26 21:31:18 -07:00
/// Set HTTP bearer authentication header
2019-04-02 12:51:16 -07:00
pub fn bearer_auth<T>(self, token: T) -> Self
where
T: fmt::Display,
{
self.header(header::AUTHORIZATION, format!("Bearer {}", token))
}
2019-03-25 21:58:01 -07:00
/// Set a cookie
///
/// ```rust
/// # use actix_rt::System;
/// # use futures::future::{lazy, Future};
/// fn main() {
/// System::new("test").block_on(lazy(|| {
/// awc::Client::new().get("https://www.rust-lang.org")
/// .cookie(
/// awc::http::Cookie::build("name", "value")
/// .domain("www.rust-lang.org")
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .finish(),
/// )
/// .send()
/// .map_err(|_| ())
/// .and_then(|response| {
/// println!("Response: {:?}", response);
/// Ok(())
/// })
/// }));
/// }
/// ```
2019-07-17 15:08:30 +06:00
pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
2019-03-25 21:58:01 -07:00
if self.cookies.is_none() {
let mut jar = CookieJar::new();
jar.add(cookie.into_owned());
self.cookies = Some(jar)
} else {
self.cookies.as_mut().unwrap().add(cookie.into_owned());
}
self
}
2019-03-26 20:45:00 -07:00
/// Disable automatic decompress of response's body
2019-04-02 12:51:16 -07:00
pub fn no_decompress(mut self) -> Self {
2019-03-26 20:45:00 -07:00
self.response_decompress = false;
self
}
2019-03-29 14:07:37 -07:00
/// Set request timeout. Overrides client wide timeout setting.
///
/// Request timeout is the total time before a response must be received.
/// Default value is 5 seconds.
2019-04-02 12:51:16 -07:00
pub fn timeout(mut self, timeout: Duration) -> Self {
2019-03-29 14:07:37 -07:00
self.timeout = Some(timeout);
self
}
2019-03-25 21:58:01 -07:00
/// This method calls provided closure with builder reference if
/// value is `true`.
2019-04-17 10:28:27 -07:00
pub fn if_true<F>(self, value: bool, f: F) -> Self
2019-03-25 21:58:01 -07:00
where
F: FnOnce(ClientRequest) -> ClientRequest,
2019-03-25 21:58:01 -07:00
{
if value {
f(self)
} else {
self
2019-03-25 21:58:01 -07:00
}
}
/// This method calls provided closure with builder reference if
/// value is `Some`.
2019-04-17 10:28:27 -07:00
pub fn if_some<T, F>(self, value: Option<T>, f: F) -> Self
2019-03-25 21:58:01 -07:00
where
F: FnOnce(T, ClientRequest) -> ClientRequest,
2019-03-25 21:58:01 -07:00
{
if let Some(val) = value {
f(val, self)
} else {
self
2019-03-25 21:58:01 -07:00
}
}
pub fn freeze(self) -> Result<FrozenClientRequest, FreezeRequestError> {
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return Err(e.into()),
};
let request = FrozenClientRequest {
head: Rc::new(slf.head),
addr: slf.addr,
response_decompress: slf.response_decompress,
timeout: slf.timeout,
config: slf.config,
};
Ok(request)
}
2019-03-25 21:58:01 -07:00
/// Complete request construction and send body.
pub fn send_body<B>(
self,
2019-03-25 21:58:01 -07:00
body: B,
) -> SendBody
2019-03-25 21:58:01 -07:00
where
B: Into<Body>,
{
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return e.into(),
};
RequestSender::Owned(slf.head)
.send_body(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), body)
}
/// Set a JSON body and generate `ClientRequest`
pub fn send_json<T: Serialize>(
self,
value: &T,
) -> SendBody
{
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return e.into(),
};
RequestSender::Owned(slf.head)
.send_json(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), value)
}
/// Set a urlencoded body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn send_form<T: Serialize>(
self,
value: &T,
) -> SendBody
{
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return e.into(),
};
RequestSender::Owned(slf.head)
.send_form(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), value)
}
/// Set an streaming body and generate `ClientRequest`.
pub fn send_stream<S, E>(
self,
stream: S,
) -> SendBody
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static,
{
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return e.into(),
};
RequestSender::Owned(slf.head)
.send_stream(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), stream)
}
/// Set an empty body and generate `ClientRequest`.
pub fn send(
self,
) -> SendBody
{
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return e.into(),
};
RequestSender::Owned(slf.head)
.send(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref())
}
fn prep_for_sending(mut self) -> Result<Self, PrepForSendingError> {
if let Some(e) = self.err {
return Err(e.into());
2019-03-25 21:58:01 -07:00
}
2019-03-26 20:45:00 -07:00
// validate uri
2019-04-02 12:51:16 -07:00
let uri = &self.head.uri;
2019-03-26 20:45:00 -07:00
if uri.host().is_none() {
return Err(InvalidUrl::MissingHost.into());
2019-03-26 20:45:00 -07:00
} else if uri.scheme_part().is_none() {
return Err(InvalidUrl::MissingScheme.into());
2019-03-26 20:45:00 -07:00
} else if let Some(scheme) = uri.scheme_part() {
match scheme.as_str() {
"http" | "ws" | "https" | "wss" => (),
_ => return Err(InvalidUrl::UnknownScheme.into()),
2019-03-26 20:45:00 -07:00
}
} else {
return Err(InvalidUrl::UnknownScheme.into());
2019-03-26 20:45:00 -07:00
}
2019-03-25 21:58:01 -07:00
2019-03-29 21:13:39 -07:00
// set cookies
if let Some(ref mut jar) = self.cookies {
2019-03-29 21:13:39 -07:00
let mut cookie = String::new();
for c in jar.delta() {
2019-08-13 10:48:11 -07:00
let name = percent_encode(c.name().as_bytes(), USERINFO);
let value = percent_encode(c.value().as_bytes(), USERINFO);
2019-03-29 21:13:39 -07:00
let _ = write!(&mut cookie, "; {}={}", name, value);
2019-03-25 21:58:01 -07:00
}
2019-04-02 12:51:16 -07:00
self.head.headers.insert(
2019-03-29 21:13:39 -07:00
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
2019-03-25 21:58:01 -07:00
}
2019-04-08 11:09:57 -07:00
let mut slf = self;
2019-04-02 12:51:16 -07:00
// enable br only for https
#[cfg(any(
feature = "brotli",
feature = "flate2-zlib",
feature = "flate2-rust"
))]
2019-04-08 11:09:57 -07:00
{
if slf.response_decompress {
let https = slf
.head
.uri
.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS)
.unwrap_or(true);
if https {
slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
} else {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
{
slf = slf
.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate")
}
2019-04-08 11:09:57 -07:00
};
2019-04-02 12:51:16 -07:00
}
2019-04-08 11:09:57 -07:00
}
2019-04-02 12:51:16 -07:00
Ok(slf)
}
}
impl fmt::Debug for ClientRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"\nClientRequest {:?} {}:{}",
self.head.version, self.head.method, self.head.uri
)?;
writeln!(f, " headers:")?;
for (key, val) in self.head.headers.iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
#[derive(Clone)]
pub struct FrozenClientRequest {
pub(crate) head: Rc<RequestHead>,
pub(crate) addr: Option<net::SocketAddr>,
pub(crate) response_decompress: bool,
pub(crate) timeout: Option<Duration>,
pub(crate) config: Rc<ClientConfig>,
}
impl FrozenClientRequest {
/// Get HTTP URI of request
pub fn get_uri(&self) -> &Uri {
&self.head.uri
}
/// Get HTTP method of this request
pub fn get_method(&self) -> &Method {
&self.head.method
}
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head.headers
}
/// Send a body.
pub fn send_body<B>(
&self,
body: B,
) -> SendBody
where
B: Into<Body>,
{
RequestSender::Rc(self.head.clone(), None)
.send_body(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), body)
}
/// Send a json body.
pub fn send_json<T: Serialize>(
&self,
value: &T,
) -> SendBody
{
RequestSender::Rc(self.head.clone(), None)
.send_json(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), value)
}
/// Send an urlencoded body.
pub fn send_form<T: Serialize>(
&self,
value: &T,
) -> SendBody
{
RequestSender::Rc(self.head.clone(), None)
.send_form(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), value)
}
/// Send a streaming body.
pub fn send_stream<S, E>(
&self,
stream: S,
) -> SendBody
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static,
{
RequestSender::Rc(self.head.clone(), None)
.send_stream(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), stream)
}
/// Send an empty body.
pub fn send(
&self,
) -> SendBody
{
RequestSender::Rc(self.head.clone(), None)
.send(self.addr, self.response_decompress, self.timeout, self.config.as_ref())
}
/// Create a `FrozenSendBuilder` with extra headers
pub fn extra_headers(&self, extra_headers: HeaderMap) -> FrozenSendBuilder {
FrozenSendBuilder::new(self.clone(), extra_headers)
}
/// Create a `FrozenSendBuilder` with an extra header
pub fn extra_header<K, V>(&self, key: K, value: V) -> FrozenSendBuilder
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
self.extra_headers(HeaderMap::new()).extra_header(key, value)
}
}
pub struct FrozenSendBuilder {
req: FrozenClientRequest,
extra_headers: HeaderMap,
err: Option<HttpError>,
}
impl FrozenSendBuilder {
pub(crate) fn new(req: FrozenClientRequest, extra_headers: HeaderMap) -> Self {
Self {
req,
extra_headers,
err: None,
}
}
/// Insert a header, it overrides existing header in `FrozenClientRequest`.
pub fn extra_header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => self.extra_headers.insert(key, value),
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()),
}
self
}
/// Complete request construction and send a body.
pub fn send_body<B>(
self,
body: B,
) -> SendBody
where
B: Into<Body>,
{
if let Some(e) = self.err {
return e.into()
}
RequestSender::Rc(self.req.head, Some(self.extra_headers))
.send_body(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), body)
}
/// Complete request construction and send a json body.
pub fn send_json<T: Serialize>(
self,
value: &T,
) -> SendBody
{
if let Some(e) = self.err {
return e.into()
}
RequestSender::Rc(self.req.head, Some(self.extra_headers))
.send_json(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), value)
}
/// Complete request construction and send an urlencoded body.
pub fn send_form<T: Serialize>(
self,
value: &T,
) -> SendBody
{
if let Some(e) = self.err {
return e.into()
}
RequestSender::Rc(self.req.head, Some(self.extra_headers))
.send_form(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), value)
}
/// Complete request construction and send a streaming body.
pub fn send_stream<S, E>(
self,
stream: S,
) -> SendBody
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static,
{
if let Some(e) = self.err {
return e.into()
}
RequestSender::Rc(self.req.head, Some(self.extra_headers))
.send_stream(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), stream)
}
/// Complete request construction and send an empty body.
pub fn send(
self,
) -> SendBody
{
if let Some(e) = self.err {
return e.into()
}
RequestSender::Rc(self.req.head, Some(self.extra_headers))
.send(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref())
}
}
#[derive(Debug, From)]
enum PrepForSendingError {
Url(InvalidUrl),
Http(HttpError),
}
impl Into<FreezeRequestError> for PrepForSendingError {
fn into(self) -> FreezeRequestError {
match self {
PrepForSendingError::Url(e) => FreezeRequestError::Url(e),
PrepForSendingError::Http(e) => FreezeRequestError::Http(e),
}
}
}
impl Into<SendRequestError> for PrepForSendingError {
fn into(self) -> SendRequestError {
match self {
PrepForSendingError::Url(e) => SendRequestError::Url(e),
PrepForSendingError::Http(e) => SendRequestError::Http(e),
}
}
}
pub enum SendBody
{
Fut(Box<dyn Future<Item = ClientResponse, Error = SendRequestError>>, Option<Delay>, bool),
Err(Option<SendRequestError>),
}
impl SendBody
{
pub fn new(
send: Box<dyn Future<Item = ClientResponse, Error = SendRequestError>>,
response_decompress: bool,
timeout: Option<Duration>,
) -> SendBody
{
let delay = timeout.map(|t| Delay::new(Instant::now() + t));
SendBody::Fut(send, delay, response_decompress)
}
}
impl Future for SendBody
{
type Item = ClientResponse<Decoder<Payload<PayloadStream>>>;
type Error = SendRequestError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self {
SendBody::Fut(send, delay, response_decompress) => {
if delay.is_some() {
match delay.poll() {
Ok(Async::NotReady) => (),
_ => return Err(SendRequestError::Timeout),
2019-03-26 20:45:00 -07:00
}
2019-03-28 22:33:41 -07:00
}
let res = try_ready!(send.poll())
.map_body(|head, payload| {
if *response_decompress {
Payload::Stream(Decoder::from_headers(payload, &head.headers))
} else {
Payload::Stream(Decoder::new(payload, ContentEncoding::Identity))
}
});
Ok(Async::Ready(res))
},
SendBody::Err(ref mut e) => {
match e.take() {
Some(e) => Err(e.into()),
None => panic!("Attempting to call completed future"),
}
}
2019-03-28 22:33:41 -07:00
}
2019-03-25 21:58:01 -07:00
}
}
2019-03-25 21:58:01 -07:00
impl From<SendRequestError> for SendBody
{
fn from(e: SendRequestError) -> Self {
SendBody::Err(Some(e))
}
}
impl From<Error> for SendBody
{
fn from(e: Error) -> Self {
SendBody::Err(Some(e.into()))
}
}
impl From<HttpError> for SendBody
{
fn from(e: HttpError) -> Self {
SendBody::Err(Some(e.into()))
}
}
impl From<PrepForSendingError> for SendBody
{
fn from(e: PrepForSendingError) -> Self {
SendBody::Err(Some(e.into()))
}
}
#[derive(Debug)]
enum RequestSender {
Owned(RequestHead),
Rc(Rc<RequestHead>, Option<HeaderMap>),
}
impl RequestSender {
pub fn send_body<B>(
2019-04-02 12:51:16 -07:00
self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
body: B,
) -> SendBody
where
B: Into<Body>,
{
let mut connector = config.connector.borrow_mut();
let fut = match self {
RequestSender::Owned(head) => connector.send_request(head, body.into(), addr),
RequestSender::Rc(head, extra_headers) => connector.send_request_extra(head, extra_headers, body.into(), addr),
};
SendBody::new(fut, response_decompress, timeout.or_else(|| config.timeout.clone()))
}
pub fn send_json<T: Serialize>(
mut self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
value: &T,
) -> SendBody
{
2019-04-01 18:00:38 -07:00
let body = match serde_json::to_string(value) {
2019-03-25 21:58:01 -07:00
Ok(body) => body,
Err(e) => return Error::from(e).into(),
2019-03-25 21:58:01 -07:00
};
if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") {
return e.into();
}
2019-03-25 21:58:01 -07:00
self.send_body(addr, response_decompress, timeout, config, Body::Bytes(Bytes::from(body)))
2019-03-25 21:58:01 -07:00
}
pub fn send_form<T: Serialize>(
mut self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
2019-04-01 18:00:38 -07:00
value: &T,
) -> SendBody
{
2019-04-01 18:00:38 -07:00
let body = match serde_urlencoded::to_string(value) {
2019-03-25 21:58:01 -07:00
Ok(body) => body,
Err(e) => return Error::from(e).into(),
2019-03-25 21:58:01 -07:00
};
// set content-type
if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/x-www-form-urlencoded") {
return e.into();
}
2019-03-25 21:58:01 -07:00
self.send_body(addr, response_decompress, timeout, config, Body::Bytes(Bytes::from(body)))
2019-03-25 21:58:01 -07:00
}
pub fn send_stream<S, E>(
2019-04-02 12:51:16 -07:00
self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
2019-03-25 21:58:01 -07:00
stream: S,
) -> SendBody
2019-03-25 21:58:01 -07:00
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static,
{
self.send_body(addr, response_decompress, timeout, config, Body::from_message(BodyStream::new(stream)))
2019-03-25 21:58:01 -07:00
}
2019-03-26 20:45:00 -07:00
pub fn send(
2019-04-02 12:51:16 -07:00
self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
) -> SendBody
{
self.send_body(addr, response_decompress, timeout, config, Body::Empty)
2019-03-25 21:58:01 -07:00
}
fn set_header_if_none<V>(&mut self, key: HeaderName, value: V) -> Result<(), HttpError>
where
V: IntoHeaderValue,
{
match self {
RequestSender::Owned(head) => {
if !head.headers.contains_key(&key) {
match value.try_into() {
Ok(value) => head.headers.insert(key, value),
Err(e) => return Err(e.into()),
}
}
},
RequestSender::Rc(head, extra_headers) => {
if !head.headers.contains_key(&key) && !extra_headers.iter().any(|h| h.contains_key(&key)) {
match value.try_into(){
Ok(v) => {
let h = extra_headers.get_or_insert(HeaderMap::new());
h.insert(key, v)
},
Err(e) => return Err(e.into()),
};
}
}
2019-03-25 21:58:01 -07:00
}
2019-03-25 21:58:01 -07:00
Ok(())
}
}
#[cfg(test)]
mod tests {
2019-04-14 20:20:33 -07:00
use std::time::SystemTime;
use super::*;
use crate::Client;
#[test]
fn test_debug() {
let request = Client::new().get("/").header("x-test", "111");
let repr = format!("{:?}", request);
assert!(repr.contains("ClientRequest"));
assert!(repr.contains("x-test"));
}
2019-04-14 20:20:33 -07:00
#[test]
fn test_basics() {
let mut req = Client::new()
.put("/")
.version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into()))
.content_type("plain/text")
2019-04-18 21:28:23 -07:00
.if_true(true, |req| req.header(header::SERVER, "awc"))
.if_true(false, |req| req.header(header::EXPECT, "awc"))
.if_some(Some("server"), |val, req| {
req.header(header::USER_AGENT, val)
})
.if_some(Option::<&str>::None, |_, req| {
req.header(header::ALLOW, "1")
})
2019-04-14 20:20:33 -07:00
.content_length(100);
assert!(req.headers().contains_key(header::CONTENT_TYPE));
assert!(req.headers().contains_key(header::DATE));
2019-04-18 21:28:23 -07:00
assert!(req.headers().contains_key(header::SERVER));
assert!(req.headers().contains_key(header::USER_AGENT));
assert!(!req.headers().contains_key(header::ALLOW));
assert!(!req.headers().contains_key(header::EXPECT));
2019-04-14 20:20:33 -07:00
assert_eq!(req.head.version, Version::HTTP_2);
let _ = req.headers_mut();
let _ = req.send_body("");
}
#[test]
fn test_client_header() {
let req = Client::build()
.header(header::CONTENT_TYPE, "111")
.finish()
.get("/");
assert_eq!(
req.head
.headers
.get(header::CONTENT_TYPE)
.unwrap()
.to_str()
.unwrap(),
"111"
);
}
#[test]
fn test_client_header_override() {
let req = Client::build()
.header(header::CONTENT_TYPE, "111")
.finish()
.get("/")
.set_header(header::CONTENT_TYPE, "222");
assert_eq!(
req.head
.headers
.get(header::CONTENT_TYPE)
.unwrap()
.to_str()
.unwrap(),
"222"
);
}
#[test]
fn client_basic_auth() {
let req = Client::new()
.get("/")
.basic_auth("username", Some("password"));
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
);
let req = Client::new().get("/").basic_auth("username", None);
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Basic dXNlcm5hbWU6"
);
}
#[test]
fn client_bearer_auth() {
let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n");
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Bearer someS3cr3tAutht0k3n"
);
}
}