surf/src/client.rs

625 lines
20 KiB
Rust

use std::convert::TryFrom;
use std::fmt;
use std::sync::Arc;
use crate::http::{Method, Url};
use crate::middleware::{Middleware, Next};
use crate::{Config, HttpClient, Request, RequestBuilder, Response, Result};
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "curl-client")] {
use http_client::isahc::IsahcClient as DefaultClient;
} else if #[cfg(feature = "wasm-client")] {
use http_client::wasm::WasmClient as DefaultClient;
} else if #[cfg(any(feature = "h1-client", feature = "h1-client-rustls", feature = "h1-client-no-tls"))] {
use http_client::h1::H1Client as DefaultClient;
} else if #[cfg(feature = "hyper-client")] {
use http_client::hyper::HyperClient as DefaultClient;
}
}
cfg_if! {
if #[cfg(any(feature = "curl-client", feature = "h1-client", feature = "h1-client-rustls", feature = "hyper-client"))] {
use once_cell::sync::Lazy;
static GLOBAL_CLIENT: Lazy<Arc<DefaultClient>> = Lazy::new(|| Arc::new(DefaultClient::new()));
}
}
/// An HTTP client, capable of sending `Request`s and running a middleware stack.
///
/// Can be optionally set with a base url.
///
/// # Examples
///
/// ```no_run
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// let client = surf::Client::new();
/// 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(()) }
/// ```
pub struct Client {
config: Config,
http_client: Arc<dyn HttpClient>,
/// Holds the middleware stack.
///
/// Note(Fishrock123): We do actually want this structure.
/// The outer Arc allows us to clone in .send() without cloning the array.
/// The Vec allows us to add middleware at runtime.
/// The inner Arc-s allow us to implement Clone without sharing the vector with the parent.
/// We don't use a Mutex around the Vec here because adding a middleware during execution should be an error.
#[allow(clippy::rc_buffer)]
middleware: Arc<Vec<Arc<dyn Middleware>>>,
}
impl Clone for Client {
/// Clones the Client.
///
/// This copies the middleware stack from the original, but shares
/// the `HttpClient` and http client config of the original.
/// Note that individual middleware in the middleware stack are
/// still shared by reference.
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
http_client: self.http_client.clone(),
middleware: Arc::new(self.middleware.iter().cloned().collect()),
}
}
}
impl fmt::Debug for Client {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Client {{}}")
}
}
#[cfg(feature = "default-client")]
impl Default for Client {
fn default() -> Self {
Self::new()
}
}
impl Client {
/// Create a new `Client` instance.
///
/// # Examples
///
/// ```rust
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// let client = surf::Client::new();
///
/// let req = surf::get("https://httpbin.org/get");
/// let res = client.send(req).await?;
/// # Ok(()) }
/// ```
#[cfg(feature = "default-client")]
pub fn new() -> Self {
Self::with_http_client(DefaultClient::new())
}
pub(crate) fn new_shared_or_panic() -> Self {
cfg_if! {
if #[cfg(feature = "default-client")] {
Self::new_shared()
} else {
panic!("default client not configured")
}
}
}
/// Create a new `Client` instance with an `http_client::HttpClient` backend.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "curl-client")] {
/// # use std::sync::Arc;
/// use http_client::isahc::IsahcClient;
/// let client = surf::Client::with_http_client(IsahcClient::new());
/// # }
/// ```
pub fn with_http_client<C: HttpClient>(http_client: C) -> Self {
Self::with_http_client_internal(Arc::new(http_client))
}
fn with_http_client_internal(http_client: Arc<dyn HttpClient>) -> Self {
let client = Self {
config: Config::default(),
http_client,
middleware: Arc::new(vec![]),
};
#[cfg(feature = "middleware-logger")]
let client = client.with(crate::middleware::Logger::new());
client
}
#[cfg(feature = "default-client")]
pub(crate) fn new_shared() -> Self {
cfg_if! {
if #[cfg(any(feature = "curl-client", feature = "h1-client", feature = "h1-client-rustls", feature = "hyper-client"))] {
Self::with_http_client_internal(GLOBAL_CLIENT.clone())
} else {
Self::new()
}
}
}
/// Push middleware onto the middleware stack.
///
/// See the [middleware] submodule for more information on middleware.
///
/// [middleware]: ../middleware/index.html
///
/// # Examples
///
/// ```no_run
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// let req = surf::get("https://httpbin.org/get");
/// let client = surf::client()
/// .with(surf::middleware::Redirect::default());
/// let res = client.send(req).await?;
/// # Ok(()) }
/// ```
pub fn with(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
}
/// Send a `Request` using this client.
///
/// Client middleware is run before per-request middleware.
///
/// # Examples
///
/// ```no_run
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// let req = surf::get("https://httpbin.org/get");
/// let client = surf::client();
/// let res = client.send(req).await?;
/// # Ok(()) }
/// ```
pub async fn send(&self, req: impl Into<Request>) -> Result<Response> {
let mut req: Request = req.into();
let http_client = self.http_client.clone();
let middleware = self.middleware.clone();
let mw_stack = match req.take_middleware() {
Some(req_mw) => {
let mut mw = Vec::with_capacity(middleware.len() + req_mw.len());
mw.extend(middleware.iter().cloned());
mw.extend(req_mw);
Arc::new(mw)
}
None => middleware,
};
let next = Next::new(&mw_stack, &|req, client| {
Box::pin(async move {
let req: http_types::Request = req.into();
client.http_client.send(req).await.map(Into::into)
})
});
let client = Self {
config: self.config.clone(),
http_client,
// Erase the middleware stack for the Client accessible from within middleware.
// This avoids gratuitous circular borrow & logic issues.
middleware: Arc::new(vec![]),
};
let res = next.run(req, client).await?;
Ok(Response::new(res.into()))
}
/// Submit a `Request` and get the response body as bytes.
///
/// # Examples
///
/// ```no_run
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// let req = surf::get("https://httpbin.org/get");
/// let bytes = surf::client().recv_bytes(req).await?;
/// assert!(bytes.len() > 0);
/// # Ok(()) }
/// ```
pub async fn recv_bytes(&self, req: impl Into<Request>) -> Result<Vec<u8>> {
let mut res = self.send(req.into()).await?;
res.body_bytes().await
}
/// Submit a `Request` and get the response body as a string.
///
/// # Examples
///
/// ```no_run
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// let req = surf::get("https://httpbin.org/get");
/// let string = surf::client().recv_string(req).await?;
/// assert!(string.len() > 0);
/// # Ok(()) }
/// ```
pub async fn recv_string(&self, req: impl Into<Request>) -> Result<String> {
let mut res = self.send(req.into()).await?;
res.body_string().await
}
/// Submit a `Request` and decode the response body from json into a struct.
///
/// # Examples
///
/// ```no_run
/// # use serde::{Deserialize, Serialize};
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// #[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 async fn recv_json<T: serde::de::DeserializeOwned>(
&self,
req: impl Into<Request>,
) -> Result<T> {
let mut res = self.send(req.into()).await?;
res.body_json::<T>().await
}
/// Submit a `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() -> surf::Result<()> {
/// #[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 async fn recv_form<T: serde::de::DeserializeOwned>(
&self,
req: impl Into<Request>,
) -> Result<T> {
let mut res = self.send(req.into()).await?;
res.body_form::<T>().await
}
/// Perform an HTTP `GET` 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() -> surf::Result<()> {
/// let client = surf::client();
/// let string = client.get("https://httpbin.org/get").recv_string().await?;
/// # Ok(()) }
/// ```
pub fn get(&self, uri: impl AsRef<str>) -> RequestBuilder {
RequestBuilder::new(Method::Get, self.url(uri)).with_client(self.clone())
}
/// 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.
///
/// # Examples
///
/// ```no_run
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// let client = surf::client();
/// let string = client.head("https://httpbin.org/head").recv_string().await?;
/// # Ok(()) }
/// ```
pub fn head(&self, uri: impl AsRef<str>) -> RequestBuilder {
RequestBuilder::new(Method::Head, self.url(uri)).with_client(self.clone())
}
/// 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.
///
/// # Examples
///
/// ```no_run
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// let client = surf::client();
/// let string = client.post("https://httpbin.org/post").recv_string().await?;
/// # Ok(()) }
/// ```
pub fn post(&self, uri: impl AsRef<str>) -> RequestBuilder {
RequestBuilder::new(Method::Post, self.url(uri)).with_client(self.clone())
}
/// 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.
///
/// # Examples
///
/// ```no_run
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// let client = surf::client();
/// let string = client.put("https://httpbin.org/put").recv_string().await?;
/// # Ok(()) }
/// ```
pub fn put(&self, uri: impl AsRef<str>) -> RequestBuilder {
RequestBuilder::new(Method::Put, self.url(uri)).with_client(self.clone())
}
/// 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.
///
/// # Examples
///
/// ```no_run
/// # #[async_std::main]
/// # async fn main() -> surf::Result<()> {
/// let client = surf::client();
/// let string = client.delete("https://httpbin.org/delete").recv_string().await?;
/// # Ok(()) }
/// ```
pub fn delete(&self, uri: impl AsRef<str>) -> RequestBuilder {
RequestBuilder::new(Method::Delete, self.url(uri)).with_client(self.clone())
}
/// Perform an HTTP `CONNECT` 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() -> surf::Result<()> {
/// let client = surf::client();
/// let string = client.connect("https://httpbin.org/connect").recv_string().await?;
/// # Ok(()) }
/// ```
pub fn connect(&self, uri: impl AsRef<str>) -> RequestBuilder {
RequestBuilder::new(Method::Connect, self.url(uri)).with_client(self.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() -> surf::Result<()> {
/// let client = surf::client();
/// let string = client.options("https://httpbin.org/options").recv_string().await?;
/// # Ok(()) }
/// ```
pub fn options(&self, uri: impl AsRef<str>) -> RequestBuilder {
RequestBuilder::new(Method::Options, self.url(uri)).with_client(self.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() -> surf::Result<()> {
/// let client = surf::client();
/// let string = client.trace("https://httpbin.org/trace").recv_string().await?;
/// # Ok(()) }
/// ```
pub fn trace(&self, uri: impl AsRef<str>) -> RequestBuilder {
RequestBuilder::new(Method::Trace, self.url(uri)).with_client(self.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() -> surf::Result<()> {
/// let client = surf::client();
/// let string = client.patch("https://httpbin.org/patch").recv_string().await?;
/// # Ok(()) }
/// ```
pub fn patch(&self, uri: impl AsRef<str>) -> RequestBuilder {
RequestBuilder::new(Method::Patch, self.url(uri)).with_client(self.clone())
}
/// Perform a HTTP request with the given verb 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() -> surf::Result<()> {
/// use http_types::Method;
/// let client = surf::client();
/// let req = client.request(Method::Get, "http://httpbin.org/get");
/// let res = client.send(req).await?;
/// # Ok(()) }
/// ```
pub fn request(&self, verb: Method, uri: impl AsRef<str>) -> RequestBuilder {
RequestBuilder::new(verb, self.url(uri)).with_client(self.clone())
}
/// Sets the base URL for this client. All request URLs will be relative to this URL.
///
/// Note: a trailing slash is significant.
/// Without it, the last path component is considered to be a “file” name
/// to be removed to get at the “directory” that is used as the base.
///
/// # Examples
/// ```no_run
/// # use http_types::Url;
/// # fn main() -> http_types::Result<()> { async_std::task::block_on(async {
/// let mut client = surf::client();
/// client.set_base_url(Url::parse("http://example.com/api/v1/")?);
/// client.get("posts.json").recv_json().await?; /// http://example.com/api/v1/posts.json
/// # Ok(()) }) }
/// ```
#[deprecated(since = "6.5.0", note = "Please use `Config` instead")]
pub fn set_base_url(&mut self, base: Url) {
self.config.base_url = Some(base);
}
/// Get the current configuration.
pub fn config(&self) -> &Config {
&self.config
}
// private function to generate a url based on the base_path
fn url(&self, uri: impl AsRef<str>) -> Url {
match &self.config.base_url {
None => uri.as_ref().parse().unwrap(),
Some(base) => base.join(uri.as_ref()).unwrap(),
}
}
}
impl TryFrom<Config> for Client {
#[cfg(feature = "default-client")]
type Error = <DefaultClient as TryFrom<http_client::Config>>::Error;
#[cfg(not(feature = "default-client"))]
type Error = std::convert::Infallible;
fn try_from(mut config: Config) -> std::result::Result<Self, Self::Error> {
let http_client = match config.http_client.take() {
Some(client) => client,
#[cfg(feature = "default-client")]
None => Arc::new(DefaultClient::try_from(config.http_config.clone())?),
#[cfg(not(feature = "default-client"))]
None => panic!("Config without an http client provided to Surf configured without a default client.")
};
Ok(Client {
config,
http_client,
middleware: Arc::new(vec![]),
})
}
}
#[cfg(test)]
mod client_tests {
use std::convert::TryInto;
use super::Client;
use super::Config;
use crate::Url;
#[test]
fn base_url() {
let base_url = Url::parse("http://example.com/api/v1/").unwrap();
let client: Client = Config::new().set_base_url(base_url).try_into().unwrap();
let url = client.url("posts.json");
assert_eq!(url.as_str(), "http://example.com/api/v1/posts.json");
}
}