2018-03-08 20:16:54 +01:00
|
|
|
use std::fmt::Write as FmtWrite;
|
2018-01-29 20:39:26 +01:00
|
|
|
use std::io::Write;
|
2018-03-19 03:27:51 +01:00
|
|
|
use std::time::Duration;
|
2018-04-14 01:02:01 +02:00
|
|
|
use std::{fmt, mem};
|
2018-01-29 20:39:26 +01:00
|
|
|
|
2018-05-27 14:02:49 +02:00
|
|
|
use actix::Addr;
|
2018-04-14 01:02:01 +02:00
|
|
|
use bytes::{BufMut, Bytes, BytesMut};
|
2018-01-29 20:39:26 +01:00
|
|
|
use cookie::{Cookie, CookieJar};
|
2018-03-22 03:14:18 +01:00
|
|
|
use futures::Stream;
|
2018-04-14 01:02:01 +02:00
|
|
|
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
|
2018-01-29 20:39:26 +01:00
|
|
|
use serde::Serialize;
|
2018-04-14 01:02:01 +02:00
|
|
|
use serde_json;
|
2018-03-29 01:10:58 +02:00
|
|
|
use url::Url;
|
2018-01-29 20:39:26 +01:00
|
|
|
|
2018-04-14 01:02:01 +02:00
|
|
|
use super::connector::{ClientConnector, Connection};
|
|
|
|
use super::pipeline::SendRequest;
|
2018-01-29 20:39:26 +01:00
|
|
|
use body::Body;
|
|
|
|
use error::Error;
|
2018-03-07 00:18:04 +01:00
|
|
|
use header::{ContentEncoding, Header, IntoHeaderValue};
|
2018-04-14 01:02:01 +02:00
|
|
|
use http::header::{self, HeaderName, HeaderValue};
|
|
|
|
use http::{uri, Error as HttpError, HeaderMap, HttpTryFrom, Method, Uri, Version};
|
2018-03-22 03:54:21 +01:00
|
|
|
use httpmessage::HttpMessage;
|
|
|
|
use httprequest::HttpRequest;
|
2018-01-29 20:39:26 +01:00
|
|
|
|
2018-01-29 23:44:25 +01:00
|
|
|
/// An HTTP Client Request
|
2018-04-06 19:09:31 +02:00
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # extern crate actix;
|
|
|
|
/// # extern crate actix_web;
|
|
|
|
/// # extern crate futures;
|
|
|
|
/// # use futures::Future;
|
|
|
|
/// use actix_web::client::ClientRequest;
|
|
|
|
///
|
|
|
|
/// fn main() {
|
|
|
|
/// let sys = actix::System::new("test");
|
|
|
|
///
|
2018-05-25 06:03:16 +02:00
|
|
|
/// actix::Arbiter::spawn({
|
2018-04-06 19:09:31 +02:00
|
|
|
/// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
|
|
|
|
/// .header("User-Agent", "Actix-web")
|
|
|
|
/// .finish().unwrap()
|
|
|
|
/// .send() // <- Send http request
|
|
|
|
/// .map_err(|_| ())
|
|
|
|
/// .and_then(|response| { // <- server http response
|
|
|
|
/// println!("Response: {:?}", response);
|
|
|
|
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
|
|
/// Ok(())
|
|
|
|
/// })
|
|
|
|
/// });
|
|
|
|
///
|
|
|
|
/// sys.run();
|
|
|
|
/// }
|
|
|
|
/// ```
|
2018-01-29 20:39:26 +01:00
|
|
|
pub struct ClientRequest {
|
|
|
|
uri: Uri,
|
|
|
|
method: Method,
|
|
|
|
version: Version,
|
|
|
|
headers: HeaderMap,
|
|
|
|
body: Body,
|
2018-02-19 12:11:11 +01:00
|
|
|
chunked: bool,
|
|
|
|
upgrade: bool,
|
2018-03-19 03:27:51 +01:00
|
|
|
timeout: Option<Duration>,
|
2018-01-29 20:39:26 +01:00
|
|
|
encoding: ContentEncoding,
|
2018-02-24 05:29:35 +01:00
|
|
|
response_decompress: bool,
|
2018-03-09 19:09:13 +01:00
|
|
|
buffer_capacity: usize,
|
2018-02-28 00:14:33 +01:00
|
|
|
conn: ConnectionType,
|
|
|
|
}
|
|
|
|
|
|
|
|
enum ConnectionType {
|
|
|
|
Default,
|
2018-05-27 14:02:49 +02:00
|
|
|
Connector(Addr<ClientConnector>),
|
2018-02-28 00:14:33 +01:00
|
|
|
Connection(Connection),
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for ClientRequest {
|
|
|
|
fn default() -> ClientRequest {
|
|
|
|
ClientRequest {
|
|
|
|
uri: Uri::default(),
|
|
|
|
method: Method::default(),
|
|
|
|
version: Version::HTTP_11,
|
|
|
|
headers: HeaderMap::with_capacity(16),
|
|
|
|
body: Body::Empty,
|
2018-02-19 12:11:11 +01:00
|
|
|
chunked: false,
|
|
|
|
upgrade: false,
|
2018-03-19 03:27:51 +01:00
|
|
|
timeout: None,
|
2018-01-29 20:39:26 +01:00
|
|
|
encoding: ContentEncoding::Auto,
|
2018-02-24 05:29:35 +01:00
|
|
|
response_decompress: true,
|
2018-03-09 19:09:13 +01:00
|
|
|
buffer_capacity: 32_768,
|
2018-02-28 00:14:33 +01:00
|
|
|
conn: ConnectionType::Default,
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-29 23:44:25 +01:00
|
|
|
impl ClientRequest {
|
|
|
|
/// Create request builder for `GET` request
|
2018-03-29 01:10:58 +02:00
|
|
|
pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
2018-01-29 23:44:25 +01:00
|
|
|
let mut builder = ClientRequest::build();
|
|
|
|
builder.method(Method::GET).uri(uri);
|
|
|
|
builder
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create request builder for `HEAD` request
|
2018-03-29 01:10:58 +02:00
|
|
|
pub fn head<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
2018-01-29 23:44:25 +01:00
|
|
|
let mut builder = ClientRequest::build();
|
|
|
|
builder.method(Method::HEAD).uri(uri);
|
|
|
|
builder
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create request builder for `POST` request
|
2018-03-29 01:10:58 +02:00
|
|
|
pub fn post<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
2018-01-29 23:44:25 +01:00
|
|
|
let mut builder = ClientRequest::build();
|
|
|
|
builder.method(Method::POST).uri(uri);
|
|
|
|
builder
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create request builder for `PUT` request
|
2018-03-29 01:10:58 +02:00
|
|
|
pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
2018-01-29 23:44:25 +01:00
|
|
|
let mut builder = ClientRequest::build();
|
|
|
|
builder.method(Method::PUT).uri(uri);
|
|
|
|
builder
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create request builder for `DELETE` request
|
2018-03-29 01:10:58 +02:00
|
|
|
pub fn delete<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
2018-01-29 23:44:25 +01:00
|
|
|
let mut builder = ClientRequest::build();
|
|
|
|
builder.method(Method::DELETE).uri(uri);
|
|
|
|
builder
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-29 20:39:26 +01:00
|
|
|
impl ClientRequest {
|
|
|
|
/// Create client request builder
|
|
|
|
pub fn build() -> ClientRequestBuilder {
|
|
|
|
ClientRequestBuilder {
|
|
|
|
request: Some(ClientRequest::default()),
|
|
|
|
err: None,
|
|
|
|
cookies: None,
|
2018-04-14 01:02:01 +02:00
|
|
|
default_headers: true,
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-22 03:54:21 +01:00
|
|
|
/// Create client request builder
|
|
|
|
pub fn build_from<T: Into<ClientRequestBuilder>>(source: T) -> ClientRequestBuilder {
|
|
|
|
source.into()
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:00:57 +02:00
|
|
|
/// Get the request URI
|
2018-01-29 20:39:26 +01:00
|
|
|
#[inline]
|
|
|
|
pub fn uri(&self) -> &Uri {
|
|
|
|
&self.uri
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:00:57 +02:00
|
|
|
/// Set client request URI
|
2018-01-29 20:39:26 +01:00
|
|
|
#[inline]
|
|
|
|
pub fn set_uri(&mut self, uri: Uri) {
|
|
|
|
self.uri = uri
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the request method
|
|
|
|
#[inline]
|
|
|
|
pub fn method(&self) -> &Method {
|
|
|
|
&self.method
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:00:57 +02:00
|
|
|
/// Set HTTP `Method` for the request
|
2018-01-29 20:39:26 +01:00
|
|
|
#[inline]
|
|
|
|
pub fn set_method(&mut self, method: Method) {
|
|
|
|
self.method = method
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:00:57 +02:00
|
|
|
/// Get HTTP version for the request
|
2018-01-29 23:44:25 +01:00
|
|
|
#[inline]
|
|
|
|
pub fn version(&self) -> Version {
|
|
|
|
self.version
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set http `Version` for the request
|
|
|
|
#[inline]
|
|
|
|
pub fn set_version(&mut self, version: Version) {
|
|
|
|
self.version = version
|
|
|
|
}
|
|
|
|
|
2018-01-29 20:39:26 +01:00
|
|
|
/// Get the headers from the request
|
|
|
|
#[inline]
|
|
|
|
pub fn headers(&self) -> &HeaderMap {
|
|
|
|
&self.headers
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a mutable reference to the headers
|
|
|
|
#[inline]
|
|
|
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
|
|
|
&mut self.headers
|
|
|
|
}
|
|
|
|
|
2018-02-19 12:11:11 +01:00
|
|
|
/// is chunked encoding enabled
|
|
|
|
#[inline]
|
|
|
|
pub fn chunked(&self) -> bool {
|
|
|
|
self.chunked
|
|
|
|
}
|
|
|
|
|
|
|
|
/// is upgrade request
|
|
|
|
#[inline]
|
|
|
|
pub fn upgrade(&self) -> bool {
|
|
|
|
self.upgrade
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Content encoding
|
|
|
|
#[inline]
|
|
|
|
pub fn content_encoding(&self) -> ContentEncoding {
|
|
|
|
self.encoding
|
|
|
|
}
|
|
|
|
|
2018-02-24 05:29:35 +01:00
|
|
|
/// Decompress response payload
|
|
|
|
#[inline]
|
|
|
|
pub fn response_decompress(&self) -> bool {
|
|
|
|
self.response_decompress
|
|
|
|
}
|
|
|
|
|
2018-03-09 19:09:13 +01:00
|
|
|
/// Requested write buffer capacity
|
|
|
|
pub fn write_buffer_capacity(&self) -> usize {
|
2018-02-27 00:26:27 +01:00
|
|
|
self.buffer_capacity
|
|
|
|
}
|
2018-04-07 17:00:57 +02:00
|
|
|
|
|
|
|
/// Get body of this response
|
2018-01-29 20:39:26 +01:00
|
|
|
#[inline]
|
|
|
|
pub fn body(&self) -> &Body {
|
|
|
|
&self.body
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set a body
|
|
|
|
pub fn set_body<B: Into<Body>>(&mut self, body: B) {
|
|
|
|
self.body = body.into();
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:00:57 +02:00
|
|
|
/// Extract body, replace it with `Empty`
|
2018-02-19 12:11:11 +01:00
|
|
|
pub(crate) fn replace_body(&mut self, body: Body) -> Body {
|
|
|
|
mem::replace(&mut self.body, body)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Send request
|
2018-02-28 00:14:33 +01:00
|
|
|
///
|
2018-04-07 17:00:57 +02:00
|
|
|
/// This method returns a future that resolves to a ClientResponse
|
2018-02-28 00:14:33 +01:00
|
|
|
pub fn send(mut self) -> SendRequest {
|
2018-03-19 03:27:51 +01:00
|
|
|
let timeout = self.timeout.take();
|
|
|
|
let send = match mem::replace(&mut self.conn, ConnectionType::Default) {
|
2018-02-28 00:14:33 +01:00
|
|
|
ConnectionType::Default => SendRequest::new(self),
|
|
|
|
ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn),
|
|
|
|
ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn),
|
2018-03-19 03:27:51 +01:00
|
|
|
};
|
|
|
|
if let Some(timeout) = timeout {
|
|
|
|
send.timeout(timeout)
|
|
|
|
} else {
|
|
|
|
send
|
2018-02-28 00:14:33 +01:00
|
|
|
}
|
2018-02-19 22:41:21 +01:00
|
|
|
}
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for ClientRequest {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2018-04-14 01:02:01 +02:00
|
|
|
let res = writeln!(
|
|
|
|
f,
|
|
|
|
"\nClientRequest {:?} {}:{}",
|
|
|
|
self.version, self.method, self.uri
|
|
|
|
);
|
2018-04-09 23:25:30 +02:00
|
|
|
let _ = writeln!(f, " headers:");
|
2018-03-08 00:41:46 +01:00
|
|
|
for (key, val) in self.headers.iter() {
|
2018-04-09 23:25:30 +02:00
|
|
|
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
res
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-30 21:44:14 +01:00
|
|
|
/// An HTTP Client request builder
|
2018-01-29 23:44:25 +01:00
|
|
|
///
|
|
|
|
/// This type can be used to construct an instance of `ClientRequest` through a
|
|
|
|
/// builder-like pattern.
|
2018-01-29 20:39:26 +01:00
|
|
|
pub struct ClientRequestBuilder {
|
|
|
|
request: Option<ClientRequest>,
|
|
|
|
err: Option<HttpError>,
|
|
|
|
cookies: Option<CookieJar>,
|
2018-04-14 01:02:01 +02:00
|
|
|
default_headers: bool,
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ClientRequestBuilder {
|
2018-04-07 17:00:57 +02:00
|
|
|
/// Set HTTP URI of request.
|
2018-01-29 20:39:26 +01:00
|
|
|
#[inline]
|
2018-03-29 01:10:58 +02:00
|
|
|
pub fn uri<U: AsRef<str>>(&mut self, uri: U) -> &mut Self {
|
|
|
|
match Url::parse(uri.as_ref()) {
|
|
|
|
Ok(url) => self._uri(url.as_str()),
|
|
|
|
Err(_) => self._uri(uri.as_ref()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn _uri(&mut self, url: &str) -> &mut Self {
|
|
|
|
match Uri::try_from(url) {
|
2018-01-29 20:39:26 +01:00
|
|
|
Ok(uri) => {
|
|
|
|
// set request host header
|
|
|
|
if let Some(host) = uri.host() {
|
|
|
|
self.set_header(header::HOST, host);
|
|
|
|
}
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
parts.uri = uri;
|
|
|
|
}
|
2018-04-14 01:02:01 +02:00
|
|
|
}
|
|
|
|
Err(e) => self.err = Some(e.into()),
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set HTTP method of this request.
|
|
|
|
#[inline]
|
|
|
|
pub fn method(&mut self, method: Method) -> &mut Self {
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
parts.method = method;
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-03-07 00:18:04 +01:00
|
|
|
/// Set HTTP method of this request.
|
|
|
|
#[inline]
|
|
|
|
pub fn get_method(&mut self) -> &Method {
|
2018-04-14 01:02:01 +02:00
|
|
|
let parts =
|
|
|
|
parts(&mut self.request, &self.err).expect("cannot reuse request builder");
|
2018-03-07 00:18:04 +01:00
|
|
|
&parts.method
|
|
|
|
}
|
|
|
|
|
2018-01-29 20:39:26 +01:00
|
|
|
/// Set HTTP version of this request.
|
|
|
|
///
|
2018-04-07 17:00:57 +02:00
|
|
|
/// By default requests's HTTP version depends on network stream
|
2018-01-29 20:39:26 +01:00
|
|
|
#[inline]
|
|
|
|
pub fn version(&mut self, version: Version) -> &mut Self {
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
parts.version = version;
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-03-07 00:18:04 +01:00
|
|
|
/// Set a header.
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # extern crate mime;
|
|
|
|
/// # extern crate actix_web;
|
2018-03-07 07:36:34 +01:00
|
|
|
/// # use actix_web::client::*;
|
2018-03-07 00:18:04 +01:00
|
|
|
/// #
|
2018-03-31 02:31:18 +02:00
|
|
|
/// use actix_web::{client, http};
|
2018-03-07 00:18:04 +01:00
|
|
|
///
|
|
|
|
/// fn main() {
|
2018-03-31 02:31:18 +02:00
|
|
|
/// let req = client::ClientRequest::build()
|
|
|
|
/// .set(http::header::Date::now())
|
|
|
|
/// .set(http::header::ContentType(mime::TEXT_HTML))
|
2018-03-07 00:18:04 +01:00
|
|
|
/// .finish().unwrap();
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
#[doc(hidden)]
|
2018-04-14 01:02:01 +02:00
|
|
|
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
|
2018-03-07 00:18:04 +01:00
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
match hdr.try_into() {
|
2018-04-14 01:02:01 +02:00
|
|
|
Ok(value) => {
|
|
|
|
parts.headers.insert(H::name(), value);
|
|
|
|
}
|
2018-03-07 00:18:04 +01:00
|
|
|
Err(e) => self.err = Some(e.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Append a header.
|
2018-01-29 23:44:25 +01:00
|
|
|
///
|
2018-04-07 17:00:57 +02:00
|
|
|
/// Header gets appended to existing header.
|
2018-01-29 23:44:25 +01:00
|
|
|
/// To override header use `set_header()` method.
|
2018-01-29 20:39:26 +01:00
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # extern crate http;
|
|
|
|
/// # extern crate actix_web;
|
|
|
|
/// # use actix_web::client::*;
|
|
|
|
/// #
|
|
|
|
/// use http::header;
|
|
|
|
///
|
|
|
|
/// fn main() {
|
|
|
|
/// let req = ClientRequest::build()
|
|
|
|
/// .header("X-TEST", "value")
|
|
|
|
/// .header(header::CONTENT_TYPE, "application/json")
|
|
|
|
/// .finish().unwrap();
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
2018-04-14 01:02:01 +02:00
|
|
|
where
|
|
|
|
HeaderName: HttpTryFrom<K>,
|
|
|
|
V: IntoHeaderValue,
|
2018-01-29 20:39:26 +01:00
|
|
|
{
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
match HeaderName::try_from(key) {
|
2018-04-14 01:02:01 +02:00
|
|
|
Ok(key) => match value.try_into() {
|
|
|
|
Ok(value) => {
|
|
|
|
parts.headers.append(key, value);
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
2018-04-14 01:02:01 +02:00
|
|
|
Err(e) => self.err = Some(e.into()),
|
2018-01-29 20:39:26 +01:00
|
|
|
},
|
|
|
|
Err(e) => self.err = Some(e.into()),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-03-07 00:18:04 +01:00
|
|
|
/// Set a header.
|
2018-01-29 20:39:26 +01:00
|
|
|
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
2018-04-14 01:02:01 +02:00
|
|
|
where
|
|
|
|
HeaderName: HttpTryFrom<K>,
|
|
|
|
V: IntoHeaderValue,
|
2018-01-29 20:39:26 +01:00
|
|
|
{
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
match HeaderName::try_from(key) {
|
2018-04-14 01:02:01 +02:00
|
|
|
Ok(key) => match value.try_into() {
|
|
|
|
Ok(value) => {
|
|
|
|
parts.headers.insert(key, value);
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
2018-04-14 01:02:01 +02:00
|
|
|
Err(e) => self.err = Some(e.into()),
|
2018-01-29 20:39:26 +01:00
|
|
|
},
|
|
|
|
Err(e) => self.err = Some(e.into()),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set content encoding.
|
|
|
|
///
|
|
|
|
/// By default `ContentEncoding::Identity` is used.
|
|
|
|
#[inline]
|
|
|
|
pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
parts.encoding = enc;
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Enables automatic chunked transfer encoding
|
|
|
|
#[inline]
|
|
|
|
pub fn chunked(&mut self) -> &mut Self {
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
2018-02-19 12:11:11 +01:00
|
|
|
parts.chunked = true;
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Enable connection upgrade
|
|
|
|
#[inline]
|
|
|
|
pub fn upgrade(&mut self) -> &mut Self {
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
parts.upgrade = true;
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set request's content type
|
|
|
|
#[inline]
|
|
|
|
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
2018-04-14 01:02:01 +02:00
|
|
|
where
|
|
|
|
HeaderValue: HttpTryFrom<V>,
|
2018-01-29 20:39:26 +01:00
|
|
|
{
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
match HeaderValue::try_from(value) {
|
2018-04-14 01:02:01 +02:00
|
|
|
Ok(value) => {
|
|
|
|
parts.headers.insert(header::CONTENT_TYPE, value);
|
|
|
|
}
|
2018-01-29 20:39:26 +01:00
|
|
|
Err(e) => self.err = Some(e.into()),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set content length
|
|
|
|
#[inline]
|
|
|
|
pub fn content_length(&mut self, len: u64) -> &mut Self {
|
|
|
|
let mut wrt = BytesMut::new().writer();
|
|
|
|
let _ = write!(wrt, "{}", len);
|
|
|
|
self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set a cookie
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # extern crate actix_web;
|
2018-03-31 02:31:18 +02:00
|
|
|
/// use actix_web::{client, http};
|
2018-01-29 20:39:26 +01:00
|
|
|
///
|
2018-01-29 23:44:25 +01:00
|
|
|
/// fn main() {
|
2018-03-31 02:31:18 +02:00
|
|
|
/// let req = client::ClientRequest::build()
|
2018-01-29 20:39:26 +01:00
|
|
|
/// .cookie(
|
2018-03-31 02:31:18 +02:00
|
|
|
/// http::Cookie::build("name", "value")
|
2018-01-29 20:39:26 +01:00
|
|
|
/// .domain("www.rust-lang.org")
|
|
|
|
/// .path("/")
|
|
|
|
/// .secure(true)
|
|
|
|
/// .http_only(true)
|
|
|
|
/// .finish())
|
2018-01-29 23:44:25 +01:00
|
|
|
/// .finish().unwrap();
|
2018-01-29 20:39:26 +01:00
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
|
|
|
|
if self.cookies.is_none() {
|
|
|
|
let mut jar = CookieJar::new();
|
|
|
|
jar.add(cookie.into_owned());
|
|
|
|
self.cookies = Some(jar)
|
|
|
|
} else {
|
2018-05-17 21:20:20 +02:00
|
|
|
self.cookies.as_mut().unwrap().add(cookie.into_owned());
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-02-24 05:29:35 +01:00
|
|
|
/// Do not add default request headers.
|
|
|
|
/// By default `Accept-Encoding` header is set.
|
|
|
|
pub fn no_default_headers(&mut self) -> &mut Self {
|
|
|
|
self.default_headers = false;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Disable automatic decompress response body
|
|
|
|
pub fn disable_decompress(&mut self) -> &mut Self {
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
parts.response_decompress = false;
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-02-27 00:26:27 +01:00
|
|
|
/// Set write buffer capacity
|
2018-03-09 19:09:13 +01:00
|
|
|
///
|
|
|
|
/// Default buffer capacity is 32kb
|
|
|
|
pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self {
|
2018-02-27 00:26:27 +01:00
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
2018-03-09 19:09:13 +01:00
|
|
|
parts.buffer_capacity = cap;
|
2018-02-27 00:26:27 +01:00
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
2018-02-24 05:29:35 +01:00
|
|
|
|
2018-03-19 03:27:51 +01:00
|
|
|
/// Set request timeout
|
|
|
|
///
|
|
|
|
/// Request timeout is a total time before response should be received.
|
|
|
|
/// Default value is 5 seconds.
|
|
|
|
pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
parts.timeout = Some(timeout);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-02-28 00:14:33 +01:00
|
|
|
/// Send request using custom connector
|
2018-05-27 14:02:49 +02:00
|
|
|
pub fn with_connector(&mut self, conn: Addr<ClientConnector>) -> &mut Self {
|
2018-02-28 00:14:33 +01:00
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
parts.conn = ConnectionType::Connector(conn);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:00:57 +02:00
|
|
|
/// Send request using existing `Connection`
|
2018-02-28 00:14:33 +01:00
|
|
|
pub fn with_connection(&mut self, conn: Connection) -> &mut Self {
|
|
|
|
if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
parts.conn = ConnectionType::Connection(conn);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:00:57 +02:00
|
|
|
/// This method calls provided closure with builder reference if
|
|
|
|
/// value is `true`.
|
2018-01-29 20:39:26 +01:00
|
|
|
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
|
2018-04-14 01:02:01 +02:00
|
|
|
where
|
|
|
|
F: FnOnce(&mut ClientRequestBuilder),
|
2018-01-29 20:39:26 +01:00
|
|
|
{
|
|
|
|
if value {
|
|
|
|
f(self);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:00:57 +02:00
|
|
|
/// This method calls provided closure with builder reference if
|
|
|
|
/// value is `Some`.
|
2018-01-29 20:39:26 +01:00
|
|
|
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
|
2018-04-14 01:02:01 +02:00
|
|
|
where
|
|
|
|
F: FnOnce(T, &mut ClientRequestBuilder),
|
2018-01-29 20:39:26 +01:00
|
|
|
{
|
|
|
|
if let Some(val) = value {
|
|
|
|
f(val, self);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set a body and generate `ClientRequest`.
|
|
|
|
///
|
|
|
|
/// `ClientRequestBuilder` can not be used after this call.
|
2018-03-22 04:19:31 +01:00
|
|
|
pub fn body<B: Into<Body>>(&mut self, body: B) -> Result<ClientRequest, Error> {
|
2018-01-29 20:39:26 +01:00
|
|
|
if let Some(e) = self.err.take() {
|
2018-04-14 01:02:01 +02:00
|
|
|
return Err(e.into());
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
|
2018-02-24 05:29:35 +01:00
|
|
|
if self.default_headers {
|
|
|
|
// enable br only for https
|
2018-04-14 01:02:01 +02:00
|
|
|
let https = if let Some(parts) = parts(&mut self.request, &self.err) {
|
2018-04-29 18:09:08 +02:00
|
|
|
parts
|
|
|
|
.uri
|
|
|
|
.scheme_part()
|
|
|
|
.map(|s| s == &uri::Scheme::HTTPS)
|
|
|
|
.unwrap_or(true)
|
2018-04-14 01:02:01 +02:00
|
|
|
} else {
|
|
|
|
true
|
|
|
|
};
|
2018-02-24 05:29:35 +01:00
|
|
|
|
|
|
|
if https {
|
|
|
|
self.header(header::ACCEPT_ENCODING, "br, gzip, deflate");
|
|
|
|
} else {
|
|
|
|
self.header(header::ACCEPT_ENCODING, "gzip, deflate");
|
|
|
|
}
|
|
|
|
}
|
2018-03-02 04:12:59 +01:00
|
|
|
|
2018-05-17 21:20:20 +02:00
|
|
|
let mut request = self.request.take().expect("cannot reuse request builder");
|
2018-01-29 20:39:26 +01:00
|
|
|
|
|
|
|
// set cookies
|
2018-03-08 00:41:46 +01:00
|
|
|
if let Some(ref mut jar) = self.cookies {
|
2018-03-08 20:16:54 +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);
|
2018-03-09 14:38:07 +01:00
|
|
|
let _ = write!(&mut cookie, "; {}={}", name, value);
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
2018-03-08 20:16:54 +01:00
|
|
|
request.headers.insert(
|
2018-04-14 01:02:01 +02:00
|
|
|
header::COOKIE,
|
|
|
|
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
|
|
|
|
);
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
request.body = body.into();
|
|
|
|
Ok(request)
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:00:57 +02:00
|
|
|
/// Set a JSON body and generate `ClientRequest`
|
2018-01-29 20:39:26 +01:00
|
|
|
///
|
|
|
|
/// `ClientRequestBuilder` can not be used after this call.
|
|
|
|
pub fn json<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
|
|
|
|
let body = serde_json::to_string(&value)?;
|
|
|
|
|
|
|
|
let contains = if let Some(parts) = parts(&mut self.request, &self.err) {
|
|
|
|
parts.headers.contains_key(header::CONTENT_TYPE)
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
};
|
|
|
|
if !contains {
|
|
|
|
self.header(header::CONTENT_TYPE, "application/json");
|
|
|
|
}
|
|
|
|
|
2018-03-22 04:19:31 +01:00
|
|
|
self.body(body)
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
|
2018-03-22 03:14:18 +01:00
|
|
|
/// Set a streaming body and generate `ClientRequest`.
|
|
|
|
///
|
|
|
|
/// `ClientRequestBuilder` can not be used after this call.
|
2018-03-22 04:19:31 +01:00
|
|
|
pub fn streaming<S, E>(&mut self, stream: S) -> Result<ClientRequest, Error>
|
2018-04-14 01:02:01 +02:00
|
|
|
where
|
|
|
|
S: Stream<Item = Bytes, Error = E> + 'static,
|
|
|
|
E: Into<Error>,
|
2018-03-22 03:14:18 +01:00
|
|
|
{
|
2018-05-17 21:20:20 +02:00
|
|
|
self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
|
2018-03-22 03:14:18 +01:00
|
|
|
}
|
|
|
|
|
2018-01-29 20:39:26 +01:00
|
|
|
/// Set an empty body and generate `ClientRequest`
|
|
|
|
///
|
|
|
|
/// `ClientRequestBuilder` can not be used after this call.
|
2018-03-22 04:19:31 +01:00
|
|
|
pub fn finish(&mut self) -> Result<ClientRequest, Error> {
|
2018-01-29 20:39:26 +01:00
|
|
|
self.body(Body::Empty)
|
|
|
|
}
|
2018-02-19 22:18:18 +01:00
|
|
|
|
|
|
|
/// This method construct new `ClientRequestBuilder`
|
|
|
|
pub fn take(&mut self) -> ClientRequestBuilder {
|
|
|
|
ClientRequestBuilder {
|
|
|
|
request: self.request.take(),
|
|
|
|
err: self.err.take(),
|
|
|
|
cookies: self.cookies.take(),
|
2018-04-14 01:02:01 +02:00
|
|
|
default_headers: self.default_headers,
|
2018-02-19 22:18:18 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-04-14 01:02:01 +02:00
|
|
|
fn parts<'a>(
|
2018-04-29 07:55:47 +02:00
|
|
|
parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>,
|
2018-04-14 01:02:01 +02:00
|
|
|
) -> Option<&'a mut ClientRequest> {
|
2018-01-29 20:39:26 +01:00
|
|
|
if err.is_some() {
|
2018-04-14 01:02:01 +02:00
|
|
|
return None;
|
2018-01-29 20:39:26 +01:00
|
|
|
}
|
|
|
|
parts.as_mut()
|
|
|
|
}
|
2018-03-13 23:09:05 +01:00
|
|
|
|
|
|
|
impl fmt::Debug for ClientRequestBuilder {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
if let Some(ref parts) = self.request {
|
2018-04-14 01:02:01 +02:00
|
|
|
let res = writeln!(
|
|
|
|
f,
|
|
|
|
"\nClientRequestBuilder {:?} {}:{}",
|
|
|
|
parts.version, parts.method, parts.uri
|
|
|
|
);
|
2018-04-09 23:25:30 +02:00
|
|
|
let _ = writeln!(f, " headers:");
|
2018-03-13 23:09:05 +01:00
|
|
|
for (key, val) in parts.headers.iter() {
|
2018-04-09 23:25:30 +02:00
|
|
|
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
2018-03-13 23:09:05 +01:00
|
|
|
}
|
|
|
|
res
|
|
|
|
} else {
|
|
|
|
write!(f, "ClientRequestBuilder(Consumed)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-22 03:54:21 +01:00
|
|
|
|
|
|
|
/// Create `ClientRequestBuilder` from `HttpRequest`
|
|
|
|
///
|
|
|
|
/// It is useful for proxy requests. This implementation
|
2018-04-07 17:00:57 +02:00
|
|
|
/// copies all request headers and the method.
|
2018-03-22 03:54:21 +01:00
|
|
|
impl<'a, S: 'static> From<&'a HttpRequest<S>> for ClientRequestBuilder {
|
|
|
|
fn from(req: &'a HttpRequest<S>) -> ClientRequestBuilder {
|
|
|
|
let mut builder = ClientRequest::build();
|
|
|
|
for (key, value) in req.headers() {
|
|
|
|
builder.header(key.clone(), value.clone());
|
|
|
|
}
|
|
|
|
builder.method(req.method().clone());
|
|
|
|
builder
|
|
|
|
}
|
|
|
|
}
|