1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-30 18:44:35 +01:00
actix-web/awc/src/request.rs

682 lines
19 KiB
Rust
Raw Normal View History

2019-12-05 18:35:43 +01:00
use std::convert::TryFrom;
2019-03-26 05:58:01 +01:00
use std::rc::Rc;
use std::time::Duration;
use std::{fmt, net};
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;
use actix_http::body::Body;
2021-02-13 16:08:43 +01:00
#[cfg(feature = "cookies")]
use actix_http::cookie::{Cookie, CookieJar};
2021-01-15 03:11:10 +01:00
use actix_http::http::header::{self, IntoHeaderPair};
2019-03-26 05:58:01 +01:00
use actix_http::http::{
2021-02-12 00:03:17 +01:00
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version,
2019-03-26 05:58:01 +01:00
};
use actix_http::{Error, RequestHead};
2019-03-26 05:58:01 +01:00
use crate::error::{FreezeRequestError, InvalidUrl};
use crate::frozen::FrozenClientRequest;
use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest};
2019-03-29 06:33:41 +01:00
use crate::ClientConfig;
2019-03-27 04:45:00 +01:00
2021-03-25 23:47:37 +01:00
#[cfg(feature = "compress")]
const HTTPS_ENCODING: &str = "br, gzip, deflate";
#[cfg(not(feature = "compress"))]
const HTTPS_ENCODING: &str = "br";
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.
///
/// ```
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>,
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>,
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.
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,
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
}
/// 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-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
}
/// Get HTTP method of this request
pub fn get_method(&self) -> &Method {
&self.head.method
}
2019-03-26 05:58:01 +01:00
#[doc(hidden)]
/// Set HTTP version of this request.
///
/// By default requests's HTTP version depends on network stream
#[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
}
/// 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-01-15 03:11:10 +01:00
/// # #[actix_rt::main]
/// # async fn main() {
/// # use awc::Client;
/// use awc::http::header::ContentType;
///
/// Client::new()
/// .get("http://www.rust-lang.org")
/// .insert_header(("X-TEST", "value"))
/// .insert_header(ContentType(mime::APPLICATION_JSON));
/// # }
/// ```
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
}
/// 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 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 {
let mut buf = itoa::Buffer::new();
self.insert_header((header::CONTENT_LENGTH, buf.format(len)))
2019-03-26 05:58:01 +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((
header::AUTHORIZATION,
format!("Basic {}", base64::encode(&auth)),
2021-01-15 03:11:10 +01:00
))
}
2019-03-27 05:31:18 +01:00
/// Set HTTP bearer authentication header
pub fn bearer_auth(self, token: impl fmt::Display) -> Self {
self.insert_header((header::AUTHORIZATION, format!("Bearer {}", token)))
}
2019-03-26 05:58:01 +01:00
/// Set a cookie
///
/// ```
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(
/// awc::http::Cookie::build("name", "value")
/// .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
}
2020-10-30 03:10:05 +01:00
/// This method calls provided closure with builder reference if value is `true`.
#[doc(hidden)]
#[deprecated = "Use an if statement."]
2019-04-17 19:28:27 +02:00
pub fn if_true<F>(self, value: bool, f: F) -> Self
2019-03-26 05:58:01 +01:00
where
F: FnOnce(ClientRequest) -> ClientRequest,
2019-03-26 05:58:01 +01:00
{
if value {
f(self)
} else {
self
2019-03-26 05:58:01 +01:00
}
}
2020-10-30 03:10:05 +01:00
/// This method calls provided closure with builder reference if value is `Some`.
#[doc(hidden)]
#[deprecated = "Use an if-let construction."]
2019-04-17 19:28:27 +02:00
pub fn if_some<T, F>(self, value: Option<T>, f: F) -> Self
2019-03-26 05:58:01 +01:00
where
F: FnOnce(T, ClientRequest) -> ClientRequest,
2019-03-26 05:58:01 +01:00
{
if let Some(val) = value {
f(val, self)
} else {
self
2019-03-26 05:58:01 +01: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)
}
/// Freeze request builder and construct `FrozenClientRequest`,
/// which could be used for sending same request multiple times.
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.
pub fn send_body<B>(self, body: B) -> SendClientRequest
2019-03-26 05:58:01 +01: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,
body,
)
}
/// Set a JSON body and generate `ClientRequest`
pub fn send_json<T: Serialize>(self, value: &T) -> SendClientRequest {
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,
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) -> SendClientRequest {
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,
value,
)
}
/// Set an streaming body and generate `ClientRequest`.
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
where
S: Stream<Item = Result<Bytes, E>> + Unpin + '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,
stream,
)
}
/// Set an empty body and generate `ClientRequest`.
pub fn send(self) -> SendClientRequest {
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,
)
}
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> {
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() {
return Err(InvalidUrl::MissingHost.into());
2019-12-05 18:35:43 +01:00
} else if uri.scheme().is_none() {
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" => {}
_ => return Err(InvalidUrl::UnknownScheme.into()),
2019-03-27 04:45:00 +01:00
}
} else {
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")]
if let Some(ref mut jar) = self.cookies {
let cookie: String = jar
.delta()
// ensure only name=value is written to cookie header
.map(|c| Cookie::new(c.name(), c.value()).encoded().to_string())
.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
if slf.response_decompress {
let https = slf
.head
.uri
.scheme()
.map(|s| s == &uri::Scheme::HTTPS)
.unwrap_or(true);
if https {
2021-03-25 23:47:37 +01:00
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING));
} else {
2021-03-25 23:47:37 +01:00
#[cfg(feature = "compress")]
{
2021-03-25 23:47:37 +01:00
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate"));
}
};
2019-04-08 20:09:57 +02:00
}
2019-04-02 21:51:16 +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 {
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(())
}
}
#[cfg(test)]
mod tests {
2019-04-15 05:20:33 +02:00
use std::time::SystemTime;
use super::*;
use crate::Client;
#[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"));
let repr = format!("{:?}", request);
assert!(repr.contains("ClientRequest"));
assert!(repr.contains("x-test"));
}
#[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-01-15 03:11:10 +01:00
.insert_header(header::Date(SystemTime::now().into()))
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("");
}
#[actix_rt::test]
async fn test_client_header() {
let req = Client::builder()
.header(header::CONTENT_TYPE, "111")
.finish()
.get("/");
assert_eq!(
req.head
.headers
.get(header::CONTENT_TYPE)
.unwrap()
.to_str()
.unwrap(),
"111"
);
}
#[actix_rt::test]
async fn test_client_header_override() {
let req = Client::builder()
.header(header::CONTENT_TYPE, "111")
.finish()
.get("/")
2021-01-15 03:11:10 +01:00
.insert_header((header::CONTENT_TYPE, "222"));
assert_eq!(
req.head
.headers
.get(header::CONTENT_TYPE)
.unwrap()
.to_str()
.unwrap(),
"222"
);
}
#[actix_rt::test]
async fn client_basic_auth() {
let req = Client::new().get("/").basic_auth("username", "password");
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
);
let req = Client::new().get("/").basic_auth("username", "");
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Basic dXNlcm5hbWU6"
);
}
#[actix_rt::test]
async 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"
);
}
#[actix_rt::test]
async fn client_query() {
let req = Client::new()
.get("/")
.query(&[("key1", "val1"), ("key2", "val2")])
.unwrap();
assert_eq!(req.get_uri().query().unwrap(), "key1=val1&key2=val2");
}
}