461 lines
13 KiB
Rust
461 lines
13 KiB
Rust
use std::convert::TryFrom;
|
|
use std::fmt;
|
|
|
|
use bytes::Bytes;
|
|
use http::{request::Parts, Method, Request as HttpRequest};
|
|
use serde::Serialize;
|
|
#[cfg(feature = "json")]
|
|
use serde_json;
|
|
use url::Url;
|
|
use web_sys::RequestCredentials;
|
|
|
|
use super::{Body, Client, Response};
|
|
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
|
|
|
|
/// A request which can be executed with `Client::execute()`.
|
|
pub struct Request {
|
|
method: Method,
|
|
url: Url,
|
|
headers: HeaderMap,
|
|
body: Option<Body>,
|
|
pub(super) cors: bool,
|
|
pub(super) credentials: Option<RequestCredentials>,
|
|
}
|
|
|
|
/// A builder to construct the properties of a `Request`.
|
|
pub struct RequestBuilder {
|
|
client: Client,
|
|
request: crate::Result<Request>,
|
|
}
|
|
|
|
impl Request {
|
|
/// Constructs a new request.
|
|
#[inline]
|
|
pub fn new(method: Method, url: Url) -> Self {
|
|
Request {
|
|
method,
|
|
url,
|
|
headers: HeaderMap::new(),
|
|
body: None,
|
|
cors: true,
|
|
credentials: None,
|
|
}
|
|
}
|
|
|
|
/// Get the method.
|
|
#[inline]
|
|
pub fn method(&self) -> &Method {
|
|
&self.method
|
|
}
|
|
|
|
/// Get a mutable reference to the method.
|
|
#[inline]
|
|
pub fn method_mut(&mut self) -> &mut Method {
|
|
&mut self.method
|
|
}
|
|
|
|
/// Get the url.
|
|
#[inline]
|
|
pub fn url(&self) -> &Url {
|
|
&self.url
|
|
}
|
|
|
|
/// Get a mutable reference to the url.
|
|
#[inline]
|
|
pub fn url_mut(&mut self) -> &mut Url {
|
|
&mut self.url
|
|
}
|
|
|
|
/// Get the headers.
|
|
#[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
|
|
}
|
|
|
|
/// Get the body.
|
|
#[inline]
|
|
pub fn body(&self) -> Option<&Body> {
|
|
self.body.as_ref()
|
|
}
|
|
|
|
/// Get a mutable reference to the body.
|
|
#[inline]
|
|
pub fn body_mut(&mut self) -> &mut Option<Body> {
|
|
&mut self.body
|
|
}
|
|
|
|
/// Attempts to clone the `Request`.
|
|
///
|
|
/// None is returned if a body is which can not be cloned.
|
|
pub fn try_clone(&self) -> Option<Request> {
|
|
let body = match self.body.as_ref() {
|
|
Some(body) => Some(body.try_clone()?),
|
|
None => None,
|
|
};
|
|
|
|
Some(Self {
|
|
method: self.method.clone(),
|
|
url: self.url.clone(),
|
|
headers: self.headers.clone(),
|
|
body,
|
|
cors: self.cors,
|
|
credentials: self.credentials,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl RequestBuilder {
|
|
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
|
|
RequestBuilder { client, request }
|
|
}
|
|
|
|
/// Modify the query string of the URL.
|
|
///
|
|
/// Modifies the URL of this request, adding the parameters provided.
|
|
/// This method appends and does not overwrite. This means that it can
|
|
/// be called multiple times and that existing query parameters are not
|
|
/// overwritten if the same key is used. The key will simply show up
|
|
/// twice in the query string.
|
|
/// Calling `.query([("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`.
|
|
///
|
|
/// # Note
|
|
/// This method does not support serializing a single key-value
|
|
/// pair. Instead of using `.query(("key", "val"))`, use a sequence, such
|
|
/// as `.query(&[("key", "val")])`. It's also possible to serialize structs
|
|
/// and maps into a key-value pair.
|
|
///
|
|
/// # Errors
|
|
/// This method will fail if the object you provide cannot be serialized
|
|
/// into a query string.
|
|
pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
|
|
let mut error = None;
|
|
if let Ok(ref mut req) = self.request {
|
|
let url = req.url_mut();
|
|
let mut pairs = url.query_pairs_mut();
|
|
let serializer = serde_urlencoded::Serializer::new(&mut pairs);
|
|
|
|
if let Err(err) = query.serialize(serializer) {
|
|
error = Some(crate::error::builder(err));
|
|
}
|
|
}
|
|
if let Ok(ref mut req) = self.request {
|
|
if let Some("") = req.url().query() {
|
|
req.url_mut().set_query(None);
|
|
}
|
|
}
|
|
if let Some(err) = error {
|
|
self.request = Err(err);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Send a form body.
|
|
pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
|
|
let mut error = None;
|
|
if let Ok(ref mut req) = self.request {
|
|
match serde_urlencoded::to_string(form) {
|
|
Ok(body) => {
|
|
req.headers_mut().insert(
|
|
CONTENT_TYPE,
|
|
HeaderValue::from_static("application/x-www-form-urlencoded"),
|
|
);
|
|
*req.body_mut() = Some(body.into());
|
|
}
|
|
Err(err) => error = Some(crate::error::builder(err)),
|
|
}
|
|
}
|
|
if let Some(err) = error {
|
|
self.request = Err(err);
|
|
}
|
|
self
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
|
|
/// Set the request json
|
|
pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
|
|
let mut error = None;
|
|
if let Ok(ref mut req) = self.request {
|
|
match serde_json::to_vec(json) {
|
|
Ok(body) => {
|
|
req.headers_mut()
|
|
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
|
*req.body_mut() = Some(body.into());
|
|
}
|
|
Err(err) => error = Some(crate::error::builder(err)),
|
|
}
|
|
}
|
|
if let Some(err) = error {
|
|
self.request = Err(err);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Enable HTTP bearer authentication.
|
|
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
|
|
where
|
|
T: fmt::Display,
|
|
{
|
|
let header_value = format!("Bearer {}", token);
|
|
self.header(crate::header::AUTHORIZATION, header_value)
|
|
}
|
|
|
|
/// Set the request body.
|
|
pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
|
|
if let Ok(ref mut req) = self.request {
|
|
req.body = Some(body.into());
|
|
}
|
|
self
|
|
}
|
|
|
|
/// TODO
|
|
#[cfg(feature = "multipart")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
|
|
pub fn multipart(mut self, multipart: super::multipart::Form) -> RequestBuilder {
|
|
if let Ok(ref mut req) = self.request {
|
|
*req.body_mut() = Some(Body::from_form(multipart))
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Add a `Header` to this Request.
|
|
pub fn header<K, V>(mut self, key: K, value: V) -> RequestBuilder
|
|
where
|
|
HeaderName: TryFrom<K>,
|
|
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
|
|
HeaderValue: TryFrom<V>,
|
|
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
|
|
{
|
|
let mut error = None;
|
|
if let Ok(ref mut req) = self.request {
|
|
match <HeaderName as TryFrom<K>>::try_from(key) {
|
|
Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
|
|
Ok(value) => {
|
|
req.headers_mut().append(key, value);
|
|
}
|
|
Err(e) => error = Some(crate::error::builder(e.into())),
|
|
},
|
|
Err(e) => error = Some(crate::error::builder(e.into())),
|
|
};
|
|
}
|
|
if let Some(err) = error {
|
|
self.request = Err(err);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Add a set of Headers to the existing ones on this Request.
|
|
///
|
|
/// The headers will be merged in to any already set.
|
|
pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
|
|
if let Ok(ref mut req) = self.request {
|
|
crate::util::replace_headers(req.headers_mut(), headers);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Disable CORS on fetching the request.
|
|
///
|
|
/// # WASM
|
|
///
|
|
/// This option is only effective with WebAssembly target.
|
|
///
|
|
/// The [request mode][mdn] will be set to 'no-cors'.
|
|
///
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
|
|
pub fn fetch_mode_no_cors(mut self) -> RequestBuilder {
|
|
if let Ok(ref mut req) = self.request {
|
|
req.cors = false;
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Set fetch credentials to 'same-origin'
|
|
///
|
|
/// # WASM
|
|
///
|
|
/// This option is only effective with WebAssembly target.
|
|
///
|
|
/// The [request credentials][mdn] will be set to 'same-origin'.
|
|
///
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
|
|
pub fn fetch_credentials_same_origin(mut self) -> RequestBuilder {
|
|
if let Ok(ref mut req) = self.request {
|
|
req.credentials = Some(RequestCredentials::SameOrigin);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Set fetch credentials to 'include'
|
|
///
|
|
/// # WASM
|
|
///
|
|
/// This option is only effective with WebAssembly target.
|
|
///
|
|
/// The [request credentials][mdn] will be set to 'include'.
|
|
///
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
|
|
pub fn fetch_credentials_include(mut self) -> RequestBuilder {
|
|
if let Ok(ref mut req) = self.request {
|
|
req.credentials = Some(RequestCredentials::Include);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Set fetch credentials to 'omit'
|
|
///
|
|
/// # WASM
|
|
///
|
|
/// This option is only effective with WebAssembly target.
|
|
///
|
|
/// The [request credentials][mdn] will be set to 'omit'.
|
|
///
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
|
|
pub fn fetch_credentials_omit(mut self) -> RequestBuilder {
|
|
if let Ok(ref mut req) = self.request {
|
|
req.credentials = Some(RequestCredentials::Omit);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Build a `Request`, which can be inspected, modified and executed with
|
|
/// `Client::execute()`.
|
|
pub fn build(self) -> crate::Result<Request> {
|
|
self.request
|
|
}
|
|
|
|
/// Constructs the Request and sends it to the target URL, returning a
|
|
/// future Response.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// This method fails if there was an error while sending request.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```no_run
|
|
/// # use reqwest::Error;
|
|
/// #
|
|
/// # async fn run() -> Result<(), Error> {
|
|
/// let response = reqwest::Client::new()
|
|
/// .get("https://hyper.rs")
|
|
/// .send()
|
|
/// .await?;
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub async fn send(self) -> crate::Result<Response> {
|
|
let req = self.request?;
|
|
self.client.execute_request(req).await
|
|
}
|
|
|
|
/// Attempt to clone the RequestBuilder.
|
|
///
|
|
/// `None` is returned if the RequestBuilder can not be cloned.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```no_run
|
|
/// # use reqwest::Error;
|
|
/// #
|
|
/// # fn run() -> Result<(), Error> {
|
|
/// let client = reqwest::Client::new();
|
|
/// let builder = client.post("http://httpbin.org/post")
|
|
/// .body("from a &str!");
|
|
/// let clone = builder.try_clone();
|
|
/// assert!(clone.is_some());
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub fn try_clone(&self) -> Option<RequestBuilder> {
|
|
self.request
|
|
.as_ref()
|
|
.ok()
|
|
.and_then(|req| req.try_clone())
|
|
.map(|req| RequestBuilder {
|
|
client: self.client.clone(),
|
|
request: Ok(req),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Request {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for RequestBuilder {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let mut builder = f.debug_struct("RequestBuilder");
|
|
match self.request {
|
|
Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
|
|
Err(ref err) => builder.field("error", err).finish(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn fmt_request_fields<'a, 'b>(
|
|
f: &'a mut fmt::DebugStruct<'a, 'b>,
|
|
req: &Request,
|
|
) -> &'a mut fmt::DebugStruct<'a, 'b> {
|
|
f.field("method", &req.method)
|
|
.field("url", &req.url)
|
|
.field("headers", &req.headers)
|
|
}
|
|
|
|
impl<T> TryFrom<HttpRequest<T>> for Request
|
|
where
|
|
T: Into<Body>,
|
|
{
|
|
type Error = crate::Error;
|
|
|
|
fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
|
|
let (parts, body) = req.into_parts();
|
|
let Parts {
|
|
method,
|
|
uri,
|
|
headers,
|
|
..
|
|
} = parts;
|
|
let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
|
|
Ok(Request {
|
|
method,
|
|
url,
|
|
headers,
|
|
body: Some(body.into()),
|
|
cors: true,
|
|
credentials: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Request> for HttpRequest<Body> {
|
|
type Error = crate::Error;
|
|
|
|
fn try_from(req: Request) -> crate::Result<Self> {
|
|
let Request {
|
|
method,
|
|
url,
|
|
headers,
|
|
body,
|
|
..
|
|
} = req;
|
|
|
|
let mut req = HttpRequest::builder()
|
|
.method(method)
|
|
.uri(url.as_str())
|
|
.body(body.unwrap_or_else(|| Body::from(Bytes::default())))
|
|
.map_err(crate::error::builder)?;
|
|
|
|
*req.headers_mut() = headers;
|
|
Ok(req)
|
|
}
|
|
}
|