1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-28 09:42:40 +01:00
actix-extras/awc/src/request.rs

723 lines
22 KiB
Rust
Raw Normal View History

2019-03-26 05:58:01 +01:00
use std::fmt;
2019-03-30 05:13:39 +01:00
use std::fmt::Write as FmtWrite;
2019-03-26 05:58:01 +01:00
use std::io::Write;
use std::rc::Rc;
2019-03-29 22:07:37 +01:00
use std::time::Duration;
2019-03-26 05:58:01 +01:00
use bytes::{BufMut, Bytes, BytesMut};
use futures::future::{err, Either};
use futures::{Future, Stream};
2019-03-30 05:13:39 +01:00
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
2019-03-26 05:58:01 +01:00
use serde::Serialize;
use serde_json;
2019-03-29 06:33:41 +01:00
use tokio_timer::Timeout;
2019-03-26 05:58:01 +01:00
use actix_http::body::{Body, BodyStream};
2019-03-30 05:13:39 +01:00
use actix_http::cookie::{Cookie, CookieJar};
2019-03-27 04:45:00 +01:00
use actix_http::encoding::Decoder;
use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue};
2019-03-26 05:58:01 +01:00
use actix_http::http::{
uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom,
Method, Uri, Version,
};
2019-03-27 18:38:01 +01:00
use actix_http::{Error, Payload, RequestHead};
2019-03-26 05:58:01 +01:00
2019-03-28 02:53:19 +01:00
use crate::error::{InvalidUrl, PayloadError, SendRequestError};
use crate::response::ClientResponse;
2019-03-29 06:33:41 +01:00
use crate::ClientConfig;
2019-03-27 04:45:00 +01: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-26 05:58:01 +01: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-26 05:58:01 +01:00
/// .header("User-Agent", "Actix-web")
/// .send() // <- Send http request
2019-03-26 05:58:01 +01:00
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
2019-03-26 05:58:01 +01:00
/// println!("Response: {:?}", response);
/// Ok(())
/// })
/// }));
/// }
/// ```
pub struct ClientRequest {
pub(crate) head: Option<RequestHead>,
2019-03-26 05:58:01 +01:00
err: Option<HttpError>,
cookies: Option<CookieJar>,
default_headers: bool,
2019-03-27 04:45:00 +01:00
response_decompress: bool,
2019-03-29 22:07:37 +01:00
timeout: Option<Duration>,
2019-03-29 06:33:41 +01:00
config: Rc<ClientConfig>,
2019-03-26 05:58:01 +01:00
}
impl ClientRequest {
/// Create new client request builder.
2019-03-29 06:33:41 +01:00
pub(crate) fn new<U>(method: Method, uri: U, config: Rc<ClientConfig>) -> Self
2019-03-26 05:58:01 +01:00
where
Uri: HttpTryFrom<U>,
{
let mut req = ClientRequest {
2019-03-29 06:33:41 +01:00
config,
head: Some(RequestHead::default()),
2019-03-30 00:27:18 +01:00
err: None,
2019-03-26 05:58:01 +01:00
cookies: None,
2019-03-29 22:07:37 +01:00
timeout: None,
2019-03-26 05:58:01 +01:00
default_headers: true,
2019-03-27 04:45:00 +01:00
response_decompress: true,
};
req.method(method).uri(uri);
req
2019-03-30 00:27:18 +01:00
}
/// Set HTTP URI of request.
#[inline]
pub fn uri<U>(&mut self, uri: U) -> &mut Self
2019-03-30 00:27:18 +01:00
where
Uri: HttpTryFrom<U>,
{
if let Some(head) = parts(&mut self.head, &self.err) {
match Uri::try_from(uri) {
Ok(uri) => head.uri = uri,
Err(e) => self.err = Some(e.into()),
}
2019-03-30 00:27:18 +01:00
}
self
2019-03-26 05:58:01 +01:00
}
/// Set HTTP method of this request.
#[inline]
pub fn method(&mut self, method: Method) -> &mut Self {
if let Some(head) = parts(&mut self.head, &self.err) {
head.method = method;
}
2019-03-26 05:58:01 +01:00
self
}
#[doc(hidden)]
/// Set HTTP version of this request.
///
/// By default requests's HTTP version depends on network stream
#[inline]
pub fn version(&mut self, version: Version) -> &mut Self {
if let Some(head) = parts(&mut self.head, &self.err) {
head.version = version;
}
2019-03-26 05:58:01 +01:00
self
}
/// 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::<_, ()>(())
/// # }));
/// }
/// ```
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
if let Some(head) = parts(&mut self.head, &self.err) {
match hdr.try_into() {
Ok(value) => {
head.headers.insert(H::name(), value);
}
Err(e) => self.err = Some(e.into()),
2019-03-26 05:58:01 +01:00
}
}
self
}
/// Append a header.
///
/// Header gets appended to existing header.
/// To override header use `set_header()` method.
///
/// ```rust
2019-03-27 06:33:01 +01:00
/// use awc::{http, Client};
2019-03-26 05:58:01 +01:00
///
/// fn main() {
/// # actix_rt::System::new("test").block_on(futures::future::lazy(|| {
2019-03-27 06:33:01 +01:00
/// let req = Client::new()
/// .get("http://www.rust-lang.org")
2019-03-26 05:58:01 +01:00
/// .header("X-TEST", "value")
/// .header(http::header::CONTENT_TYPE, "application/json");
/// # Ok::<_, ()>(())
/// # }));
2019-03-26 05:58:01 +01:00
/// }
/// ```
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
2019-03-26 05:58:01 +01:00
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(head) = parts(&mut self.head, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
head.headers.append(key, value);
}
Err(e) => self.err = Some(e.into()),
},
2019-03-26 05:58:01 +01:00
Err(e) => self.err = Some(e.into()),
}
2019-03-26 05:58:01 +01:00
}
self
}
/// Insert a header, replaces existing header.
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
2019-03-26 05:58:01 +01:00
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(head) = parts(&mut self.head, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
head.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
},
2019-03-26 05:58:01 +01:00
Err(e) => self.err = Some(e.into()),
}
2019-03-26 05:58:01 +01:00
}
self
}
/// Insert a header only if it is not yet set.
pub fn set_header_if_none<K, V>(&mut self, key: K, value: V) -> &mut Self
2019-03-26 05:58:01 +01:00
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(head) = parts(&mut self.head, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => {
if !head.headers.contains_key(&key) {
match value.try_into() {
Ok(value) => {
head.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
2019-03-26 05:58:01 +01:00
}
}
}
Err(e) => self.err = Some(e.into()),
2019-03-26 05:58:01 +01:00
}
}
self
}
/// Close connection instead of returning it back to connections pool.
/// This setting affect only http/1 connections.
2019-03-26 05:58:01 +01:00
#[inline]
pub fn close_connection(&mut self) -> &mut Self {
if let Some(head) = parts(&mut self.head, &self.err) {
head.set_connection_type(ConnectionType::Close);
}
2019-03-26 05:58:01 +01:00
self
}
/// Set request's content type
#[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self
2019-03-26 05:58:01 +01:00
where
HeaderValue: HttpTryFrom<V>,
{
if let Some(head) = parts(&mut self.head, &self.err) {
match HeaderValue::try_from(value) {
Ok(value) => {
let _ = head.headers.insert(header::CONTENT_TYPE, value);
}
Err(e) => self.err = Some(e.into()),
2019-03-26 05:58:01 +01:00
}
}
self
}
/// Set content length
#[inline]
pub fn content_length(&mut self, len: u64) -> &mut Self {
2019-03-26 05:58:01 +01:00
let mut wrt = BytesMut::new().writer();
let _ = write!(wrt, "{}", len);
self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze())
}
2019-03-27 05:31:18 +01:00
/// Set HTTP basic authorization header
pub fn basic_auth<U>(&mut self, username: U, password: Option<&str>) -> &mut 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-27 05:31:18 +01:00
/// Set HTTP bearer authentication header
pub fn bearer_auth<T>(&mut self, token: T) -> &mut Self
where
T: fmt::Display,
{
self.header(header::AUTHORIZATION, format!("Bearer {}", token))
}
2019-03-26 05:58:01 +01: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(())
/// })
/// }));
/// }
/// ```
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
2019-03-26 05:58:01 +01: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
}
/// Do not add default request headers.
2019-03-27 06:33:01 +01:00
/// By default `Date` and `User-Agent` headers are set.
pub fn no_default_headers(&mut self) -> &mut Self {
2019-03-26 05:58:01 +01:00
self.default_headers = false;
self
}
2019-03-27 04:45:00 +01:00
/// Disable automatic decompress of response's body
pub fn no_decompress(&mut self) -> &mut Self {
2019-03-27 04:45:00 +01:00
self.response_decompress = false;
self
}
2019-03-29 22:07:37 +01: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.
pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
2019-03-29 22:07:37 +01:00
self.timeout = Some(timeout);
self
}
2019-03-26 05:58:01 +01:00
/// This method calls provided closure with builder reference if
/// value is `true`.
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
2019-03-26 05:58:01 +01:00
where
F: FnOnce(&mut ClientRequest),
{
if value {
f(self);
2019-03-26 05:58:01 +01:00
}
self
}
/// This method calls provided closure with builder reference if
/// value is `Some`.
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
2019-03-26 05:58:01 +01:00
where
F: FnOnce(T, &mut ClientRequest),
{
if let Some(val) = value {
f(val, self);
2019-03-26 05:58:01 +01:00
}
self
}
/// Complete request construction and send body.
pub fn send_body<B>(
&mut self,
2019-03-26 05:58:01 +01:00
body: B,
2019-03-27 04:45:00 +01:00
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
>
2019-03-26 05:58:01 +01:00
where
B: Into<Body>,
{
if let Some(e) = self.err.take() {
return Either::A(err(e.into()));
}
let mut head = self.head.take().expect("cannot reuse response builder");
2019-03-27 04:45:00 +01:00
// validate uri
let uri = &head.uri;
2019-03-27 04:45:00 +01:00
if uri.host().is_none() {
return Either::A(err(InvalidUrl::MissingHost.into()));
} else if uri.scheme_part().is_none() {
return Either::A(err(InvalidUrl::MissingScheme.into()));
} else if let Some(scheme) = uri.scheme_part() {
match scheme.as_str() {
"http" | "ws" | "https" | "wss" => (),
_ => return Either::A(err(InvalidUrl::UnknownScheme.into())),
}
} else {
return Either::A(err(InvalidUrl::UnknownScheme.into()));
}
2019-03-26 05:58:01 +01:00
2019-03-27 04:45:00 +01:00
// set default headers
if self.default_headers {
2019-03-26 05:58:01 +01:00
// set request host header
if let Some(host) = head.uri.host() {
if !head.headers.contains_key(header::HOST) {
2019-03-26 05:58:01 +01:00
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match head.uri.port_u16() {
2019-03-26 05:58:01 +01:00
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
head.headers.insert(header::HOST, value);
2019-03-26 05:58:01 +01:00
}
2019-03-27 04:45:00 +01:00
Err(e) => return Either::A(err(HttpError::from(e).into())),
2019-03-26 05:58:01 +01:00
}
}
}
// user agent
2019-03-27 04:45:00 +01:00
self.set_header_if_none(
2019-03-26 05:58:01 +01:00
header::USER_AGENT,
2019-03-27 06:33:01 +01:00
concat!("awc/", env!("CARGO_PKG_VERSION")),
);
}
2019-03-26 05:58:01 +01:00
2019-03-27 04:45:00 +01:00
// enable br only for https
let https = head
2019-03-27 04:45:00 +01:00
.uri
.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS)
.unwrap_or(true);
#[cfg(any(
feature = "brotli",
feature = "flate2-zlib",
feature = "flate2-rust"
))]
{
2019-03-27 04:45:00 +01:00
if https {
self.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING);
2019-03-27 04:45:00 +01:00
} else {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
{
self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate");
2019-03-27 04:45:00 +01:00
}
}
}
2019-03-26 05:58:01 +01:00
2019-03-30 05:13:39 +01:00
// set cookies
if let Some(ref mut jar) = self.cookies {
2019-03-30 05:13:39 +01:00
let mut cookie = String::new();
for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value);
2019-03-26 05:58:01 +01:00
}
2019-03-30 05:13:39 +01:00
head.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
2019-03-26 05:58:01 +01:00
}
let config = self.config.as_ref();
let response_decompress = self.response_decompress;
2019-03-27 04:45:00 +01:00
2019-03-29 22:07:37 +01:00
let fut = config
2019-03-27 04:45:00 +01:00
.connector
.borrow_mut()
.send_request(head, body.into())
.map(move |res| {
res.map_body(|head, payload| {
if response_decompress {
2019-03-28 19:08:24 +01:00
Payload::Stream(Decoder::from_headers(payload, &head.headers))
2019-03-27 04:45:00 +01:00
} else {
Payload::Stream(Decoder::new(payload, ContentEncoding::Identity))
}
})
});
2019-03-29 06:33:41 +01:00
// set request timeout
if let Some(timeout) = self.timeout.or_else(|| config.timeout.clone()) {
2019-03-29 06:33:41 +01:00
Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| {
if let Some(e) = e.into_inner() {
e
} else {
SendRequestError::Timeout
}
})))
} else {
Either::B(Either::B(fut))
}
2019-03-26 05:58:01 +01:00
}
/// Set a JSON body and generate `ClientRequest`
pub fn send_json<T: Serialize>(
&mut self,
value: &T,
2019-03-27 04:45:00 +01:00
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
2019-04-02 03:00:38 +02:00
let body = match serde_json::to_string(value) {
2019-03-26 05:58:01 +01:00
Ok(body) => body,
Err(e) => return Either::A(err(Error::from(e).into())),
};
2019-03-26 05:58:01 +01:00
// set content-type
self.set_header_if_none(header::CONTENT_TYPE, "application/json");
2019-03-26 05:58:01 +01:00
Either::B(self.send_body(Body::Bytes(Bytes::from(body))))
2019-03-26 05:58:01 +01:00
}
/// Set a urlencoded body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn send_form<T: Serialize>(
&mut self,
2019-04-02 03:00:38 +02:00
value: &T,
2019-03-27 04:45:00 +01:00
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
2019-04-02 03:00:38 +02:00
let body = match serde_urlencoded::to_string(value) {
2019-03-26 05:58:01 +01:00
Ok(body) => body,
Err(e) => return Either::A(err(Error::from(e).into())),
};
// set content-type
self.set_header_if_none(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
);
2019-03-26 05:58:01 +01:00
Either::B(self.send_body(Body::Bytes(Bytes::from(body))))
2019-03-26 05:58:01 +01:00
}
/// Set an streaming body and generate `ClientRequest`.
pub fn send_stream<S, E>(
&mut self,
2019-03-26 05:58:01 +01:00
stream: S,
2019-03-27 04:45:00 +01:00
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
>
2019-03-26 05:58:01 +01:00
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static,
{
self.send_body(Body::from_message(BodyStream::new(stream)))
}
/// Set an empty body and generate `ClientRequest`.
2019-03-27 04:45:00 +01:00
pub fn send(
&mut self,
2019-03-27 04:45:00 +01:00
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
2019-03-26 05:58:01 +01:00
self.send_body(Body::Empty)
}
}
impl std::ops::Deref for ClientRequest {
type Target = RequestHead;
fn deref(&self) -> &RequestHead {
self.head.as_ref().expect("cannot reuse response builder")
}
}
impl std::ops::DerefMut for ClientRequest {
fn deref_mut(&mut self) -> &mut RequestHead {
self.head.as_mut().expect("cannot reuse response builder")
}
}
2019-03-26 05:58:01 +01:00
impl fmt::Debug for ClientRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let head = self.head.as_ref().expect("cannot reuse response builder");
2019-03-26 05:58:01 +01:00
writeln!(
f,
"\nClientRequest {:?} {}:{}",
head.version, head.method, head.uri
2019-03-26 05:58:01 +01:00
)?;
writeln!(f, " headers:")?;
for (key, val) in head.headers.iter() {
2019-03-26 05:58:01 +01:00
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
#[inline]
fn parts<'a>(
parts: &'a mut Option<RequestHead>,
err: &Option<HttpError>,
) -> Option<&'a mut RequestHead> {
if err.is_some() {
return None;
}
parts.as_mut()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{test, Client};
#[test]
fn test_debug() {
test::run_on(|| {
let mut request = Client::new().get("/");
request.header("x-test", "111");
let repr = format!("{:?}", request);
assert!(repr.contains("ClientRequest"));
assert!(repr.contains("x-test"));
})
}
#[test]
fn test_client_header() {
test::run_on(|| {
let req = Client::build()
.header(header::CONTENT_TYPE, "111")
.finish()
.get("/");
assert_eq!(
req.head
.as_ref()
.unwrap()
.headers
.get(header::CONTENT_TYPE)
.unwrap()
.to_str()
.unwrap(),
"111"
);
})
}
#[test]
fn test_client_header_override() {
test::run_on(|| {
let mut req = Client::build()
.header(header::CONTENT_TYPE, "111")
.finish()
.get("/");
req.set_header(header::CONTENT_TYPE, "222");
assert_eq!(
req.head
.as_ref()
.unwrap()
.headers
.get(header::CONTENT_TYPE)
.unwrap()
.to_str()
.unwrap(),
"222"
);
})
}
#[test]
fn client_basic_auth() {
test::run_on(|| {
let mut req = Client::new().get("/");
req.basic_auth("username", Some("password"));
assert_eq!(
req.head
.as_ref()
.unwrap()
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
);
let mut req = Client::new().get("/");
req.basic_auth("username", None);
assert_eq!(
req.head
.as_ref()
.unwrap()
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Basic dXNlcm5hbWU="
);
});
}
#[test]
fn client_bearer_auth() {
test::run_on(|| {
let mut req = Client::new().get("/");
req.bearer_auth("someS3cr3tAutht0k3n");
assert_eq!(
req.head
.as_ref()
.unwrap()
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Bearer someS3cr3tAutht0k3n"
);
})
}
}