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:
Jeremiah Senkpiel 2020-07-09 16:09:04 -07:00
parent 57d03222dc
commit b6113dec3d
14 changed files with 542 additions and 620 deletions

View File

@ -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(())
}

View File

@ -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);

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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?)
}
}

View File

@ -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>;

View File

@ -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(()) }
//! ```

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -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)
}

View File

@ -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]
}
}

252
src/request_builder.rs Normal file
View File

@ -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()
}
}

View File

@ -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,