2021-06-17 18:57:58 +02:00
|
|
|
use std::{convert::TryFrom, error::Error as StdError, fmt, net, rc::Rc, time::Duration};
|
2019-03-26 05:58:01 +01:00
|
|
|
|
2019-12-05 18:35:43 +01:00
|
|
|
use bytes::Bytes;
|
2019-12-13 06:24:57 +01:00
|
|
|
use futures_core::Stream;
|
2019-03-26 05:58:01 +01:00
|
|
|
use serde::Serialize;
|
|
|
|
|
2021-06-17 18:57:58 +02:00
|
|
|
use actix_http::{
|
2021-11-16 23:10:30 +01:00
|
|
|
body::AnyBody,
|
2021-06-17 18:57:58 +02:00
|
|
|
http::{
|
|
|
|
header::{self, IntoHeaderPair},
|
2021-06-19 21:21:13 +02:00
|
|
|
ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version,
|
2021-06-17 18:57:58 +02:00
|
|
|
},
|
|
|
|
RequestHead,
|
2019-03-26 05:58:01 +01:00
|
|
|
};
|
|
|
|
|
2021-04-09 19:07:10 +02:00
|
|
|
#[cfg(feature = "cookies")]
|
|
|
|
use crate::cookie::{Cookie, CookieJar};
|
2021-06-17 18:57:58 +02:00
|
|
|
use crate::{
|
|
|
|
error::{FreezeRequestError, InvalidUrl},
|
|
|
|
frozen::FrozenClientRequest,
|
|
|
|
sender::{PrepForSendingError, RequestSender, SendClientRequest},
|
|
|
|
ClientConfig,
|
|
|
|
};
|
2019-03-27 04:45:00 +01:00
|
|
|
|
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.
|
|
|
|
///
|
2021-03-25 09:45:52 +01:00
|
|
|
/// ```
|
2019-11-26 06:25:50 +01:00
|
|
|
/// #[actix_rt::main]
|
|
|
|
/// async fn main() {
|
|
|
|
/// let response = awc::Client::new()
|
|
|
|
/// .get("http://www.rust-lang.org") // <- Create request builder
|
2021-01-15 03:11:10 +01:00
|
|
|
/// .insert_header(("User-Agent", "Actix-web"))
|
2021-02-11 23:39:54 +01:00
|
|
|
/// .send() // <- Send HTTP request
|
2019-11-26 06:25:50 +01:00
|
|
|
/// .await;
|
2019-11-20 19:35:07 +01:00
|
|
|
///
|
2021-02-11 23:39:54 +01:00
|
|
|
/// response.and_then(|response| { // <- server HTTP response
|
2019-11-26 06:25:50 +01:00
|
|
|
/// println!("Response: {:?}", response);
|
|
|
|
/// Ok(())
|
|
|
|
/// });
|
2019-03-26 05:58:01 +01:00
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub struct ClientRequest {
|
2019-04-02 21:51:16 +02:00
|
|
|
pub(crate) head: RequestHead,
|
2019-03-26 05:58:01 +01:00
|
|
|
err: Option<HttpError>,
|
2019-04-20 03:03:44 +02:00
|
|
|
addr: Option<net::SocketAddr>,
|
2019-03-27 04:45:00 +01:00
|
|
|
response_decompress: bool,
|
2019-03-29 22:07:37 +01:00
|
|
|
timeout: Option<Duration>,
|
2021-03-18 18:53:22 +01:00
|
|
|
config: ClientConfig,
|
2021-02-13 16:08:43 +01:00
|
|
|
|
|
|
|
#[cfg(feature = "cookies")]
|
|
|
|
cookies: Option<CookieJar>,
|
2019-03-26 05:58:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ClientRequest {
|
|
|
|
/// Create new client request builder.
|
2021-03-18 18:53:22 +01:00
|
|
|
pub(crate) fn new<U>(method: Method, uri: U, config: ClientConfig) -> Self
|
2019-03-26 05:58:01 +01:00
|
|
|
where
|
2019-12-05 18:35:43 +01:00
|
|
|
Uri: TryFrom<U>,
|
|
|
|
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
2019-03-26 05:58:01 +01:00
|
|
|
{
|
2019-04-02 21:51:16 +02:00
|
|
|
ClientRequest {
|
2019-03-29 06:33:41 +01:00
|
|
|
config,
|
2019-04-02 21:51:16 +02:00
|
|
|
head: RequestHead::default(),
|
2019-03-30 00:27:18 +01:00
|
|
|
err: None,
|
2019-04-20 03:03:44 +02:00
|
|
|
addr: None,
|
2021-02-13 16:08:43 +01:00
|
|
|
#[cfg(feature = "cookies")]
|
2019-03-26 05:58:01 +01:00
|
|
|
cookies: None,
|
2019-03-29 22:07:37 +01:00
|
|
|
timeout: None,
|
2019-03-27 04:45:00 +01:00
|
|
|
response_decompress: true,
|
2019-04-02 21:51:16 +02:00
|
|
|
}
|
|
|
|
.method(method)
|
|
|
|
.uri(uri)
|
2019-03-30 00:27:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Set HTTP URI of request.
|
|
|
|
#[inline]
|
2019-04-02 21:51:16 +02:00
|
|
|
pub fn uri<U>(mut self, uri: U) -> Self
|
2019-03-30 00:27:18 +01:00
|
|
|
where
|
2019-12-05 18:35:43 +01:00
|
|
|
Uri: TryFrom<U>,
|
|
|
|
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
2019-03-30 00:27:18 +01:00
|
|
|
{
|
2019-04-02 21:51:16 +02:00
|
|
|
match Uri::try_from(uri) {
|
|
|
|
Ok(uri) => self.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
|
|
|
}
|
|
|
|
|
2019-09-25 08:50:45 +02:00
|
|
|
/// Get HTTP URI of request.
|
2019-09-10 06:29:32 +02:00
|
|
|
pub fn get_uri(&self) -> &Uri {
|
|
|
|
&self.head.uri
|
|
|
|
}
|
|
|
|
|
2019-04-20 03:03:44 +02:00
|
|
|
/// 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-26 05:58:01 +01:00
|
|
|
/// Set HTTP method of this request.
|
|
|
|
#[inline]
|
2019-04-02 21:51:16 +02:00
|
|
|
pub fn method(mut self, method: Method) -> Self {
|
|
|
|
self.head.method = method;
|
2019-03-26 05:58:01 +01:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-09-10 06:29:32 +02:00
|
|
|
/// Get HTTP method of this request
|
|
|
|
pub fn get_method(&self) -> &Method {
|
|
|
|
&self.head.method
|
|
|
|
}
|
|
|
|
|
2019-03-26 05:58:01 +01:00
|
|
|
/// Set HTTP version of this request.
|
|
|
|
///
|
|
|
|
/// By default requests's HTTP version depends on network stream
|
2021-11-22 19:16:56 +01:00
|
|
|
#[doc(hidden)]
|
2019-03-26 05:58:01 +01:00
|
|
|
#[inline]
|
2019-04-02 21:51:16 +02:00
|
|
|
pub fn version(mut self, version: Version) -> Self {
|
|
|
|
self.head.version = version;
|
2019-03-26 05:58:01 +01:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-09-25 08:50:45 +02:00
|
|
|
/// Get HTTP version of this request.
|
|
|
|
pub fn get_version(&self) -> &Version {
|
|
|
|
&self.head.version
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get peer address of this request.
|
|
|
|
pub fn get_peer_addr(&self) -> &Option<net::SocketAddr> {
|
|
|
|
&self.head.peer_addr
|
|
|
|
}
|
|
|
|
|
2019-04-02 21:51:16 +02:00
|
|
|
/// Returns request's headers.
|
2021-01-15 03:11:10 +01:00
|
|
|
#[inline]
|
2019-04-02 21:51:16 +02:00
|
|
|
pub fn headers(&self) -> &HeaderMap {
|
|
|
|
&self.head.headers
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns request's mutable headers.
|
2021-01-15 03:11:10 +01:00
|
|
|
#[inline]
|
2019-04-02 21:51:16 +02:00
|
|
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
|
|
|
&mut self.head.headers
|
|
|
|
}
|
|
|
|
|
2021-01-15 03:11:10 +01:00
|
|
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
|
|
|
pub fn insert_header<H>(mut self, header: H) -> Self
|
2019-03-26 05:58:01 +01:00
|
|
|
where
|
2021-01-15 03:11:10 +01:00
|
|
|
H: IntoHeaderPair,
|
2019-03-26 05:58:01 +01:00
|
|
|
{
|
2021-01-15 03:11:10 +01:00
|
|
|
match header.try_into_header_pair() {
|
2021-02-09 23:59:17 +01:00
|
|
|
Ok((key, value)) => {
|
|
|
|
self.head.headers.insert(key, value);
|
|
|
|
}
|
2019-04-02 21:51:16 +02:00
|
|
|
Err(e) => self.err = Some(e.into()),
|
2021-01-15 03:11:10 +01:00
|
|
|
};
|
|
|
|
|
2019-03-26 05:58:01 +01:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-01-15 03:11:10 +01:00
|
|
|
/// Insert a header only if it is not yet set.
|
|
|
|
pub fn insert_header_if_none<H>(mut self, header: H) -> Self
|
2019-03-26 05:58:01 +01:00
|
|
|
where
|
2021-01-15 03:11:10 +01:00
|
|
|
H: IntoHeaderPair,
|
2019-03-26 05:58:01 +01:00
|
|
|
{
|
2021-01-15 03:11:10 +01:00
|
|
|
match header.try_into_header_pair() {
|
|
|
|
Ok((key, value)) => {
|
|
|
|
if !self.head.headers.contains_key(&key) {
|
|
|
|
self.head.headers.insert(key, value);
|
|
|
|
}
|
|
|
|
}
|
2019-04-02 21:51:16 +02:00
|
|
|
Err(e) => self.err = Some(e.into()),
|
2021-01-15 03:11:10 +01:00
|
|
|
};
|
|
|
|
|
2019-03-26 05:58:01 +01:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-01-15 03:11:10 +01:00
|
|
|
/// Append a header, keeping any that were set with an equivalent field name.
|
|
|
|
///
|
2021-03-25 09:45:52 +01:00
|
|
|
/// ```
|
2021-01-15 03:11:10 +01:00
|
|
|
/// # #[actix_rt::main]
|
|
|
|
/// # async fn main() {
|
|
|
|
/// # use awc::Client;
|
2021-04-01 17:42:18 +02:00
|
|
|
/// use awc::http::header::CONTENT_TYPE;
|
2021-01-15 03:11:10 +01:00
|
|
|
///
|
|
|
|
/// Client::new()
|
|
|
|
/// .get("http://www.rust-lang.org")
|
|
|
|
/// .insert_header(("X-TEST", "value"))
|
2021-04-01 17:42:18 +02:00
|
|
|
/// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON));
|
2021-01-15 03:11:10 +01:00
|
|
|
/// # }
|
|
|
|
/// ```
|
|
|
|
pub fn append_header<H>(mut self, header: H) -> Self
|
2019-03-26 05:58:01 +01:00
|
|
|
where
|
2021-01-15 03:11:10 +01:00
|
|
|
H: IntoHeaderPair,
|
2019-03-26 05:58:01 +01:00
|
|
|
{
|
2021-01-15 03:11:10 +01:00
|
|
|
match header.try_into_header_pair() {
|
|
|
|
Ok((key, value)) => self.head.headers.append(key, value),
|
2019-04-02 21:51:16 +02:00
|
|
|
Err(e) => self.err = Some(e.into()),
|
2021-01-15 03:11:10 +01:00
|
|
|
};
|
|
|
|
|
2019-03-26 05:58:01 +01:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-04-24 20:27:57 +02:00
|
|
|
/// Send headers in `Camel-Case` form.
|
2019-04-24 19:48:49 +02:00
|
|
|
#[inline]
|
2019-04-24 20:27:57 +02:00
|
|
|
pub fn camel_case(mut self) -> Self {
|
|
|
|
self.head.set_camel_case_headers(true);
|
2019-04-24 19:48:49 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-04-02 23:08:30 +02:00
|
|
|
/// Force close connection instead of returning it back to connections pool.
|
2021-02-11 23:39:54 +01:00
|
|
|
/// This setting affect only HTTP/1 connections.
|
2019-03-26 05:58:01 +01:00
|
|
|
#[inline]
|
2019-04-02 23:08:30 +02:00
|
|
|
pub fn force_close(mut self) -> Self {
|
2019-04-02 21:51:16 +02:00
|
|
|
self.head.set_connection_type(ConnectionType::Close);
|
2019-03-26 05:58:01 +01:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set request's content type
|
|
|
|
#[inline]
|
2019-04-02 21:51:16 +02:00
|
|
|
pub fn content_type<V>(mut self, value: V) -> Self
|
2019-03-26 05:58:01 +01:00
|
|
|
where
|
2019-12-05 18:35:43 +01:00
|
|
|
HeaderValue: TryFrom<V>,
|
|
|
|
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
|
2019-03-26 05:58:01 +01:00
|
|
|
{
|
2019-04-02 21:51:16 +02:00
|
|
|
match HeaderValue::try_from(value) {
|
2021-02-09 23:59:17 +01:00
|
|
|
Ok(value) => {
|
|
|
|
self.head.headers.insert(header::CONTENT_TYPE, value);
|
|
|
|
}
|
2019-04-02 21:51:16 +02:00
|
|
|
Err(e) => self.err = Some(e.into()),
|
2019-03-26 05:58:01 +01:00
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set content length
|
|
|
|
#[inline]
|
2019-04-02 21:51:16 +02:00
|
|
|
pub fn content_length(self, len: u64) -> Self {
|
2021-03-07 20:29:02 +01:00
|
|
|
let mut buf = itoa::Buffer::new();
|
|
|
|
self.insert_header((header::CONTENT_LENGTH, buf.format(len)))
|
2019-03-26 05:58:01 +01:00
|
|
|
}
|
|
|
|
|
2021-03-07 20:29:02 +01:00
|
|
|
/// Set HTTP basic authorization header.
|
|
|
|
///
|
|
|
|
/// If no password is needed, just provide an empty string.
|
|
|
|
pub fn basic_auth(self, username: impl fmt::Display, password: impl fmt::Display) -> Self {
|
|
|
|
let auth = format!("{}:{}", username, password);
|
|
|
|
|
|
|
|
self.insert_header((
|
2019-03-27 04:57:06 +01:00
|
|
|
header::AUTHORIZATION,
|
|
|
|
format!("Basic {}", base64::encode(&auth)),
|
2021-01-15 03:11:10 +01:00
|
|
|
))
|
2019-03-27 04:57:06 +01:00
|
|
|
}
|
|
|
|
|
2019-03-27 05:31:18 +01:00
|
|
|
/// Set HTTP bearer authentication header
|
2021-03-07 20:29:02 +01:00
|
|
|
pub fn bearer_auth(self, token: impl fmt::Display) -> Self {
|
|
|
|
self.insert_header((header::AUTHORIZATION, format!("Bearer {}", token)))
|
2019-03-27 04:57:06 +01:00
|
|
|
}
|
|
|
|
|
2019-03-26 05:58:01 +01:00
|
|
|
/// Set a cookie
|
|
|
|
///
|
2021-03-25 09:45:52 +01:00
|
|
|
/// ```
|
2019-11-26 06:25:50 +01:00
|
|
|
/// #[actix_rt::main]
|
|
|
|
/// async fn main() {
|
|
|
|
/// let resp = awc::Client::new().get("https://www.rust-lang.org")
|
|
|
|
/// .cookie(
|
2021-04-09 19:07:10 +02:00
|
|
|
/// awc::cookie::Cookie::build("name", "value")
|
2019-11-26 06:25:50 +01:00
|
|
|
/// .domain("www.rust-lang.org")
|
|
|
|
/// .path("/")
|
|
|
|
/// .secure(true)
|
|
|
|
/// .http_only(true)
|
|
|
|
/// .finish(),
|
|
|
|
/// )
|
|
|
|
/// .send()
|
|
|
|
/// .await;
|
|
|
|
///
|
|
|
|
/// println!("Response: {:?}", resp);
|
2019-03-26 05:58:01 +01:00
|
|
|
/// }
|
|
|
|
/// ```
|
2021-02-13 16:08:43 +01:00
|
|
|
#[cfg(feature = "cookies")]
|
2019-07-17 11:08:30 +02:00
|
|
|
pub fn cookie(mut self, cookie: Cookie<'_>) -> 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
|
|
|
|
}
|
|
|
|
|
2019-03-27 04:45:00 +01:00
|
|
|
/// Disable automatic decompress of response's body
|
2019-04-02 21:51:16 +02:00
|
|
|
pub fn no_decompress(mut self) -> 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.
|
2019-04-02 21:51:16 +02:00
|
|
|
pub fn timeout(mut self, timeout: Duration) -> Self {
|
2019-03-29 22:07:37 +01:00
|
|
|
self.timeout = Some(timeout);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-10-26 07:27:14 +02:00
|
|
|
/// Sets the query part of the request
|
|
|
|
pub fn query<T: Serialize>(
|
|
|
|
mut self,
|
|
|
|
query: &T,
|
|
|
|
) -> Result<Self, serde_urlencoded::ser::Error> {
|
|
|
|
let mut parts = self.head.uri.clone().into_parts();
|
|
|
|
|
|
|
|
if let Some(path_and_query) = parts.path_and_query {
|
|
|
|
let query = serde_urlencoded::to_string(query)?;
|
|
|
|
let path = path_and_query.path();
|
|
|
|
parts.path_and_query = format!("{}?{}", path, query).parse().ok();
|
|
|
|
|
|
|
|
match Uri::from_parts(parts) {
|
|
|
|
Ok(uri) => self.head.uri = uri,
|
|
|
|
Err(e) => self.err = Some(e.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(self)
|
|
|
|
}
|
|
|
|
|
2019-09-12 06:40:56 +02:00
|
|
|
/// Freeze request builder and construct `FrozenClientRequest`,
|
|
|
|
/// which could be used for sending same request multiple times.
|
2019-09-10 06:29:32 +02: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-26 05:58:01 +01:00
|
|
|
/// Complete request construction and send body.
|
2019-09-12 06:40:56 +02:00
|
|
|
pub fn send_body<B>(self, body: B) -> SendClientRequest
|
2019-03-26 05:58:01 +01:00
|
|
|
where
|
2021-11-16 23:10:30 +01:00
|
|
|
B: Into<AnyBody>,
|
2019-03-26 05:58:01 +01:00
|
|
|
{
|
2019-09-10 06:29:32 +02:00
|
|
|
let slf = match self.prep_for_sending() {
|
|
|
|
Ok(slf) => slf,
|
|
|
|
Err(e) => return e.into(),
|
|
|
|
};
|
|
|
|
|
2019-09-12 06:40:56 +02:00
|
|
|
RequestSender::Owned(slf.head).send_body(
|
|
|
|
slf.addr,
|
|
|
|
slf.response_decompress,
|
|
|
|
slf.timeout,
|
2021-03-18 18:53:22 +01:00
|
|
|
&slf.config,
|
2019-09-12 06:40:56 +02:00
|
|
|
body,
|
|
|
|
)
|
2019-09-10 06:29:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Set a JSON body and generate `ClientRequest`
|
2019-09-12 06:40:56 +02:00
|
|
|
pub fn send_json<T: Serialize>(self, value: &T) -> SendClientRequest {
|
2019-09-10 06:29:32 +02:00
|
|
|
let slf = match self.prep_for_sending() {
|
|
|
|
Ok(slf) => slf,
|
|
|
|
Err(e) => return e.into(),
|
|
|
|
};
|
|
|
|
|
2019-09-12 06:40:56 +02:00
|
|
|
RequestSender::Owned(slf.head).send_json(
|
|
|
|
slf.addr,
|
|
|
|
slf.response_decompress,
|
|
|
|
slf.timeout,
|
2021-03-18 18:53:22 +01:00
|
|
|
&slf.config,
|
2019-09-12 06:40:56 +02:00
|
|
|
value,
|
|
|
|
)
|
2019-09-10 06:29:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Set a urlencoded body and generate `ClientRequest`
|
|
|
|
///
|
|
|
|
/// `ClientRequestBuilder` can not be used after this call.
|
2019-09-12 06:40:56 +02:00
|
|
|
pub fn send_form<T: Serialize>(self, value: &T) -> SendClientRequest {
|
2019-09-10 06:29:32 +02:00
|
|
|
let slf = match self.prep_for_sending() {
|
|
|
|
Ok(slf) => slf,
|
|
|
|
Err(e) => return e.into(),
|
|
|
|
};
|
|
|
|
|
2019-09-12 06:40:56 +02:00
|
|
|
RequestSender::Owned(slf.head).send_form(
|
|
|
|
slf.addr,
|
|
|
|
slf.response_decompress,
|
|
|
|
slf.timeout,
|
2021-03-18 18:53:22 +01:00
|
|
|
&slf.config,
|
2019-09-12 06:40:56 +02:00
|
|
|
value,
|
|
|
|
)
|
2019-09-10 06:29:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Set an streaming body and generate `ClientRequest`.
|
2019-09-12 06:40:56 +02:00
|
|
|
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
2019-09-10 06:29:32 +02:00
|
|
|
where
|
2019-11-19 04:55:17 +01:00
|
|
|
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
2021-06-17 18:57:58 +02:00
|
|
|
E: Into<Box<dyn StdError>> + 'static,
|
2019-09-10 06:29:32 +02:00
|
|
|
{
|
|
|
|
let slf = match self.prep_for_sending() {
|
|
|
|
Ok(slf) => slf,
|
|
|
|
Err(e) => return e.into(),
|
|
|
|
};
|
|
|
|
|
2019-09-12 06:40:56 +02:00
|
|
|
RequestSender::Owned(slf.head).send_stream(
|
|
|
|
slf.addr,
|
|
|
|
slf.response_decompress,
|
|
|
|
slf.timeout,
|
2021-03-18 18:53:22 +01:00
|
|
|
&slf.config,
|
2019-09-12 06:40:56 +02:00
|
|
|
stream,
|
|
|
|
)
|
2019-09-10 06:29:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Set an empty body and generate `ClientRequest`.
|
2019-09-12 06:40:56 +02:00
|
|
|
pub fn send(self) -> SendClientRequest {
|
2019-09-10 06:29:32 +02:00
|
|
|
let slf = match self.prep_for_sending() {
|
|
|
|
Ok(slf) => slf,
|
|
|
|
Err(e) => return e.into(),
|
|
|
|
};
|
|
|
|
|
2019-09-12 06:40:56 +02:00
|
|
|
RequestSender::Owned(slf.head).send(
|
|
|
|
slf.addr,
|
|
|
|
slf.response_decompress,
|
|
|
|
slf.timeout,
|
2021-03-18 18:53:22 +01:00
|
|
|
&slf.config,
|
2019-09-12 06:40:56 +02:00
|
|
|
)
|
2019-09-10 06:29:32 +02:00
|
|
|
}
|
|
|
|
|
2021-02-13 16:08:43 +01:00
|
|
|
// allow unused mut when cookies feature is disabled
|
|
|
|
fn prep_for_sending(#[allow(unused_mut)] mut self) -> Result<Self, PrepForSendingError> {
|
2019-09-10 06:29:32 +02:00
|
|
|
if let Some(e) = self.err {
|
|
|
|
return Err(e.into());
|
2019-03-26 05:58:01 +01:00
|
|
|
}
|
|
|
|
|
2019-03-27 04:45:00 +01:00
|
|
|
// validate uri
|
2019-04-02 21:51:16 +02:00
|
|
|
let uri = &self.head.uri;
|
2019-03-27 04:45:00 +01:00
|
|
|
if uri.host().is_none() {
|
2019-09-10 06:29:32 +02:00
|
|
|
return Err(InvalidUrl::MissingHost.into());
|
2019-12-05 18:35:43 +01:00
|
|
|
} else if uri.scheme().is_none() {
|
2019-09-10 06:29:32 +02:00
|
|
|
return Err(InvalidUrl::MissingScheme.into());
|
2019-12-05 18:35:43 +01:00
|
|
|
} else if let Some(scheme) = uri.scheme() {
|
2019-03-27 04:45:00 +01:00
|
|
|
match scheme.as_str() {
|
2021-01-04 02:01:35 +01:00
|
|
|
"http" | "ws" | "https" | "wss" => {}
|
2019-09-10 06:29:32 +02:00
|
|
|
_ => return Err(InvalidUrl::UnknownScheme.into()),
|
2019-03-27 04:45:00 +01:00
|
|
|
}
|
|
|
|
} else {
|
2019-09-10 06:29:32 +02:00
|
|
|
return Err(InvalidUrl::UnknownScheme.into());
|
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
|
2021-02-13 16:08:43 +01:00
|
|
|
#[cfg(feature = "cookies")]
|
2019-04-02 00:19:34 +02:00
|
|
|
if let Some(ref mut jar) = self.cookies {
|
2020-06-19 15:34:14 +02:00
|
|
|
let cookie: String = jar
|
|
|
|
.delta()
|
|
|
|
// ensure only name=value is written to cookie header
|
2021-04-09 19:07:10 +02:00
|
|
|
.map(|c| c.stripped().encoded().to_string())
|
2020-06-19 15:34:14 +02:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join("; ");
|
|
|
|
|
|
|
|
if !cookie.is_empty() {
|
|
|
|
self.head
|
|
|
|
.headers
|
|
|
|
.insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap());
|
2019-03-26 05:58:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-08 20:09:57 +02:00
|
|
|
let mut slf = self;
|
2019-04-02 21:51:16 +02:00
|
|
|
|
2021-06-19 21:21:13 +02:00
|
|
|
// Set Accept-Encoding HTTP header depending on enabled feature.
|
|
|
|
// If decompress is not ask, then we are not able to find which encoding is
|
|
|
|
// supported, so we cannot guess Accept-Encoding HTTP header.
|
2019-12-07 16:55:41 +01:00
|
|
|
if slf.response_decompress {
|
2021-06-19 21:21:13 +02:00
|
|
|
// Set Accept-Encoding with compression algorithm awc is built with.
|
2021-06-24 16:11:01 +02:00
|
|
|
#[allow(clippy::vec_init_then_push)]
|
2021-06-19 21:21:13 +02:00
|
|
|
#[cfg(feature = "__compress")]
|
|
|
|
let accept_encoding = {
|
|
|
|
let mut encoding = vec![];
|
|
|
|
|
|
|
|
#[cfg(feature = "compress-brotli")]
|
2021-06-26 16:33:43 +02:00
|
|
|
{
|
|
|
|
encoding.push("br");
|
|
|
|
}
|
2021-06-19 21:21:13 +02:00
|
|
|
|
|
|
|
#[cfg(feature = "compress-gzip")]
|
2019-12-07 16:55:41 +01:00
|
|
|
{
|
2021-06-19 21:21:13 +02:00
|
|
|
encoding.push("gzip");
|
|
|
|
encoding.push("deflate");
|
2019-12-07 16:55:41 +01:00
|
|
|
}
|
2021-06-19 21:21:13 +02:00
|
|
|
|
|
|
|
#[cfg(feature = "compress-zstd")]
|
|
|
|
encoding.push("zstd");
|
|
|
|
|
2021-06-24 16:11:01 +02:00
|
|
|
assert!(
|
|
|
|
!encoding.is_empty(),
|
|
|
|
"encoding can not be empty unless __compress feature has been explicitly enabled"
|
|
|
|
);
|
|
|
|
|
2021-06-19 21:21:13 +02:00
|
|
|
encoding.join(", ")
|
2019-12-07 16:55:41 +01:00
|
|
|
};
|
2021-06-19 21:21:13 +02:00
|
|
|
|
|
|
|
// Otherwise tell the server, we do not support any compression algorithm.
|
|
|
|
// So we clearly indicate that we do want identity encoding.
|
|
|
|
#[cfg(not(feature = "__compress"))]
|
|
|
|
let accept_encoding = "identity";
|
|
|
|
|
|
|
|
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, accept_encoding));
|
2019-04-08 20:09:57 +02:00
|
|
|
}
|
2019-04-02 21:51:16 +02:00
|
|
|
|
2019-09-10 06:29:32 +02:00
|
|
|
Ok(slf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for ClientRequest {
|
2019-12-08 07:31:16 +01:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2019-09-10 06:29:32 +02:00
|
|
|
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(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-29 05:48:35 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2019-04-15 05:20:33 +02:00
|
|
|
use std::time::SystemTime;
|
|
|
|
|
2021-04-01 17:42:18 +02:00
|
|
|
use actix_http::http::header::HttpDate;
|
|
|
|
|
2019-03-29 05:48:35 +01:00
|
|
|
use super::*;
|
2019-04-14 16:43:53 +02:00
|
|
|
use crate::Client;
|
2019-03-29 05:48:35 +01:00
|
|
|
|
2020-07-10 23:35:22 +02:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_debug() {
|
2021-01-15 03:11:10 +01:00
|
|
|
let request = Client::new().get("/").append_header(("x-test", "111"));
|
2019-04-14 16:43:53 +02:00
|
|
|
let repr = format!("{:?}", request);
|
|
|
|
assert!(repr.contains("ClientRequest"));
|
|
|
|
assert!(repr.contains("x-test"));
|
2019-03-29 05:48:35 +01:00
|
|
|
}
|
|
|
|
|
2020-07-10 23:35:22 +02:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_basics() {
|
2020-10-30 03:10:05 +01:00
|
|
|
let req = Client::new()
|
2019-04-15 05:20:33 +02:00
|
|
|
.put("/")
|
|
|
|
.version(Version::HTTP_2)
|
2021-04-01 17:42:18 +02:00
|
|
|
.insert_header((header::DATE, HttpDate::from(SystemTime::now())))
|
2019-04-15 05:20:33 +02:00
|
|
|
.content_type("plain/text")
|
2021-01-15 03:11:10 +01:00
|
|
|
.append_header((header::SERVER, "awc"));
|
2020-10-30 03:10:05 +01:00
|
|
|
|
|
|
|
let req = if let Some(val) = Some("server") {
|
2021-01-15 03:11:10 +01:00
|
|
|
req.append_header((header::USER_AGENT, val))
|
2020-10-30 03:10:05 +01:00
|
|
|
} else {
|
|
|
|
req
|
|
|
|
};
|
|
|
|
|
|
|
|
let req = if let Some(_val) = Option::<&str>::None {
|
2021-01-15 03:11:10 +01:00
|
|
|
req.append_header((header::ALLOW, "1"))
|
2020-10-30 03:10:05 +01:00
|
|
|
} else {
|
|
|
|
req
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut req = req.content_length(100);
|
|
|
|
|
2019-04-15 05:20:33 +02:00
|
|
|
assert!(req.headers().contains_key(header::CONTENT_TYPE));
|
|
|
|
assert!(req.headers().contains_key(header::DATE));
|
2019-04-19 06:28:23 +02: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-15 05:20:33 +02:00
|
|
|
assert_eq!(req.head.version, Version::HTTP_2);
|
2020-10-30 03:10:05 +01:00
|
|
|
|
2019-04-15 05:20:33 +02:00
|
|
|
let _ = req.headers_mut();
|
|
|
|
let _ = req.send_body("");
|
|
|
|
}
|
|
|
|
|
2020-07-10 23:35:22 +02:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_client_header() {
|
2020-09-11 10:24:39 +02:00
|
|
|
let req = Client::builder()
|
2019-04-14 16:43:53 +02:00
|
|
|
.header(header::CONTENT_TYPE, "111")
|
|
|
|
.finish()
|
|
|
|
.get("/");
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
req.head
|
|
|
|
.headers
|
|
|
|
.get(header::CONTENT_TYPE)
|
|
|
|
.unwrap()
|
|
|
|
.to_str()
|
|
|
|
.unwrap(),
|
|
|
|
"111"
|
|
|
|
);
|
2019-03-29 05:48:35 +01:00
|
|
|
}
|
|
|
|
|
2020-07-10 23:35:22 +02:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_client_header_override() {
|
2020-09-11 10:24:39 +02:00
|
|
|
let req = Client::builder()
|
2019-04-14 16:43:53 +02:00
|
|
|
.header(header::CONTENT_TYPE, "111")
|
|
|
|
.finish()
|
|
|
|
.get("/")
|
2021-01-15 03:11:10 +01:00
|
|
|
.insert_header((header::CONTENT_TYPE, "222"));
|
2019-04-14 16:43:53 +02:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
req.head
|
|
|
|
.headers
|
|
|
|
.get(header::CONTENT_TYPE)
|
|
|
|
.unwrap()
|
|
|
|
.to_str()
|
|
|
|
.unwrap(),
|
|
|
|
"222"
|
|
|
|
);
|
2019-03-29 05:48:35 +01:00
|
|
|
}
|
|
|
|
|
2020-07-10 23:35:22 +02:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn client_basic_auth() {
|
2021-03-07 20:29:02 +01:00
|
|
|
let req = Client::new().get("/").basic_auth("username", "password");
|
2019-04-14 16:43:53 +02:00
|
|
|
assert_eq!(
|
|
|
|
req.head
|
|
|
|
.headers
|
|
|
|
.get(header::AUTHORIZATION)
|
|
|
|
.unwrap()
|
|
|
|
.to_str()
|
|
|
|
.unwrap(),
|
|
|
|
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
|
|
|
|
);
|
2019-03-29 05:48:35 +01:00
|
|
|
|
2021-03-07 20:29:02 +01:00
|
|
|
let req = Client::new().get("/").basic_auth("username", "");
|
2019-04-14 16:43:53 +02:00
|
|
|
assert_eq!(
|
|
|
|
req.head
|
|
|
|
.headers
|
|
|
|
.get(header::AUTHORIZATION)
|
|
|
|
.unwrap()
|
|
|
|
.to_str()
|
|
|
|
.unwrap(),
|
2019-07-01 05:34:42 +02:00
|
|
|
"Basic dXNlcm5hbWU6"
|
2019-04-14 16:43:53 +02:00
|
|
|
);
|
2019-03-29 05:48:35 +01:00
|
|
|
}
|
|
|
|
|
2020-07-10 23:35:22 +02:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn client_bearer_auth() {
|
2019-04-14 16:43:53 +02:00
|
|
|
let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n");
|
|
|
|
assert_eq!(
|
|
|
|
req.head
|
|
|
|
.headers
|
|
|
|
.get(header::AUTHORIZATION)
|
|
|
|
.unwrap()
|
|
|
|
.to_str()
|
|
|
|
.unwrap(),
|
|
|
|
"Bearer someS3cr3tAutht0k3n"
|
|
|
|
);
|
2019-03-29 05:48:35 +01:00
|
|
|
}
|
2019-10-26 07:27:14 +02:00
|
|
|
|
2020-07-10 23:35:22 +02:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn client_query() {
|
2019-10-26 07:27:14 +02:00
|
|
|
let req = Client::new()
|
|
|
|
.get("/")
|
|
|
|
.query(&[("key1", "val1"), ("key2", "val2")])
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(req.get_uri().query().unwrap(), "key1=val1&key2=val2");
|
|
|
|
}
|
2019-03-29 05:48:35 +01:00
|
|
|
}
|