//! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem. //! //! # Making a GET request //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { //! let mut client = awc::Client::default(); //! let response = client.get("http://www.rust-lang.org") // <- Create request builder //! .insert_header(("User-Agent", "Actix-web")) //! .send() // <- Send http request //! .await?; //! //! println!("Response: {:?}", response); //! # Ok(()) //! # } //! ``` //! //! # Making POST requests //! ## Raw body contents //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { //! let mut client = awc::Client::default(); //! let response = client.post("http://httpbin.org/post") //! .send_body("Raw body contents") //! .await?; //! # Ok(()) //! # } //! ``` //! //! ## Forms //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { //! let params = [("foo", "bar"), ("baz", "quux")]; //! //! let mut client = awc::Client::default(); //! let response = client.post("http://httpbin.org/post") //! .send_form(¶ms) //! .await?; //! # Ok(()) //! # } //! ``` //! //! ## JSON //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { //! let request = serde_json::json!({ //! "lang": "rust", //! "body": "json" //! }); //! //! let mut client = awc::Client::default(); //! let response = client.post("http://httpbin.org/post") //! .send_json(&request) //! .await?; //! # Ok(()) //! # } //! ``` //! //! # Response Compression //! All [official][iana-encodings] and common content encoding codecs are supported, optionally. //! //! The `Accept-Encoding` header will automatically be populated with enabled codecs and added to //! outgoing requests, allowing servers to select their `Content-Encoding` accordingly. //! //! Feature flags enable these codecs according to the table below. By default, all `compress-*` //! features are enabled. //! //! | Feature | Codecs | //! | ----------------- | ------------- | //! | `compress-brotli` | brotli | //! | `compress-gzip` | gzip, deflate | //! | `compress-zstd` | zstd | //! //! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding //! //! # WebSocket support //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), Box> { //! use futures_util::{sink::SinkExt, stream::StreamExt}; //! let (_resp, mut connection) = awc::Client::new() //! .ws("ws://echo.websocket.org") //! .connect() //! .await?; //! //! connection //! .send(awc::ws::Message::Text("Echo".into())) //! .await?; //! let response = connection.next().await.unwrap()?; //! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into())); //! # Ok(()) //! # } //! ``` #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] #![allow( clippy::type_complexity, clippy::borrow_interior_mutable_const, clippy::needless_doctest_main )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] mod any_body; mod builder; mod client; mod connect; pub mod error; mod frozen; pub mod middleware; mod request; mod response; mod sender; pub mod test; pub mod ws; // TODO: hmmmmmm pub use actix_http as http; #[cfg(feature = "cookies")] pub use cookie; pub use self::builder::ClientBuilder; pub use self::client::Connector; pub use self::connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse}; pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::request::ClientRequest; pub use self::response::{ClientResponse, JsonBody, MessageBody}; pub use self::sender::SendClientRequest; use std::{convert::TryFrom, rc::Rc, time::Duration}; use actix_http::{error::HttpError, header::HeaderMap, Method, RequestHead, Uri}; use actix_rt::net::TcpStream; use actix_service::Service; use self::client::{ConnectInfo, TcpConnectError, TcpConnection}; pub(crate) type BoxError = Box; /// An asynchronous HTTP and WebSocket client. /// /// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU /// and memory usage. /// /// # Examples /// ``` /// use awc::Client; /// /// #[actix_rt::main] /// async fn main() { /// let mut client = Client::default(); /// /// let res = client.get("http://www.rust-lang.org") /// .insert_header(("User-Agent", "my-app/1.2")) /// .send() /// .await; /// /// println!("Response: {:?}", res); /// } /// ``` #[derive(Clone)] pub struct Client(ClientConfig); #[derive(Clone)] pub(crate) struct ClientConfig { pub(crate) connector: BoxConnectorService, pub(crate) default_headers: Rc, pub(crate) timeout: Option, } impl Default for Client { fn default() -> Self { ClientBuilder::new().finish() } } impl Client { /// Create new client instance with default settings. pub fn new() -> Client { Client::default() } /// Create `Client` builder. /// This function is equivalent of `ClientBuilder::new()`. pub fn builder() -> ClientBuilder< impl Service< ConnectInfo, Response = TcpConnection, Error = TcpConnectError, > + Clone, > { ClientBuilder::new() } /// Construct HTTP request. pub fn request(&self, method: Method, url: U) -> ClientRequest where Uri: TryFrom, >::Error: Into, { let mut req = ClientRequest::new(method, url, self.0.clone()); for header in self.0.default_headers.iter() { // header map is empty // TODO: probably append instead req = req.insert_header_if_none(header); } req } /// Create `ClientRequest` from `RequestHead` /// /// It is useful for proxy requests. This implementation /// copies all headers and the method. pub fn request_from(&self, url: U, head: &RequestHead) -> ClientRequest where Uri: TryFrom, >::Error: Into, { let mut req = self.request(head.method.clone(), url); for header in head.headers.iter() { req = req.insert_header_if_none(header); } req } /// Construct HTTP *GET* request. pub fn get(&self, url: U) -> ClientRequest where Uri: TryFrom, >::Error: Into, { self.request(Method::GET, url) } /// Construct HTTP *HEAD* request. pub fn head(&self, url: U) -> ClientRequest where Uri: TryFrom, >::Error: Into, { self.request(Method::HEAD, url) } /// Construct HTTP *PUT* request. pub fn put(&self, url: U) -> ClientRequest where Uri: TryFrom, >::Error: Into, { self.request(Method::PUT, url) } /// Construct HTTP *POST* request. pub fn post(&self, url: U) -> ClientRequest where Uri: TryFrom, >::Error: Into, { self.request(Method::POST, url) } /// Construct HTTP *PATCH* request. pub fn patch(&self, url: U) -> ClientRequest where Uri: TryFrom, >::Error: Into, { self.request(Method::PATCH, url) } /// Construct HTTP *DELETE* request. pub fn delete(&self, url: U) -> ClientRequest where Uri: TryFrom, >::Error: Into, { self.request(Method::DELETE, url) } /// Construct HTTP *OPTIONS* request. pub fn options(&self, url: U) -> ClientRequest where Uri: TryFrom, >::Error: Into, { self.request(Method::OPTIONS, url) } /// Initialize a WebSocket connection. /// Returns a WebSocket connection builder. pub fn ws(&self, url: U) -> ws::WebsocketsRequest where Uri: TryFrom, >::Error: Into, { let mut req = ws::WebsocketsRequest::new(url, self.0.clone()); for (key, value) in self.0.default_headers.iter() { req.head.headers.insert(key.clone(), value.clone()); } req } /// Get default HeaderMap of Client. /// /// Returns Some(&mut HeaderMap) when Client object is unique /// (No other clone of client exists at the same time). pub fn headers(&mut self) -> Option<&mut HeaderMap> { Rc::get_mut(&mut self.0.default_headers) } }