diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 4656df39..b24ae50b 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -6,6 +6,10 @@ * Re-export `actix_http::client::Connector` +* Session wide headers + +* Session wide basic and bearer auth + ## [0.1.0-alpha.1] - 2019-03-28 diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 562ff241..d53d0d44 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -3,13 +3,11 @@ use std::fmt; use std::rc::Rc; use actix_http::client::{ConnectError, Connection, Connector}; -use actix_http::http::{ - header::IntoHeaderValue, HeaderMap, HeaderName, HttpTryFrom, Uri, -}; +use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom, Uri}; use actix_service::Service; use crate::connect::{Connect, ConnectorWrapper}; -use crate::Client; +use crate::{Client, ClientConfig}; /// An HTTP Client builder /// @@ -77,7 +75,7 @@ impl ClientBuilder { where HeaderName: HttpTryFrom, >::Error: fmt::Debug, - V: IntoHeaderValue, + V: header::IntoHeaderValue, V::Error: fmt::Debug, { match HeaderName::try_from(key) { @@ -92,10 +90,85 @@ impl ClientBuilder { self } + /// Set client wide HTTP basic authorization header + pub fn basic_auth(self, username: U, password: Option<&str>) -> Self + where + U: fmt::Display, + { + let auth = match password { + Some(password) => format!("{}:{}", username, password), + None => format!("{}", username), + }; + self.header( + header::AUTHORIZATION, + format!("Basic {}", base64::encode(&auth)), + ) + } + + /// Set client wide HTTP bearer authentication header + pub fn bearer_auth(self, token: T) -> Self + where + T: fmt::Display, + { + self.header(header::AUTHORIZATION, format!("Bearer {}", token)) + } + /// Finish build process and create `Client` instance. pub fn finish(self) -> Client { Client { connector: self.connector, + config: Rc::new(ClientConfig { + headers: self.headers, + }), } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test; + + #[test] + fn client_basic_auth() { + test::run_on(|| { + let client = ClientBuilder::new().basic_auth("username", Some("password")); + assert_eq!( + client + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + ); + + let client = ClientBuilder::new().basic_auth("username", None); + assert_eq!( + client + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU=" + ); + }); + } + + #[test] + fn client_bearer_auth() { + test::run_on(|| { + let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n"); + assert_eq!( + client + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Bearer someS3cr3tAutht0k3n" + ); + }) + } +} diff --git a/awc/src/lib.rs b/awc/src/lib.rs index ca237798..3518bf8b 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -25,7 +25,7 @@ use std::rc::Rc; pub use actix_http::{client::Connector, http}; -use actix_http::http::{HttpTryFrom, Method, Uri}; +use actix_http::http::{HeaderMap, HttpTryFrom, Method, Uri}; use actix_http::RequestHead; mod builder; @@ -68,6 +68,11 @@ use self::connect::{Connect, ConnectorWrapper}; #[derive(Clone)] pub struct Client { pub(crate) connector: Rc>, + pub(crate) config: Rc, +} + +pub(crate) struct ClientConfig { + pub(crate) headers: HeaderMap, } impl Default for Client { @@ -76,6 +81,9 @@ impl Default for Client { connector: Rc::new(RefCell::new(ConnectorWrapper( Connector::new().service(), ))), + config: Rc::new(ClientConfig { + headers: HeaderMap::new(), + }), } } } @@ -96,7 +104,12 @@ impl Client { where Uri: HttpTryFrom, { - ClientRequest::new(method, url, self.connector.clone()) + let mut req = ClientRequest::new(method, url, self.connector.clone()); + + for (key, value) in &self.config.headers { + req.head.headers.insert(key.clone(), value.clone()); + } + req } /// Create `ClientRequest` from `RequestHead` @@ -107,13 +120,10 @@ impl Client { where Uri: HttpTryFrom, { - let mut req = - ClientRequest::new(head.method.clone(), url, self.connector.clone()); - + let mut req = self.request(head.method.clone(), url); for (key, value) in &head.headers { req.head.headers.insert(key.clone(), value.clone()); } - req } @@ -121,55 +131,60 @@ impl Client { where Uri: HttpTryFrom, { - ClientRequest::new(Method::GET, url, self.connector.clone()) + self.request(Method::GET, url) } pub fn head(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::HEAD, url, self.connector.clone()) + self.request(Method::HEAD, url) } pub fn put(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::PUT, url, self.connector.clone()) + self.request(Method::PUT, url) } pub fn post(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::POST, url, self.connector.clone()) + self.request(Method::POST, url) } pub fn patch(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::PATCH, url, self.connector.clone()) + self.request(Method::PATCH, url) } pub fn delete(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::DELETE, url, self.connector.clone()) + self.request(Method::DELETE, url) } pub fn options(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::OPTIONS, url, self.connector.clone()) + self.request(Method::OPTIONS, url) } pub fn ws(&self, url: U) -> WebsocketsRequest where Uri: HttpTryFrom, { - WebsocketsRequest::new(url, self.connector.clone()) + let mut req = WebsocketsRequest::new(url, self.connector.clone()); + + for (key, value) in &self.config.headers { + req.head.headers.insert(key.clone(), value.clone()); + } + req } } diff --git a/awc/src/request.rs b/awc/src/request.rs index dde51a8f..2e778cfc 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -241,10 +241,9 @@ impl ClientRequest { } /// Set HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option

) -> Self + pub fn basic_auth(self, username: U, password: Option<&str>) -> Self where U: fmt::Display, - P: fmt::Display, { let auth = match password { Some(password) => format!("{}:{}", username, password), @@ -552,3 +551,108 @@ impl fmt::Debug for ClientRequest { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{test, Client}; + + #[test] + fn test_debug() { + test::run_on(|| { + let request = Client::new().get("/").header("x-test", "111"); + let repr = format!("{:?}", request); + assert!(repr.contains("ClientRequest")); + assert!(repr.contains("x-test")); + }) + } + + #[test] + fn test_client_header() { + test::run_on(|| { + let req = Client::build() + .header(header::CONTENT_TYPE, "111") + .finish() + .get("/"); + + assert_eq!( + req.head + .headers + .get(header::CONTENT_TYPE) + .unwrap() + .to_str() + .unwrap(), + "111" + ); + }) + } + + #[test] + fn test_client_header_override() { + test::run_on(|| { + let req = Client::build() + .header(header::CONTENT_TYPE, "111") + .finish() + .get("/") + .set_header(header::CONTENT_TYPE, "222"); + + assert_eq!( + req.head + .headers + .get(header::CONTENT_TYPE) + .unwrap() + .to_str() + .unwrap(), + "222" + ); + }) + } + + #[test] + fn client_basic_auth() { + test::run_on(|| { + let client = Client::new() + .get("/") + .basic_auth("username", Some("password")); + assert_eq!( + client + .head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + ); + + let client = Client::new().get("/").basic_auth("username", None); + assert_eq!( + client + .head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU=" + ); + }); + } + + #[test] + fn client_bearer_auth() { + test::run_on(|| { + let client = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n"); + assert_eq!( + client + .head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Bearer someS3cr3tAutht0k3n" + ); + }) + } +} diff --git a/awc/src/test.rs b/awc/src/test.rs index 395e6290..7723b9d2 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -8,6 +8,25 @@ use cookie::{Cookie, CookieJar}; use crate::ClientResponse; +#[cfg(test)] +thread_local! { + static RT: std::cell::RefCell = { + std::cell::RefCell::new(actix_rt::Runtime::new().unwrap()) + }; +} + +#[cfg(test)] +pub fn run_on(f: F) -> R +where + F: Fn() -> R, +{ + RT.with(move |rt| { + rt.borrow_mut() + .block_on(futures::future::lazy(|| Ok::<_, ()>(f()))) + }) + .unwrap() +} + /// Test `ClientResponse` builder pub struct TestResponse { head: ResponseHead, diff --git a/awc/src/ws.rs b/awc/src/ws.rs index f959e62c..ec7fc0da 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -23,7 +23,7 @@ use crate::response::ClientResponse; /// `WebSocket` connection pub struct WebsocketsRequest { - head: RequestHead, + pub(crate) head: RequestHead, err: Option, origin: Option, protocols: Option,