mirror of https://github.com/http-rs/surf
src: overhaul Client, Request, and Middleware
A practical implementation of https://github.com/http-rs/surf/issues/192 Some form of this is required in order to make middleware use `surf::Request` and `surf::Response`. Doing so means that those types, particularly request, must own their related `http_types` data. Refs: https://github.com/http-rs/surf/issues/131 This necessitates that `surf::{method}()` return some kind of builder, as is include here as `RequestBuilder`, which includes methods for the common request manipulations when creating new requests. As added convience, the builder can be awaited directly, but does not allow for middleware without use of `Client` due to confusing semantics regarding combining per-request and per-client middleware. PR-URL: https://github.com/http-rs/surf/pull/194
This commit is contained in:
parent
57d03222dc
commit
b6113dec3d
|
@ -1,5 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
use surf::middleware::{HttpClient, Middleware, Next, Request, Response};
|
||||
use surf::middleware::{Middleware, Next};
|
||||
use surf::{Client, Request, Response};
|
||||
|
||||
struct Printer;
|
||||
|
||||
|
@ -8,7 +8,7 @@ impl Middleware for Printer {
|
|||
async fn handle(
|
||||
&self,
|
||||
req: Request,
|
||||
client: Arc<dyn HttpClient>,
|
||||
client: Client,
|
||||
next: Next<'_>,
|
||||
) -> Result<Response, http_types::Error> {
|
||||
println!("sending a request!");
|
||||
|
@ -22,8 +22,7 @@ impl Middleware for Printer {
|
|||
async fn main() -> Result<(), http_types::Error> {
|
||||
femme::start(log::LevelFilter::Info)?;
|
||||
|
||||
surf::get("https://httpbin.org/get")
|
||||
.middleware(Printer {})
|
||||
.await?;
|
||||
let req = surf::get("https://httpbin.org/get");
|
||||
surf::client().middleware(Printer {}).send(req).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use futures_util::io::AsyncReadExt;
|
||||
use std::sync::Arc;
|
||||
use surf::middleware::{Body, HttpClient, Middleware, Next, Request, Response};
|
||||
use surf::middleware::{Middleware, Next};
|
||||
use surf::{Body, Client, Request, Response};
|
||||
|
||||
struct Doubler;
|
||||
|
||||
|
@ -9,12 +9,14 @@ impl Middleware for Doubler {
|
|||
async fn handle(
|
||||
&self,
|
||||
req: Request,
|
||||
client: Arc<dyn HttpClient>,
|
||||
client: Client,
|
||||
next: Next<'_>,
|
||||
) -> Result<Response, http_types::Error> {
|
||||
if req.method().is_safe() {
|
||||
let mut new_req = Request::new(req.method(), req.url().clone());
|
||||
new_req.set_version(req.version());
|
||||
let mut new_req = http_types::Request::new(req.method(), req.url().clone());
|
||||
new_req.set_version(req.as_ref().version());
|
||||
let mut new_req: Request = new_req.into();
|
||||
|
||||
for (name, value) in &req {
|
||||
new_req.insert_header(name, value);
|
||||
}
|
||||
|
@ -43,9 +45,8 @@ impl Middleware for Doubler {
|
|||
async fn main() -> Result<(), http_types::Error> {
|
||||
femme::start(log::LevelFilter::Info)?;
|
||||
|
||||
let mut res = surf::get("https://httpbin.org/get")
|
||||
.middleware(Doubler {})
|
||||
.await?;
|
||||
let req = surf::get("https://httpbin.org/get");
|
||||
let mut res = surf::client().middleware(Doubler {}).send(req).await?;
|
||||
dbg!(&res);
|
||||
let body = res.body_bytes().await?;
|
||||
let body = String::from_utf8_lossy(&body);
|
||||
|
|
|
@ -3,8 +3,8 @@ async fn main() -> Result<(), http_types::Error> {
|
|||
femme::start(log::LevelFilter::Info)?;
|
||||
|
||||
let client = surf::Client::new();
|
||||
let req1 = client.get("https://httpbin.org/get").recv_string();
|
||||
let req2 = client.get("https://httpbin.org/get").recv_string();
|
||||
futures_util::future::try_join(req1, req2).await?;
|
||||
let res1 = client.recv_string(surf::get("https://httpbin.org/get"));
|
||||
let res2 = client.recv_string(surf::get("https://httpbin.org/get"));
|
||||
futures_util::future::try_join(res1, res2).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@ async fn main() -> Result<(), http_types::Error> {
|
|||
|
||||
let uri = "https://httpbin.org/post";
|
||||
let data = serde_json::json!({ "name": "chashu" });
|
||||
let res = surf::post(uri).body_json(&data).unwrap().await?;
|
||||
let res = surf::post(uri)
|
||||
.body(http_types::Body::from_json(&data)?)
|
||||
.await?;
|
||||
assert_eq!(res.status(), http_types::StatusCode::Ok);
|
||||
Ok(())
|
||||
}
|
||||
|
|
267
src/client.rs
267
src/client.rs
|
@ -1,10 +1,10 @@
|
|||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::http::Method;
|
||||
use crate::Request;
|
||||
use crate::middleware::{Middleware, Next};
|
||||
use crate::{HttpClient, Request, Response, Result};
|
||||
|
||||
use http_client::HttpClient;
|
||||
use futures_util::future::BoxFuture;
|
||||
|
||||
#[cfg(all(feature = "native-client", not(feature = "h1-client")))]
|
||||
use http_client::native::NativeClient;
|
||||
|
@ -12,7 +12,7 @@ use http_client::native::NativeClient;
|
|||
#[cfg(feature = "h1-client")]
|
||||
use http_client::h1::H1Client;
|
||||
|
||||
/// An HTTP client, capable of creating new `Request`s.
|
||||
/// An HTTP client, capable of sending `Request`s and running a middleware stack.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -20,13 +20,16 @@ use http_client::h1::H1Client;
|
|||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let client = surf::Client::new();
|
||||
/// let req1 = client.get("https://httpbin.org/get").recv_string();
|
||||
/// let req2 = client.get("https://httpbin.org/get").recv_string();
|
||||
/// let (str1, str2) = futures_util::future::try_join(req1, req2).await?;
|
||||
/// let res1 = client.recv_string(surf::get("https://httpbin.org/get"));
|
||||
/// let res2 = client.recv_string(surf::get("https://httpbin.org/get"));
|
||||
/// let (str1, str2) = futures_util::future::try_join(res1, res2).await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct Client {
|
||||
client: Arc<dyn HttpClient>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
/// Holds the middleware stack.
|
||||
middleware: Arc<Vec<Arc<dyn Middleware>>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Client {
|
||||
|
@ -57,232 +60,168 @@ impl Client {
|
|||
let client = NativeClient::new();
|
||||
#[cfg(feature = "h1-client")]
|
||||
let client = H1Client::new();
|
||||
Self::with_client(Arc::new(client))
|
||||
Self::with_http_client(Arc::new(client))
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new instance with an `http_client::HttpClient` instance.
|
||||
// TODO(yw): hidden from docs until we make the traits public.
|
||||
#[doc(hidden)]
|
||||
#[allow(missing_doc_code_examples)]
|
||||
pub fn with_client(client: Arc<dyn HttpClient>) -> Self {
|
||||
Self { client }
|
||||
pub fn with_http_client(http_client: Arc<dyn HttpClient>) -> Self {
|
||||
Self {
|
||||
http_client,
|
||||
middleware: Arc::new(vec![
|
||||
#[cfg(feature = "middleware-logger")]
|
||||
Arc::new(crate::middleware::Logger::new()),
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform an HTTP `GET` request using the `Client` connection.
|
||||
/// Push middleware onto the middleware stack.
|
||||
///
|
||||
/// # Panics
|
||||
/// See the [middleware] submodule for more information on middleware.
|
||||
///
|
||||
/// This will panic if a malformed URL is passed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns errors from the middleware, http backend, and network sockets.
|
||||
/// [middleware]: ../middleware/index.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let client = surf::Client::new();
|
||||
/// let string = client.get("https://httpbin.org/get").recv_string().await?;
|
||||
/// let req = surf::get("https://httpbin.org/get");
|
||||
/// let client = surf::client()
|
||||
/// .middleware(surf::middleware::Redirect::default());
|
||||
/// let res = client.send(req).await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn get(&self, uri: impl AsRef<str>) -> Request {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::with_client(Method::Get, uri, self.client.clone())
|
||||
pub fn middleware(mut self, middleware: impl Middleware) -> Self {
|
||||
let m = Arc::get_mut(&mut self.middleware)
|
||||
.expect("Registering middleware is not possible after the Client has been used");
|
||||
m.push(Arc::new(middleware));
|
||||
self
|
||||
}
|
||||
|
||||
/// Perform an HTTP `HEAD` request using the `Client` connection.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if a malformed URL is passed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns errors from the middleware, http backend, and network sockets.
|
||||
/// Send a Request using this client.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let client = surf::Client::new();
|
||||
/// let string = client.head("https://httpbin.org/head").recv_string().await?;
|
||||
/// let req = surf::get("https://httpbin.org/get");
|
||||
/// let client = surf::client();
|
||||
/// let res = client.send(req).await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn head(&self, uri: impl AsRef<str>) -> Request {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::with_client(Method::Head, uri, self.client.clone())
|
||||
pub fn send(&self, req: impl Into<Request>) -> BoxFuture<'static, Result<Response>> {
|
||||
let req: Request = req.into();
|
||||
let Self {
|
||||
http_client,
|
||||
middleware,
|
||||
} = self.clone();
|
||||
Box::pin(async move {
|
||||
let next = Next::new(&middleware, &|req, client| {
|
||||
Box::pin(async move {
|
||||
let req: http_types::Request = req.into();
|
||||
client.http_client.send(req).await.map(Into::into)
|
||||
})
|
||||
});
|
||||
|
||||
let res = next.run(req, Client::with_http_client(http_client)).await?;
|
||||
Ok(Response::new(res.into()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Perform an HTTP `POST` request using the `Client` connection.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if a malformed URL is passed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns errors from the middleware, http backend, and network sockets.
|
||||
/// Submit the request and get the response body as bytes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let client = surf::Client::new();
|
||||
/// let string = client.post("https://httpbin.org/post").recv_string().await?;
|
||||
/// let req = surf::get("https://httpbin.org/get");
|
||||
/// let bytes = surf::client().recv_bytes(req).await?;
|
||||
/// assert!(bytes.len() > 0);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn post(&self, uri: impl AsRef<str>) -> Request {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::with_client(Method::Post, uri, self.client.clone())
|
||||
pub async fn recv_bytes(&self, req: impl Into<Request>) -> Result<Vec<u8>> {
|
||||
let mut res = self.send(req.into()).await?;
|
||||
Ok(res.body_bytes().await?)
|
||||
}
|
||||
|
||||
/// Perform an HTTP `PUT` request using the `Client` connection.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if a malformed URL is passed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns errors from the middleware, http backend, and network sockets.
|
||||
/// Submit the request and get the response body as a string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let client = surf::Client::new();
|
||||
/// let string = client.put("https://httpbin.org/put").recv_string().await?;
|
||||
/// let req = surf::get("https://httpbin.org/get");
|
||||
/// let string = surf::client().recv_string(req).await?;
|
||||
/// assert!(string.len() > 0);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn put(&self, uri: impl AsRef<str>) -> Request {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::with_client(Method::Put, uri, self.client.clone())
|
||||
pub async fn recv_string(&self, req: impl Into<Request>) -> Result<String> {
|
||||
let mut res = self.send(req.into()).await?;
|
||||
Ok(res.body_string().await?)
|
||||
}
|
||||
|
||||
/// Perform an HTTP `DELETE` request using the `Client` connection.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if a malformed URL is passed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns errors from the middleware, http backend, and network sockets.
|
||||
/// Submit the request and decode the response body from json into a struct.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let client = surf::Client::new();
|
||||
/// let string = client.delete("https://httpbin.org/delete").recv_string().await?;
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct Ip {
|
||||
/// ip: String
|
||||
/// }
|
||||
///
|
||||
/// let req = surf::get("https://api.ipify.org?format=json");
|
||||
/// let Ip { ip } = surf::client().recv_json(req).await?;
|
||||
/// assert!(ip.len() > 10);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn delete(&self, uri: impl AsRef<str>) -> Request {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::with_client(Method::Delete, uri, self.client.clone())
|
||||
pub async fn recv_json<T: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
req: impl Into<Request>,
|
||||
) -> Result<T> {
|
||||
let mut res = self.send(req.into()).await?;
|
||||
Ok(res.body_json::<T>().await?)
|
||||
}
|
||||
|
||||
/// Perform an HTTP `CONNECT` request using the `Client` connection.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if a malformed URL is passed.
|
||||
/// Submit the request and decode the response body from form encoding into a struct.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns errors from the middleware, http backend, and network sockets.
|
||||
/// Any I/O error encountered while reading the body is immediately returned
|
||||
/// as an `Err`.
|
||||
///
|
||||
/// If the body cannot be interpreted as valid json for the target type `T`,
|
||||
/// an `Err` is returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let client = surf::Client::new();
|
||||
/// let string = client.connect("https://httpbin.org/connect").recv_string().await?;
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct Body {
|
||||
/// apples: u32
|
||||
/// }
|
||||
///
|
||||
/// let req = surf::get("https://api.example.com/v1/response");
|
||||
/// let Body { apples } = surf::client().recv_form(req).await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn connect(&self, uri: impl AsRef<str>) -> Request {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::with_client(Method::Connect, uri, self.client.clone())
|
||||
}
|
||||
|
||||
/// Perform an HTTP `OPTIONS` request using the `Client` connection.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if a malformed URL is passed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns errors from the middleware, http backend, and network sockets.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let client = surf::Client::new();
|
||||
/// let string = client.options("https://httpbin.org/options").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn options(&self, uri: impl AsRef<str>) -> Request {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::with_client(Method::Options, uri, self.client.clone())
|
||||
}
|
||||
|
||||
/// Perform an HTTP `TRACE` request using the `Client` connection.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if a malformed URL is passed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns errors from the middleware, http backend, and network sockets.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let client = surf::Client::new();
|
||||
/// let string = client.trace("https://httpbin.org/trace").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn trace(&self, uri: impl AsRef<str>) -> Request {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::with_client(Method::Trace, uri, self.client.clone())
|
||||
}
|
||||
|
||||
/// Perform an HTTP `PATCH` request using the `Client` connection.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if a malformed URL is passed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns errors from the middleware, http backend, and network sockets.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let client = surf::Client::new();
|
||||
/// let string = client.patch("https://httpbin.org/patch").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn patch(&self, uri: impl AsRef<str>) -> Request {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::with_client(Method::Patch, uri, self.client.clone())
|
||||
pub async fn recv_form<T: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
req: impl Into<Request>,
|
||||
) -> Result<T> {
|
||||
let mut res = self.send(req.into()).await?;
|
||||
Ok(res.body_form::<T>().await?)
|
||||
}
|
||||
}
|
||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -41,7 +41,7 @@
|
|||
//!
|
||||
//! let uri = "https://httpbin.org/post";
|
||||
//! let data = &Ip { ip: "129.0.0.1".into() };
|
||||
//! let res = surf::post(uri).body_json(data)?.await?;
|
||||
//! let res = surf::post(uri).body(surf::Body::from_json(data)?).await?;
|
||||
//! assert_eq!(res.status(), 200);
|
||||
//!
|
||||
//! let uri = "https://api.ipify.org?format=json";
|
||||
|
@ -57,7 +57,7 @@
|
|||
//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
//! let req = surf::get("https://img.fyi/q6YvNqP").await?;
|
||||
//! let body = surf::http::Body::from_reader(req, None);
|
||||
//! let res = surf::post("https://box.rs/upload").set_body(body).await?;
|
||||
//! let res = surf::post("https://box.rs/upload").body(body).await?;
|
||||
//! # Ok(()) }
|
||||
//! ```
|
||||
//!
|
||||
|
@ -79,6 +79,7 @@
|
|||
|
||||
mod client;
|
||||
mod request;
|
||||
mod request_builder;
|
||||
mod response;
|
||||
|
||||
pub mod middleware;
|
||||
|
@ -87,10 +88,14 @@ pub mod utils;
|
|||
#[doc(inline)]
|
||||
pub use http_types::{self as http, Body, Error, Status, StatusCode};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use http_client::HttpClient;
|
||||
|
||||
pub use url;
|
||||
|
||||
pub use client::Client;
|
||||
pub use request::Request;
|
||||
pub use request_builder::RequestBuilder;
|
||||
pub use response::{DecodeError, Response};
|
||||
|
||||
#[cfg(any(feature = "native-client", feature = "h1-client"))]
|
||||
|
@ -98,5 +103,10 @@ mod one_off;
|
|||
#[cfg(any(feature = "native-client", feature = "h1-client"))]
|
||||
pub use one_off::{connect, delete, get, head, options, patch, post, put, trace};
|
||||
|
||||
/// Construct a new `Client`.
|
||||
pub fn client() -> Client {
|
||||
Client::new()
|
||||
}
|
||||
|
||||
/// A specialized Result type for Surf.
|
||||
pub type Result<T = Response> = std::result::Result<T, Error>;
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
//! ```no_run
|
||||
//! # #[async_std::main]
|
||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
//! let mut res = surf::get("https://httpbin.org/get")
|
||||
//! let req = surf::get("https://httpbin.org/get");
|
||||
//! let mut res = surf::client()
|
||||
//! .middleware(surf::middleware::Logger::new())
|
||||
//! .await?;
|
||||
//! .send(req).await?;
|
||||
//! dbg!(res.body_string().await?);
|
||||
//! # Ok(()) }
|
||||
//! ```
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use crate::middleware::{Middleware, Next, Request, Response};
|
||||
use http_client::HttpClient;
|
||||
use crate::middleware::{Middleware, Next};
|
||||
use crate::{Client, Request, Response};
|
||||
|
||||
use std::fmt::Arguments;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
@ -27,7 +26,7 @@ impl Middleware for Logger {
|
|||
async fn handle(
|
||||
&self,
|
||||
req: Request,
|
||||
client: Arc<dyn HttpClient>,
|
||||
client: Client,
|
||||
next: Next<'_>,
|
||||
) -> Result<Response, http_types::Error> {
|
||||
let start_time = time::Instant::now();
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
//!
|
||||
//! # Examples
|
||||
//! ```no_run
|
||||
//! use surf::middleware::{Next, Middleware, Request, Response, HttpClient};
|
||||
//! use std::error::Error;
|
||||
//! use surf::middleware::{Next, Middleware};
|
||||
//! use surf::{Client, Request, Response, Result};
|
||||
//! use std::time;
|
||||
//! use std::sync::Arc;
|
||||
//!
|
||||
|
@ -16,9 +16,9 @@
|
|||
//! async fn handle(
|
||||
//! &self,
|
||||
//! req: Request,
|
||||
//! client: Arc<dyn HttpClient>,
|
||||
//! client: Client,
|
||||
//! next: Next<'_>,
|
||||
//! ) -> Result<Response, http_types::Error> {
|
||||
//! ) -> Result<Response> {
|
||||
//! println!("sending request to {}", req.url());
|
||||
//! let now = time::Instant::now();
|
||||
//! let res = next.run(req, client).await?;
|
||||
|
@ -32,11 +32,12 @@
|
|||
//!
|
||||
//! ```no_run
|
||||
//! use futures_util::future::BoxFuture;
|
||||
//! use surf::middleware::{Next, Middleware, Request, Response, HttpClient};
|
||||
//! use surf::middleware::{Next, Middleware};
|
||||
//! use surf::{Client, Request, Response, Result};
|
||||
//! use std::time;
|
||||
//! use std::sync::Arc;
|
||||
//!
|
||||
//! fn logger<'a>(req: Request, client: Arc<dyn HttpClient>, next: Next<'a>) -> BoxFuture<'a, Result<Response, http_types::Error>> {
|
||||
//! fn logger<'a>(req: Request, client: Client, next: Next<'a>) -> BoxFuture<'a, Result<Response>> {
|
||||
//! Box::pin(async move {
|
||||
//! println!("sending request to {}", req.url());
|
||||
//! let now = time::Instant::now();
|
||||
|
@ -47,18 +48,15 @@
|
|||
//! }
|
||||
//! #
|
||||
//! # #[async_std::main]
|
||||
//! # async fn main() -> Result<(), http_types::Error> {
|
||||
//! # surf::get("https://httpbin.org/get")
|
||||
//! # .middleware(logger)
|
||||
//! # .await?;
|
||||
//! # async fn main() -> Result<()> {
|
||||
//! # surf::client().middleware(logger);
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use http_client::{Body, HttpClient, Request, Response};
|
||||
use crate::{Client, Request, Response, Result};
|
||||
|
||||
mod logger;
|
||||
mod redirect;
|
||||
|
@ -68,18 +66,12 @@ pub use redirect::Redirect;
|
|||
|
||||
use async_trait::async_trait;
|
||||
use futures_util::future::BoxFuture;
|
||||
use http_types::Error;
|
||||
|
||||
/// Middleware that wraps around remaining middleware chain.
|
||||
#[async_trait]
|
||||
pub trait Middleware: 'static + Send + Sync {
|
||||
/// Asynchronously handle the request, and return a response.
|
||||
async fn handle(
|
||||
&self,
|
||||
req: Request,
|
||||
client: Arc<dyn HttpClient>,
|
||||
next: Next<'_>,
|
||||
) -> Result<Response, Error>;
|
||||
async fn handle(&self, req: Request, client: Client, next: Next<'_>) -> Result<Response>;
|
||||
}
|
||||
|
||||
// This allows functions to work as middleware too.
|
||||
|
@ -89,14 +81,9 @@ where
|
|||
F: Send
|
||||
+ Sync
|
||||
+ 'static
|
||||
+ for<'a> Fn(Request, Arc<dyn HttpClient>, Next<'a>) -> BoxFuture<'a, Result<Response, Error>>,
|
||||
+ for<'a> Fn(Request, Client, Next<'a>) -> BoxFuture<'a, Result<Response>>,
|
||||
{
|
||||
async fn handle(
|
||||
&self,
|
||||
req: Request,
|
||||
client: Arc<dyn HttpClient>,
|
||||
next: Next<'_>,
|
||||
) -> Result<Response, Error> {
|
||||
async fn handle(&self, req: Request, client: Client, next: Next<'_>) -> Result<Response> {
|
||||
(self)(req, client, next).await
|
||||
}
|
||||
}
|
||||
|
@ -105,10 +92,10 @@ where
|
|||
#[allow(missing_debug_implementations)]
|
||||
pub struct Next<'a> {
|
||||
next_middleware: &'a [Arc<dyn Middleware>],
|
||||
endpoint: &'a (dyn (Fn(Request, Arc<dyn HttpClient>) -> BoxFuture<'static, Result<Response, Error>>)
|
||||
+ 'static
|
||||
endpoint: &'a (dyn (Fn(Request, Client) -> BoxFuture<'static, Result<Response>>)
|
||||
+ Send
|
||||
+ Sync),
|
||||
+ Sync
|
||||
+ 'static),
|
||||
}
|
||||
|
||||
impl Clone for Next<'_> {
|
||||
|
@ -126,10 +113,10 @@ impl<'a> Next<'a> {
|
|||
/// Create a new instance
|
||||
pub fn new(
|
||||
next: &'a [Arc<dyn Middleware>],
|
||||
endpoint: &'a (dyn (Fn(Request, Arc<dyn HttpClient>) -> BoxFuture<'static, Result<Response, Error>>)
|
||||
+ 'static
|
||||
endpoint: &'a (dyn (Fn(Request, Client) -> BoxFuture<'static, Result<Response>>)
|
||||
+ Send
|
||||
+ Sync),
|
||||
+ Sync
|
||||
+ 'static),
|
||||
) -> Self {
|
||||
Self {
|
||||
endpoint,
|
||||
|
@ -138,11 +125,7 @@ impl<'a> Next<'a> {
|
|||
}
|
||||
|
||||
/// Asynchronously execute the remaining middleware chain.
|
||||
pub fn run(
|
||||
mut self,
|
||||
req: Request,
|
||||
client: Arc<dyn HttpClient>,
|
||||
) -> BoxFuture<'a, Result<Response, Error>> {
|
||||
pub fn run(mut self, req: Request, client: Client) -> BoxFuture<'a, Result<Response>> {
|
||||
if let Some((current, next)) = self.next_middleware.split_first() {
|
||||
self.next_middleware = next;
|
||||
current.handle(req, client, self)
|
||||
|
|
|
@ -5,19 +5,17 @@
|
|||
//! ```no_run
|
||||
//! # #[async_std::main]
|
||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
//! let mut res = surf::get("https://httpbin.org/redirect/2")
|
||||
//! .middleware(surf::middleware::Redirect::default())
|
||||
//! .await?;
|
||||
//! let req = surf::get("https://httpbin.org/redirect/2");
|
||||
//! let client = surf::client().middleware(surf::middleware::Redirect::new(5));
|
||||
//! let mut res = client.send(req).await?;
|
||||
//! dbg!(res.body_string().await?);
|
||||
//! # Ok(()) }
|
||||
//! ```
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::http::{headers, StatusCode};
|
||||
use crate::middleware::{HttpClient, Middleware, Next, Request, Response};
|
||||
use crate::middleware::{Middleware, Next, Request, Response};
|
||||
use crate::url::Url;
|
||||
use crate::Result;
|
||||
use crate::{Client, Result};
|
||||
|
||||
// List of acceptible 300-series redirect codes.
|
||||
const REDIRECT_CODES: &[StatusCode] = &[
|
||||
|
@ -63,9 +61,9 @@ impl Redirect {
|
|||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let mut res = surf::get("https://httpbin.org/redirect/2")
|
||||
/// .middleware(surf::middleware::Redirect::new(5))
|
||||
/// .await?;
|
||||
/// let req = surf::get("https://httpbin.org/redirect/2");
|
||||
/// let client = surf::client().middleware(surf::middleware::Redirect::new(5));
|
||||
/// let mut res = client.send(req).await?;
|
||||
/// dbg!(res.body_string().await?);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
|
@ -77,12 +75,7 @@ impl Redirect {
|
|||
#[async_trait::async_trait]
|
||||
impl Middleware for Redirect {
|
||||
#[allow(missing_doc_code_examples)]
|
||||
async fn handle(
|
||||
&self,
|
||||
mut req: Request,
|
||||
client: Arc<dyn HttpClient>,
|
||||
next: Next<'_>,
|
||||
) -> Result<Response> {
|
||||
async fn handle(&self, mut req: Request, client: Client, next: Next<'_>) -> Result<Response> {
|
||||
let mut redirect_count: u8 = 0;
|
||||
|
||||
// Note(Jeremiah): This is not ideal.
|
||||
|
@ -105,7 +98,7 @@ impl Middleware for Redirect {
|
|||
let res: Response = client.send(r).await?;
|
||||
if REDIRECT_CODES.contains(&res.status()) {
|
||||
if let Some(location) = res.header(headers::LOCATION) {
|
||||
*req.url_mut() = Url::parse(location.last().as_str())?;
|
||||
*req.as_mut().url_mut() = Url::parse(location.last().as_str())?;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::Request;
|
||||
use crate::http::Method;
|
||||
use crate::RequestBuilder;
|
||||
|
||||
/// Perform a one-off `GET` request.
|
||||
///
|
||||
|
@ -28,9 +28,9 @@ use crate::http::Method;
|
|||
/// let string = surf::get("https://httpbin.org/get").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn get(uri: impl AsRef<str>) -> Request {
|
||||
pub fn get(uri: impl AsRef<str>) -> RequestBuilder {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::new(Method::Get, uri)
|
||||
RequestBuilder::new(Method::Get, uri)
|
||||
}
|
||||
|
||||
/// Perform a one-off `HEAD` request.
|
||||
|
@ -69,9 +69,9 @@ pub fn get(uri: impl AsRef<str>) -> Request {
|
|||
/// let string = surf::head("https://httpbin.org/head").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn head(uri: impl AsRef<str>) -> Request {
|
||||
pub fn head(uri: impl AsRef<str>) -> RequestBuilder {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::new(Method::Head, uri)
|
||||
RequestBuilder::new(Method::Head, uri)
|
||||
}
|
||||
|
||||
/// Perform a one-off `POST` request.
|
||||
|
@ -127,9 +127,9 @@ pub fn head(uri: impl AsRef<str>) -> Request {
|
|||
/// let string = surf::post("https://httpbin.org/post").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn post(uri: impl AsRef<str>) -> Request {
|
||||
pub fn post(uri: impl AsRef<str>) -> RequestBuilder {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::new(Method::Post, uri)
|
||||
RequestBuilder::new(Method::Post, uri)
|
||||
}
|
||||
|
||||
/// Perform a one-off `PUT` request.
|
||||
|
@ -163,9 +163,9 @@ pub fn post(uri: impl AsRef<str>) -> Request {
|
|||
/// let string = surf::put("https://httpbin.org/put").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn put(uri: impl AsRef<str>) -> Request {
|
||||
pub fn put(uri: impl AsRef<str>) -> RequestBuilder {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::new(Method::Put, uri)
|
||||
RequestBuilder::new(Method::Put, uri)
|
||||
}
|
||||
|
||||
/// Perform a one-off `DELETE` request.
|
||||
|
@ -194,9 +194,9 @@ pub fn put(uri: impl AsRef<str>) -> Request {
|
|||
/// let string = surf::delete("https://httpbin.org/delete").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn delete(uri: impl AsRef<str>) -> Request {
|
||||
pub fn delete(uri: impl AsRef<str>) -> RequestBuilder {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::new(Method::Delete, uri)
|
||||
RequestBuilder::new(Method::Delete, uri)
|
||||
}
|
||||
|
||||
/// Perform a one-off `CONNECT` request.
|
||||
|
@ -234,9 +234,9 @@ pub fn delete(uri: impl AsRef<str>) -> Request {
|
|||
/// let string = surf::connect("https://httpbin.org/connect").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn connect(uri: impl AsRef<str>) -> Request {
|
||||
pub fn connect(uri: impl AsRef<str>) -> RequestBuilder {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::new(Method::Connect, uri)
|
||||
RequestBuilder::new(Method::Connect, uri)
|
||||
}
|
||||
|
||||
/// Perform a one-off `OPTIONS` request.
|
||||
|
@ -267,9 +267,9 @@ pub fn connect(uri: impl AsRef<str>) -> Request {
|
|||
/// let string = surf::options("https://httpbin.org/options").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn options(uri: impl AsRef<str>) -> Request {
|
||||
pub fn options(uri: impl AsRef<str>) -> RequestBuilder {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::new(Method::Options, uri)
|
||||
RequestBuilder::new(Method::Options, uri)
|
||||
}
|
||||
|
||||
/// Perform a one-off `TRACE` request.
|
||||
|
@ -304,9 +304,9 @@ pub fn options(uri: impl AsRef<str>) -> Request {
|
|||
/// let string = surf::trace("https://httpbin.org/trace").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn trace(uri: impl AsRef<str>) -> Request {
|
||||
pub fn trace(uri: impl AsRef<str>) -> RequestBuilder {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::new(Method::Trace, uri)
|
||||
RequestBuilder::new(Method::Trace, uri)
|
||||
}
|
||||
|
||||
/// Perform a one-off `PATCH` request.
|
||||
|
@ -347,7 +347,7 @@ pub fn trace(uri: impl AsRef<str>) -> Request {
|
|||
/// let string = surf::patch("https://httpbin.org/patch").recv_string().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn patch(uri: impl AsRef<str>) -> Request {
|
||||
pub fn patch(uri: impl AsRef<str>) -> RequestBuilder {
|
||||
let uri = uri.as_ref().parse().unwrap();
|
||||
Request::new(Method::Patch, uri)
|
||||
RequestBuilder::new(Method::Patch, uri)
|
||||
}
|
||||
|
|
439
src/request.rs
439
src/request.rs
|
@ -3,43 +3,24 @@ use crate::http::{
|
|||
headers::{self, HeaderName, HeaderValues, ToHeaderValues},
|
||||
Body, Error, Method, Mime,
|
||||
};
|
||||
use crate::middleware::{Middleware, Next};
|
||||
use crate::Response;
|
||||
use crate::RequestBuilder;
|
||||
|
||||
use futures_util::future::BoxFuture;
|
||||
use http_client::{self, HttpClient};
|
||||
use serde::Serialize;
|
||||
use url::Url;
|
||||
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::ops::Index;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[cfg(all(feature = "native-client", not(feature = "h1-client")))]
|
||||
use http_client::native::NativeClient;
|
||||
|
||||
#[cfg(feature = "h1-client")]
|
||||
use http_client::h1::H1Client;
|
||||
|
||||
/// An HTTP request, returns a `Response`.
|
||||
#[derive(Clone)]
|
||||
pub struct Request {
|
||||
/// Holds a `http_client::HttpClient` implementation.
|
||||
client: Option<Arc<dyn HttpClient>>,
|
||||
/// Holds the state of the request.
|
||||
req: Option<http_client::Request>,
|
||||
/// Holds the inner middleware.
|
||||
middleware: Option<Vec<Arc<dyn Middleware>>>,
|
||||
/// Holds the state of the `impl Future`.
|
||||
fut: Option<BoxFuture<'static, Result<Response, Error>>>,
|
||||
/// Holds a reference to the Url
|
||||
url: Url,
|
||||
req: http_client::Request,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "native-client", feature = "h1-client"))]
|
||||
impl Request {
|
||||
/// Create a new instance.
|
||||
///
|
||||
|
@ -57,58 +38,40 @@ impl Request {
|
|||
///
|
||||
/// let method = Method::Get;
|
||||
/// let url = Url::parse("https://httpbin.org/get")?;
|
||||
/// let string = surf::Request::new(method, url).recv_string().await?;
|
||||
/// let req = surf::Request::new(method, url);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn new(method: Method, url: Url) -> Self {
|
||||
#[cfg(all(feature = "native-client", not(feature = "h1-client")))]
|
||||
let client = NativeClient::new();
|
||||
#[cfg(feature = "h1-client")]
|
||||
let client = H1Client::new();
|
||||
Self::with_client(method, url, Arc::new(client))
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Create a new instance with an `HttpClient` instance.
|
||||
// TODO(yw): hidden from docs until we make the traits public.
|
||||
#[doc(hidden)]
|
||||
#[allow(missing_doc_code_examples)]
|
||||
pub fn with_client(method: Method, url: Url, client: Arc<dyn HttpClient>) -> Self {
|
||||
let req = http_client::Request::new(method, url.clone());
|
||||
let client = Self {
|
||||
fut: None,
|
||||
client: Some(client),
|
||||
req: Some(req),
|
||||
url,
|
||||
middleware: Some(vec![]),
|
||||
};
|
||||
|
||||
#[cfg(feature = "middleware-logger")]
|
||||
let client = client.middleware(crate::middleware::Logger::new());
|
||||
|
||||
client
|
||||
let req = http_client::Request::new(method, url);
|
||||
Self { req }
|
||||
}
|
||||
|
||||
/// Push middleware onto the middleware stack.
|
||||
/// Begin a chained request builder. For more details, see [RequestBuilder](crate::RequestBuilder)
|
||||
///
|
||||
/// See the [middleware] submodule for more information on middleware.
|
||||
///
|
||||
/// [middleware]: ../middleware/index.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # Example:
|
||||
/// ```rust
|
||||
/// # use surf::url::Url;
|
||||
/// # use surf::{http, Request};
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let res = surf::get("https://httpbin.org/get")
|
||||
/// .middleware(surf::middleware::Redirect::default())
|
||||
/// .await?;
|
||||
/// # Ok(()) }
|
||||
/// let url = Url::parse("https://httpbin.org/post")?;
|
||||
/// let mut request = Request::builder(http::Method::Post, url.clone())
|
||||
/// .body("<html>hi</html>")
|
||||
/// .header("custom-header", "value")
|
||||
/// .content_type(http::mime::HTML)
|
||||
/// .build();
|
||||
///
|
||||
/// assert_eq!(request.take_body().into_string().await.unwrap(), "<html>hi</html>");
|
||||
/// assert_eq!(request.method(), http::Method::Post);
|
||||
/// assert_eq!(request.url(), &url);
|
||||
/// assert_eq!(request["custom-header"], "value");
|
||||
/// assert_eq!(request["content-type"], "text/html;charset=utf-8");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn middleware(mut self, mw: impl Middleware) -> Self {
|
||||
self.middleware.as_mut().unwrap().push(Arc::new(mw));
|
||||
self
|
||||
#[must_use]
|
||||
pub fn builder(method: Method, url: Url) -> RequestBuilder {
|
||||
RequestBuilder::new(method, url)
|
||||
}
|
||||
|
||||
/// Get the URL querystring.
|
||||
|
@ -124,7 +87,7 @@ impl Request {
|
|||
/// page: u32
|
||||
/// }
|
||||
///
|
||||
/// let req = surf::get("https://httpbin.org/get?page=2");
|
||||
/// let req = surf::get("https://httpbin.org/get?page=2").build();
|
||||
/// let Index { page } = req.query()?;
|
||||
/// assert_eq!(page, 2);
|
||||
/// # Ok(()) }
|
||||
|
@ -132,7 +95,8 @@ impl Request {
|
|||
pub fn query<T: serde::de::DeserializeOwned>(&self) -> Result<T, Error> {
|
||||
use std::io::{Error, ErrorKind};
|
||||
let query = self
|
||||
.url
|
||||
.req
|
||||
.url()
|
||||
.query()
|
||||
.ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
|
||||
Ok(serde_urlencoded::from_str(query)?)
|
||||
|
@ -152,22 +116,22 @@ impl Request {
|
|||
/// }
|
||||
///
|
||||
/// let query = Index { page: 2 };
|
||||
/// let req = surf::get("https://httpbin.org/get").set_query(&query)?;
|
||||
/// let mut req = surf::get("https://httpbin.org/get").build();
|
||||
/// req.set_query(&query)?;
|
||||
/// assert_eq!(req.url().query(), Some("page=2"));
|
||||
/// assert_eq!(req.as_ref().url().as_str(), "https://httpbin.org/get?page=2");
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn set_query(
|
||||
mut self,
|
||||
&mut self,
|
||||
query: &(impl Serialize + ?Sized),
|
||||
) -> Result<Self, serde_urlencoded::ser::Error> {
|
||||
) -> Result<(), serde_urlencoded::ser::Error> {
|
||||
let query = serde_urlencoded::to_string(query)?;
|
||||
self.url.set_query(Some(&query));
|
||||
self.req.url_mut().set_query(Some(&query));
|
||||
|
||||
let req = self.req.as_mut().unwrap();
|
||||
*req.url_mut() = self.url.clone();
|
||||
*self.req.url_mut() = self.req.url().clone();
|
||||
|
||||
Ok(self)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get an HTTP header.
|
||||
|
@ -177,19 +141,18 @@ impl Request {
|
|||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let req = surf::get("https://httpbin.org/get").set_header("X-Requested-With", "surf");
|
||||
/// let mut req = surf::get("https://httpbin.org/get").build();
|
||||
/// req.set_header("X-Requested-With", "surf");
|
||||
/// assert_eq!(req.header("X-Requested-With").unwrap(), "surf");
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn header(&self, key: impl Into<HeaderName>) -> Option<&HeaderValues> {
|
||||
let req = self.req.as_ref().unwrap();
|
||||
req.header(key)
|
||||
self.req.header(key)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a header.
|
||||
pub fn header_mut(&mut self, name: impl Into<HeaderName>) -> Option<&mut HeaderValues> {
|
||||
let req = self.req.as_mut().unwrap();
|
||||
req.header_mut(name)
|
||||
self.req.header_mut(name)
|
||||
}
|
||||
|
||||
/// Set an HTTP header.
|
||||
|
@ -198,8 +161,7 @@ impl Request {
|
|||
name: impl Into<HeaderName>,
|
||||
values: impl ToHeaderValues,
|
||||
) -> Option<HeaderValues> {
|
||||
let req = self.req.as_mut().unwrap();
|
||||
req.insert_header(name, values)
|
||||
self.req.insert_header(name, values)
|
||||
}
|
||||
|
||||
/// Append a header to the headers.
|
||||
|
@ -207,43 +169,37 @@ impl Request {
|
|||
/// Unlike `insert` this function will not override the contents of a header, but insert a
|
||||
/// header if there aren't any. Or else append to the existing list of headers.
|
||||
pub fn append_header(&mut self, name: impl Into<HeaderName>, values: impl ToHeaderValues) {
|
||||
let req = self.req.as_mut().unwrap();
|
||||
req.append_header(name, values)
|
||||
self.req.append_header(name, values)
|
||||
}
|
||||
|
||||
/// Remove a header.
|
||||
pub fn remove_header(&mut self, name: impl Into<HeaderName>) -> Option<HeaderValues> {
|
||||
let req = self.req.as_mut().unwrap();
|
||||
req.remove_header(name)
|
||||
self.req.remove_header(name)
|
||||
}
|
||||
|
||||
/// An iterator visiting all header pairs in arbitrary order.
|
||||
#[must_use]
|
||||
pub fn iter(&self) -> headers::Iter<'_> {
|
||||
let req = self.req.as_ref().unwrap();
|
||||
req.iter()
|
||||
self.req.iter()
|
||||
}
|
||||
|
||||
/// An iterator visiting all header pairs in arbitrary order, with mutable references to the
|
||||
/// values.
|
||||
#[must_use]
|
||||
pub fn iter_mut(&mut self) -> headers::IterMut<'_> {
|
||||
let req = self.req.as_mut().unwrap();
|
||||
req.iter_mut()
|
||||
self.req.iter_mut()
|
||||
}
|
||||
|
||||
/// An iterator visiting all header names in arbitrary order.
|
||||
#[must_use]
|
||||
pub fn header_names(&self) -> headers::Names<'_> {
|
||||
let req = self.req.as_ref().unwrap();
|
||||
req.header_names()
|
||||
self.req.header_names()
|
||||
}
|
||||
|
||||
/// An iterator visiting all header values in arbitrary order.
|
||||
#[must_use]
|
||||
pub fn header_values(&self) -> headers::Values<'_> {
|
||||
let req = self.req.as_ref().unwrap();
|
||||
req.header_values()
|
||||
self.req.header_values()
|
||||
}
|
||||
|
||||
/// Set an HTTP header.
|
||||
|
@ -253,24 +209,24 @@ impl Request {
|
|||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let req = surf::get("https://httpbin.org/get").set_header("X-Requested-With", "surf");
|
||||
/// let mut req = surf::get("https://httpbin.org/get").build();
|
||||
/// req.set_header("X-Requested-With", "surf");
|
||||
/// assert_eq!(req.header("X-Requested-With").unwrap(), "surf");
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn set_header(mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) -> Self {
|
||||
self.req.as_mut().unwrap().insert_header(key, value);
|
||||
self
|
||||
pub fn set_header(&mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) {
|
||||
self.insert_header(key, value);
|
||||
}
|
||||
|
||||
/// Get a request extension value.
|
||||
#[must_use]
|
||||
pub fn ext<T: Send + Sync + 'static>(&self) -> Option<&T> {
|
||||
self.req.as_ref().unwrap().ext().get()
|
||||
self.req.ext().get()
|
||||
}
|
||||
|
||||
/// Set a request extension value.
|
||||
pub fn set_ext<T: Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
|
||||
self.req.as_mut().unwrap().ext_mut().insert(val)
|
||||
self.req.ext_mut().insert(val)
|
||||
}
|
||||
|
||||
/// Get the request HTTP method.
|
||||
|
@ -280,13 +236,12 @@ impl Request {
|
|||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let req = surf::get("https://httpbin.org/get");
|
||||
/// let req = surf::get("https://httpbin.org/get").build();
|
||||
/// assert_eq!(req.method(), surf::http::Method::Get);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn method(&self) -> Method {
|
||||
let req = self.req.as_ref().unwrap();
|
||||
req.method()
|
||||
self.req.method()
|
||||
}
|
||||
|
||||
/// Get the request url.
|
||||
|
@ -297,12 +252,12 @@ impl Request {
|
|||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// use surf::url::Url;
|
||||
/// let req = surf::get("https://httpbin.org/get");
|
||||
/// let req = surf::get("https://httpbin.org/get").build();
|
||||
/// assert_eq!(req.url(), &Url::parse("https://httpbin.org/get")?);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn url(&self) -> &Url {
|
||||
&self.url
|
||||
self.req.url()
|
||||
}
|
||||
|
||||
/// Get the request content type as a `Mime`.
|
||||
|
@ -317,42 +272,15 @@ impl Request {
|
|||
/// method to bypass any checks.
|
||||
///
|
||||
/// [`set_header`]: #method.set_header
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// use surf::http::mime;
|
||||
/// let req = surf::post("https://httpbin.org/get")
|
||||
/// .set_content_type(mime::FORM);
|
||||
/// assert_eq!(req.content_type(), Some(mime::FORM));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn content_type(&self) -> Option<Mime> {
|
||||
let req = self.req.as_ref().unwrap();
|
||||
req.content_type()
|
||||
self.req.content_type()
|
||||
}
|
||||
|
||||
/// Set the request content type from a `Mime`.
|
||||
///
|
||||
/// [Read more on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// use surf::http::mime;
|
||||
/// let req = surf::post("https://httpbin.org/get")
|
||||
/// .set_content_type(mime::FORM);
|
||||
/// assert_eq!(req.content_type(), Some(mime::FORM));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn set_content_type(mut self, mime: Mime) -> Self {
|
||||
let req = self.req.as_mut().unwrap();
|
||||
req.set_content_type(mime);
|
||||
self
|
||||
pub fn set_content_type(&mut self, mime: Mime) {
|
||||
self.req.set_content_type(mime);
|
||||
}
|
||||
|
||||
/// Get the length of the body stream, if it has been set.
|
||||
|
@ -362,15 +290,13 @@ impl Request {
|
|||
/// value to decide whether to use `Chunked` encoding, or set the
|
||||
/// response length.
|
||||
pub fn len(&self) -> Option<usize> {
|
||||
let req = self.req.as_ref().unwrap();
|
||||
req.len()
|
||||
self.req.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if the set length of the body stream is zero, `false`
|
||||
/// otherwise.
|
||||
pub fn is_empty(&self) -> Option<bool> {
|
||||
let req = self.req.as_ref().unwrap();
|
||||
req.is_empty()
|
||||
self.req.is_empty()
|
||||
}
|
||||
|
||||
/// Pass an `AsyncRead` stream as the request body.
|
||||
|
@ -378,22 +304,8 @@ impl Request {
|
|||
/// # Mime
|
||||
///
|
||||
/// The encoding is set to `application/octet-stream`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let reader = surf::get("https://httpbin.org/get").await?;
|
||||
/// let body = surf::http::Body::from_reader(reader, None);
|
||||
/// let uri = "https://httpbin.org/post";
|
||||
/// let res = surf::post(uri).set_body(body).await?;
|
||||
/// assert_eq!(res.status(), 200);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn set_body(mut self, body: impl Into<Body>) -> Self {
|
||||
self.req.as_mut().unwrap().set_body(body);
|
||||
self
|
||||
pub fn set_body(&mut self, body: impl Into<Body>) {
|
||||
self.req.set_body(body)
|
||||
}
|
||||
|
||||
/// Take the request body as a `Body`.
|
||||
|
@ -403,7 +315,7 @@ impl Request {
|
|||
///
|
||||
/// This is useful for consuming the body via an AsyncReader or AsyncBufReader.
|
||||
pub fn take_body(&mut self) -> Body {
|
||||
self.req.as_mut().unwrap().take_body()
|
||||
self.req.take_body()
|
||||
}
|
||||
|
||||
/// Pass JSON as the request body.
|
||||
|
@ -415,20 +327,9 @@ impl Request {
|
|||
/// # Errors
|
||||
///
|
||||
/// This method will return an error if the provided data could not be serialized to JSON.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let uri = "https://httpbin.org/post";
|
||||
/// let data = serde_json::json!({ "name": "chashu" });
|
||||
/// let res = surf::post(uri).body_json(&data)?.await?;
|
||||
/// assert_eq!(res.status(), 200);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn body_json(self, json: &impl Serialize) -> crate::Result<Self> {
|
||||
Ok(self.set_body(Body::from_json(json)?))
|
||||
pub fn body_json(&mut self, json: &impl Serialize) -> crate::Result<()> {
|
||||
self.set_body(Body::from_json(json)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pass a string as the request body.
|
||||
|
@ -436,19 +337,7 @@ impl Request {
|
|||
/// # Mime
|
||||
///
|
||||
/// The encoding is set to `text/plain; charset=utf-8`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let uri = "https://httpbin.org/post";
|
||||
/// let data = "hello world".to_string();
|
||||
/// let res = surf::post(uri).body_string(data).await?;
|
||||
/// assert_eq!(res.status(), 200);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn body_string(self, string: String) -> Self {
|
||||
pub fn body_string(&mut self, string: String) {
|
||||
self.set_body(Body::from_string(string))
|
||||
}
|
||||
|
||||
|
@ -457,19 +346,7 @@ impl Request {
|
|||
/// # Mime
|
||||
///
|
||||
/// The encoding is set to `application/octet-stream`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let uri = "https://httpbin.org/post";
|
||||
/// let data = b"hello world";
|
||||
/// let res = surf::post(uri).body_bytes(data).await?;
|
||||
/// assert_eq!(res.status(), 200);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn body_bytes(self, bytes: impl AsRef<[u8]>) -> Self {
|
||||
pub fn body_bytes(&mut self, bytes: impl AsRef<[u8]>) {
|
||||
self.set_body(Body::from(bytes.as_ref()))
|
||||
}
|
||||
|
||||
|
@ -486,20 +363,9 @@ impl Request {
|
|||
/// # Errors
|
||||
///
|
||||
/// This method will return an error if the file couldn't be read.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), http_types::Error> {
|
||||
/// let res = surf::post("https://httpbin.org/post")
|
||||
/// .body_file("README.md").await?
|
||||
/// .await?;
|
||||
/// assert_eq!(res.status(), 200);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub async fn body_file(self, path: impl AsRef<Path>) -> io::Result<Self> {
|
||||
Ok(self.set_body(Body::from_file(path).await?))
|
||||
pub async fn body_file(&mut self, path: impl AsRef<Path>) -> io::Result<()> {
|
||||
self.set_body(Body::from_file(path).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pass a form as the request body.
|
||||
|
@ -511,148 +377,21 @@ impl Request {
|
|||
/// # Errors
|
||||
///
|
||||
/// An error will be returned if the encoding failed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct Body {
|
||||
/// apples: u32
|
||||
/// }
|
||||
///
|
||||
/// let res = surf::post("https://httpbin.org/post")
|
||||
/// .body_form(&Body { apples: 7 })?
|
||||
/// .await?;
|
||||
/// assert_eq!(res.status(), 200);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn body_form(self, form: &impl Serialize) -> crate::Result<Self> {
|
||||
Ok(self.set_body(Body::from_form(form)?))
|
||||
}
|
||||
|
||||
/// Submit the request and get the response body as bytes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let bytes = surf::get("https://httpbin.org/get").recv_bytes().await?;
|
||||
/// assert!(bytes.len() > 0);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub async fn recv_bytes(self) -> Result<Vec<u8>, Error> {
|
||||
let mut req = self.await?;
|
||||
Ok(req.body_bytes().await?)
|
||||
}
|
||||
|
||||
/// Submit the request and get the response body as a string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let string = surf::get("https://httpbin.org/get").recv_string().await?;
|
||||
/// assert!(string.len() > 0);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub async fn recv_string(self) -> Result<String, Error> {
|
||||
let mut req = self.await?;
|
||||
Ok(req.body_string().await?)
|
||||
}
|
||||
|
||||
/// Submit the request and decode the response body from json into a struct.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct Ip {
|
||||
/// ip: String
|
||||
/// }
|
||||
///
|
||||
/// let uri = "https://api.ipify.org?format=json";
|
||||
/// let Ip { ip } = surf::get(uri).recv_json().await?;
|
||||
/// assert!(ip.len() > 10);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub async fn recv_json<T: serde::de::DeserializeOwned>(self) -> Result<T, Error> {
|
||||
let mut req = self.await?;
|
||||
Ok(req.body_json::<T>().await?)
|
||||
}
|
||||
|
||||
/// Submit the request and decode the response body from form encoding into a struct.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Any I/O error encountered while reading the body is immediately returned
|
||||
/// as an `Err`.
|
||||
///
|
||||
/// If the body cannot be interpreted as valid json for the target type `T`,
|
||||
/// an `Err` is returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct Body {
|
||||
/// apples: u32
|
||||
/// }
|
||||
///
|
||||
/// let url = "https://api.example.com/v1/response";
|
||||
/// let Body { apples } = surf::get(url).recv_form().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub async fn recv_form<T: serde::de::DeserializeOwned>(self) -> Result<T, Error> {
|
||||
let mut req = self.await?;
|
||||
Ok(req.body_form::<T>().await?)
|
||||
pub fn body_form(&mut self, form: &impl Serialize) -> crate::Result<()> {
|
||||
self.set_body(Body::from_form(form)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<http::Request> for Request {
|
||||
fn as_ref(&self) -> &http::Request {
|
||||
self.req.as_ref().unwrap()
|
||||
&self.req
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<http::Request> for Request {
|
||||
fn as_mut(&mut self) -> &mut http::Request {
|
||||
self.req.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Request {
|
||||
type Output = Result<Response, Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if self.fut.is_none() {
|
||||
// We can safely unwrap here because this is the only time we take ownership of the
|
||||
// request and middleware stack.
|
||||
let client = self.client.take().unwrap();
|
||||
let middleware = self.middleware.take().unwrap();
|
||||
let req = self.req.take().unwrap();
|
||||
|
||||
self.fut = Some(Box::pin(async move {
|
||||
let next = Next::new(&middleware, &|req, client| {
|
||||
Box::pin(async move { client.send(req).await.map_err(Into::into) })
|
||||
});
|
||||
|
||||
let res = next.run(req, client).await?;
|
||||
Ok(Response::new(res))
|
||||
}));
|
||||
}
|
||||
|
||||
self.fut.as_mut().unwrap().as_mut().poll(cx)
|
||||
&mut self.req
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -662,14 +401,16 @@ impl From<http::Request> for Request {
|
|||
fn from(http_request: http::Request) -> Self {
|
||||
let method = http_request.method();
|
||||
let url = http_request.url().clone();
|
||||
Self::new(method, url).set_body(http_request)
|
||||
let mut req = Self::new(method, url);
|
||||
req.set_body(http_request);
|
||||
req
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<http::Request> for Request {
|
||||
/// Converts a `surf::Request` to an `http::Request`.
|
||||
fn into(self) -> http::Request {
|
||||
self.req.unwrap()
|
||||
self.req
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -687,7 +428,7 @@ impl IntoIterator for Request {
|
|||
/// Returns a iterator of references over the remaining items.
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.req.map(|req| req.into_iter()).unwrap()
|
||||
self.req.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -698,7 +439,7 @@ impl<'a> IntoIterator for &'a Request {
|
|||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.req.as_ref().unwrap().iter()
|
||||
self.req.iter()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -709,7 +450,7 @@ impl<'a> IntoIterator for &'a mut Request {
|
|||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.req.as_mut().unwrap().iter_mut()
|
||||
self.req.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -723,7 +464,7 @@ impl Index<HeaderName> for Request {
|
|||
/// Panics if the name is not present in `Request`.
|
||||
#[inline]
|
||||
fn index(&self, name: HeaderName) -> &HeaderValues {
|
||||
&self.req.as_ref().unwrap()[name]
|
||||
&self.req[name]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -737,6 +478,6 @@ impl Index<&str> for Request {
|
|||
/// Panics if the name is not present in `Request`.
|
||||
#[inline]
|
||||
fn index(&self, name: &str) -> &HeaderValues {
|
||||
&self.req.as_ref().unwrap()[name]
|
||||
&self.req[name]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
use crate::http::{
|
||||
headers::{HeaderName, ToHeaderValues},
|
||||
Body, Method, Mime,
|
||||
};
|
||||
use crate::{Client, Request, Response, Result};
|
||||
|
||||
use futures_util::future::BoxFuture;
|
||||
use url::Url;
|
||||
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
// #[derive(Debug)]
|
||||
|
||||
/// Response Builder
|
||||
///
|
||||
/// Provides an ergonomic way to chain the creation of a response. This is generally accessed through `surf::{method}()`,
|
||||
/// however [`Request::builder`](crate::Request::builder) is also provided.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use surf::url::Url;
|
||||
/// # use surf::{http, Request};
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let mut request = surf::post("https://httpbin.org/post")
|
||||
/// .body("<html>hi</html>")
|
||||
/// .header("custom-header", "value")
|
||||
/// .content_type(http::mime::HTML)
|
||||
/// .build();
|
||||
///
|
||||
/// assert_eq!(request.take_body().into_string().await.unwrap(), "<html>hi</html>");
|
||||
/// assert_eq!(request.method(), http::Method::Post);
|
||||
/// assert_eq!(request.url(), &Url::parse("https://httpbin.org/post")?);
|
||||
/// assert_eq!(request["custom-header"], "value");
|
||||
/// assert_eq!(request["content-type"], "text/html;charset=utf-8");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # use surf::url::Url;
|
||||
/// # use surf::{http, Request};
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let url = Url::parse("https://httpbin.org/post")?;
|
||||
/// let request = Request::builder(http::Method::Post, url).build();
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
pub struct RequestBuilder {
|
||||
/// Holds the state of the request.
|
||||
req: Option<Request>,
|
||||
/// Holds the state of the `impl Future`.
|
||||
fut: Option<BoxFuture<'static, Result<Response>>>,
|
||||
}
|
||||
|
||||
impl RequestBuilder {
|
||||
/// Create a new instance.
|
||||
///
|
||||
/// This method is particularly useful when input URLs might be passed by third parties, and
|
||||
/// you don't want to panic if they're malformed. If URLs are statically encoded, it might be
|
||||
/// easier to use one of the shorthand methods instead.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// use surf::http::Method;
|
||||
/// use surf::url::Url;
|
||||
///
|
||||
/// let method = Method::Get;
|
||||
/// let url = Url::parse("https://httpbin.org/get")?;
|
||||
/// let req = surf::RequestBuilder::new(Method::Get, url).build();
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn new(method: Method, url: Url) -> Self {
|
||||
Self {
|
||||
req: Some(Request::new(method, url)),
|
||||
fut: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a header on the request.
|
||||
/// ```
|
||||
/// let req = surf::get("https://httpbin.org/get").header("header-name", "header-value").build();
|
||||
/// assert_eq!(req["header-name"], "header-value");
|
||||
/// ```
|
||||
pub fn header(mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) -> Self {
|
||||
self.req.as_mut().unwrap().insert_header(key, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the Content-Type header on the request.
|
||||
/// ```
|
||||
/// # use surf::http::mime;
|
||||
/// let req = surf::post("https://httpbin.org/post").content_type(mime::HTML).build();
|
||||
/// assert_eq!(req["content-type"], "text/html;charset=utf-8");
|
||||
/// ```
|
||||
pub fn content_type(mut self, content_type: impl Into<Mime>) -> Self {
|
||||
self.req
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_content_type(content_type.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the body of the request.
|
||||
/// ```
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// use serde_json::json;
|
||||
/// let mut req = surf::post("https://httpbin.org/post").body(json!({ "any": "Into<Body>"})).build();
|
||||
/// assert_eq!(req.take_body().into_string().await.unwrap(), "{\"any\":\"Into<Body>\"}");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn body(mut self, body: impl Into<Body>) -> Self {
|
||||
self.req.as_mut().unwrap().set_body(body);
|
||||
self
|
||||
}
|
||||
|
||||
/// Submit the request and get the response body as bytes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let bytes = surf::get("https://httpbin.org/get").recv_bytes().await?;
|
||||
/// assert!(bytes.len() > 0);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub async fn recv_bytes(self) -> Result<Vec<u8>> {
|
||||
let mut res = self.send().await?;
|
||||
Ok(res.body_bytes().await?)
|
||||
}
|
||||
|
||||
/// Submit the request and get the response body as a string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// let string = surf::get("https://httpbin.org/get").recv_string().await?;
|
||||
/// assert!(string.len() > 0);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub async fn recv_string(self) -> Result<String> {
|
||||
let mut res = self.send().await?;
|
||||
Ok(res.body_string().await?)
|
||||
}
|
||||
|
||||
/// Submit the request and decode the response body from json into a struct.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct Ip {
|
||||
/// ip: String
|
||||
/// }
|
||||
///
|
||||
/// let uri = "https://api.ipify.org?format=json";
|
||||
/// let Ip { ip } = surf::get(uri).recv_json().await?;
|
||||
/// assert!(ip.len() > 10);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub async fn recv_json<T: serde::de::DeserializeOwned>(self) -> Result<T> {
|
||||
let mut res = self.send().await?;
|
||||
Ok(res.body_json::<T>().await?)
|
||||
}
|
||||
|
||||
/// Submit the request and decode the response body from form encoding into a struct.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Any I/O error encountered while reading the body is immediately returned
|
||||
/// as an `Err`.
|
||||
///
|
||||
/// If the body cannot be interpreted as valid json for the target type `T`,
|
||||
/// an `Err` is returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # #[async_std::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct Body {
|
||||
/// apples: u32
|
||||
/// }
|
||||
///
|
||||
/// let url = "https://api.example.com/v1/response";
|
||||
/// let Body { apples } = surf::get(url).recv_form().await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub async fn recv_form<T: serde::de::DeserializeOwned>(self) -> Result<T> {
|
||||
let mut res = self.send().await?;
|
||||
Ok(res.body_form::<T>().await?)
|
||||
}
|
||||
|
||||
/// Return the constructed `Request`.
|
||||
pub fn build(self) -> Request {
|
||||
self.req.unwrap()
|
||||
}
|
||||
|
||||
/// Create a `Client` and send the constructed `Request` from it.
|
||||
pub fn send(self) -> BoxFuture<'static, Result<Response>> {
|
||||
let client = Client::new();
|
||||
client.send(self.build())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for RequestBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.req, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for RequestBuilder {
|
||||
type Output = Result<Response>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if self.fut.is_none() {
|
||||
let req = self.req.take().unwrap();
|
||||
let client = Client::new();
|
||||
self.fut = Some(client.send(req));
|
||||
}
|
||||
|
||||
// We can safely unwrap here because this is the only time we take ownership of the
|
||||
// request and middleware stack.
|
||||
self.fut.as_mut().unwrap().as_mut().poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Request> for RequestBuilder {
|
||||
/// Converts a `surf::RequestBuilder` to a `surf::Request`.
|
||||
fn into(self) -> Request {
|
||||
self.build()
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
use mockito::mock;
|
||||
|
||||
use http_types::Body;
|
||||
|
||||
#[async_std::test]
|
||||
async fn post_json() -> Result<(), http_types::Error> {
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
|
@ -17,8 +19,8 @@ async fn post_json() -> Result<(), http_types::Error> {
|
|||
.with_body(&serde_json::to_string(&cat)?[..])
|
||||
.create();
|
||||
let res = surf::post(mockito::server_url())
|
||||
.set_header("Accept", "application/json")
|
||||
.body_json(&cat)?
|
||||
.header("Accept", "application/json")
|
||||
.body(Body::from_json(&cat)?)
|
||||
.await?;
|
||||
m.assert();
|
||||
assert_eq!(res.status(), http_types::StatusCode::Ok);
|
||||
|
@ -47,10 +49,10 @@ async fn get_google() -> Result<(), http_types::Error> {
|
|||
femme::start(log::LevelFilter::Trace).ok();
|
||||
|
||||
let url = "https://www.google.com";
|
||||
let mut req = surf::get(url).await?;
|
||||
assert_eq!(req.status(), http_types::StatusCode::Ok);
|
||||
let mut res = surf::get(url).await?;
|
||||
assert_eq!(res.status(), http_types::StatusCode::Ok);
|
||||
|
||||
let msg = req.body_bytes().await?;
|
||||
let msg = res.body_bytes().await?;
|
||||
let msg = String::from_utf8_lossy(&msg);
|
||||
println!("recieved: '{}'", msg);
|
||||
assert!(msg.contains("<!doctype html>"));
|
||||
|
@ -72,10 +74,10 @@ async fn get_github() -> Result<(), http_types::Error> {
|
|||
femme::start(log::LevelFilter::Trace).ok();
|
||||
|
||||
let url = "https://raw.githubusercontent.com/http-rs/surf/6627d9fc15437aea3c0a69e0b620ae7769ea6765/LICENSE-MIT";
|
||||
let mut req = surf::get(url).await?;
|
||||
assert_eq!(req.status(), http_types::StatusCode::Ok, "{:?}", &req);
|
||||
let mut res = surf::get(url).await?;
|
||||
assert_eq!(res.status(), http_types::StatusCode::Ok, "{:?}", &res);
|
||||
|
||||
let msg = req.body_string().await?;
|
||||
let msg = res.body_string().await?;
|
||||
|
||||
assert_eq!(
|
||||
msg,
|
||||
|
|
Loading…
Reference in New Issue