diff options
author | Arthur Taylor <codders@octomonkey.org.uk> | 2016-11-22 10:56:20 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-11-22 10:56:20 +0100 |
commit | 8e9d234dcfe03a24409829ddd31b51bd8f840345 (patch) | |
tree | d1d833d732faf4c8709e975a4bb6129acc3d0f76 /src/http | |
parent | 0167dce98692f707b74395977c478c2ca44fa0c7 (diff) | |
parent | 4b50e1cb0945adbbcc07dfcb65a9252e7523105d (diff) | |
download | rvi_sota_client-8e9d234dcfe03a24409829ddd31b51bd8f840345.tar.gz |
Merge latest stable advancedtelematic
Diffstat (limited to 'src/http')
-rw-r--r-- | src/http/auth_client.rs | 164 | ||||
-rw-r--r-- | src/http/http_client.rs | 44 | ||||
-rw-r--r-- | src/http/mod.rs | 2 | ||||
-rw-r--r-- | src/http/test_client.rs | 10 |
4 files changed, 133 insertions, 87 deletions
diff --git a/src/http/auth_client.rs b/src/http/auth_client.rs index f4ad38b..3121c21 100644 --- a/src/http/auth_client.rs +++ b/src/http/auth_client.rs @@ -14,7 +14,7 @@ use std::time::Duration; use time; use datatype::{Auth, Error}; -use http::{Client, get_openssl, Request, Response}; +use http::{Client, get_openssl, Request, Response, ResponseData}; /// The `AuthClient` will attach an `Authentication` header to each outgoing @@ -51,59 +51,31 @@ impl AuthClient { impl Client for AuthClient { fn chan_request(&self, req: Request, resp_tx: Sender<Response>) { info!("{} {}", req.method, req.url); - let _ = self.client.request(req.url.inner(), AuthHandler { - auth: self.auth.clone(), - req: req, - timeout: Duration::from_secs(20), - started: None, - written: 0, - response: Vec::new(), - resp_tx: resp_tx.clone(), - }).map_err(|err| resp_tx.send(Err(Error::from(err)))); + let _ = self.client.request((*req.url).clone(), AuthHandler { + auth: self.auth.clone(), + req: req, + timeout: Duration::from_secs(20), + started: None, + written: 0, + resp_code: StatusCode::InternalServerError, + resp_body: Vec::new(), + resp_tx: resp_tx.clone(), + }).map_err(|err| resp_tx.send(Response::Error(Error::from(err)))); } } /// The async handler for outgoing HTTP requests. -// FIXME: uncomment when yocto is at 1.8.0: #[derive(Debug)] +#[derive(Debug)] pub struct AuthHandler { - auth: Auth, - req: Request, - timeout: Duration, - started: Option<u64>, - written: usize, - response: Vec<u8>, - resp_tx: Sender<Response>, -} - -// FIXME: required for building on 1.7.0 only -impl ::std::fmt::Debug for AuthHandler { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "unimplemented") - } -} - -impl AuthHandler { - fn redirect_request(&mut self, resp: HyperResponse) { - match resp.headers().get::<Location>() { - Some(&Location(ref loc)) => self.req.url.join(loc).map(|url| { - debug!("redirecting to {}", url); - // drop Authentication Header on redirect - let client = AuthClient::default(); - let resp_rx = client.send_request(Request { - url: url, - method: self.req.method.clone(), - body: mem::replace(&mut self.req.body, None), - }); - match resp_rx.recv().expect("no redirect_request response") { - Ok(data) => self.resp_tx.send(Ok(data)), - Err(err) => self.resp_tx.send(Err(Error::from(err))) - } - }).unwrap_or_else(|err| self.resp_tx.send(Err(Error::from(err)))), - - None => self.resp_tx.send(Err(Error::Client("redirect missing Location header".to_string()))) - } - } + auth: Auth, + req: Request, + timeout: Duration, + started: Option<u64>, + written: usize, + resp_code: StatusCode, + resp_body: Vec<u8>, + resp_tx: Sender<Response>, } /// The `AuthClient` may be used for both HTTP and HTTPS connections. @@ -125,15 +97,12 @@ impl Handler<Stream> for AuthHandler { headers.set(ContentType(mime_json)); } - Auth::Credentials(_, _) if self.req.body.is_some() => { - panic!("no request body expected for Auth::Credentials"); - } - - Auth::Credentials(ref id, ref secret) => { - headers.set(Authorization(Basic { username: id.0.clone(), - password: Some(secret.0.clone()) })); + Auth::Credentials(ref cred) => { + headers.set(Authorization(Basic { + username: cred.client_id.clone(), + password: Some(cred.client_secret.clone()) + })); headers.set(ContentType(mime_form)); - self.req.body = Some(br#"grant_type=client_credentials"#.to_vec()); } Auth::Token(ref token) => { @@ -173,7 +142,7 @@ impl Handler<Stream> for AuthHandler { Err(err) => { error!("unable to write request body: {}", err); - self.resp_tx.send(Err(Error::from(err))); + self.resp_tx.send(Response::Error(Error::from(err))); Next::remove() } } @@ -186,31 +155,25 @@ impl Handler<Stream> for AuthHandler { let latency = time::precise_time_ns() as f64 - started as f64; debug!("on_response latency: {}ms", (latency / 1e6) as u32); - if resp.status().is_success() { - if let Some(len) = resp.headers().get::<ContentLength>() { - if **len > 0 { - return Next::read(); - } - } - self.resp_tx.send(Ok(Vec::new())); - Next::end() - } else if resp.status().is_redirection() { + if resp.status().is_redirection() { self.redirect_request(resp); Next::end() - } else if resp.status() == &StatusCode::Forbidden { - self.resp_tx.send(Err(Error::Authorization(format!("{}", resp.status())))); + } else if let None = resp.headers().get::<ContentLength>() { + self.send_response(ResponseData { code: *resp.status(), body: Vec::new() }); Next::end() } else { - self.resp_tx.send(Err(Error::Client(format!("{}", resp.status())))); - Next::end() + self.resp_code = *resp.status(); + Next::read() } } fn on_response_readable(&mut self, decoder: &mut Decoder<Stream>) -> Next { - match io::copy(decoder, &mut self.response) { + match io::copy(decoder, &mut self.resp_body) { Ok(0) => { - debug!("on_response_readable bytes read: {}", self.response.len()); - self.resp_tx.send(Ok(mem::replace(&mut self.response, Vec::new()))); + debug!("on_response_readable body size: {}", self.resp_body.len()); + let code = self.resp_code.clone(); + let body = mem::replace(&mut self.resp_body, Vec::new()); + self.send_response(ResponseData { code: code, body: body }); Next::end() } @@ -226,7 +189,7 @@ impl Handler<Stream> for AuthHandler { Err(err) => { error!("unable to read response body: {}", err); - self.resp_tx.send(Err(Error::from(err))); + self.resp_tx.send(Response::Error(Error::from(err))); Next::end() } } @@ -234,11 +197,41 @@ impl Handler<Stream> for AuthHandler { fn on_error(&mut self, err: hyper::Error) -> Next { error!("on_error: {}", err); - self.resp_tx.send(Err(Error::from(err))); + self.resp_tx.send(Response::Error(Error::from(err))); Next::remove() } } +impl AuthHandler { + fn send_response(&mut self, resp: ResponseData) { + if resp.code == StatusCode::Unauthorized || resp.code == StatusCode::Forbidden { + self.resp_tx.send(Response::Error(Error::HttpAuth(resp))); + } else if resp.code.is_success() { + self.resp_tx.send(Response::Success(resp)); + } else { + self.resp_tx.send(Response::Failed(resp)); + } + } + + fn redirect_request(&mut self, resp: HyperResponse) { + match resp.headers().get::<Location>() { + Some(&Location(ref loc)) => self.req.url.join(loc).map(|url| { + debug!("redirecting to {}", url); + // drop Authorization Header on redirect + let client = AuthClient::default(); + let resp_rx = client.send_request(Request { + url: url, + method: self.req.method.clone(), + body: mem::replace(&mut self.req.body, None), + }); + self.resp_tx.send(resp_rx.recv().expect("no redirect_request response")) + }).unwrap_or_else(|err| self.resp_tx.send(Response::Error(Error::from(err)))), + + None => self.resp_tx.send(Response::Error((Error::Client("redirect missing Location header".to_string())))) + } + } +} + #[cfg(test)] mod tests { @@ -246,7 +239,7 @@ mod tests { use std::path::Path; use super::*; - use http::{Client, set_ca_certificates}; + use http::{Client, Response, set_ca_certificates}; fn get_client() -> AuthClient { @@ -259,8 +252,13 @@ mod tests { let client = get_client(); let url = "http://eu.httpbin.org/bytes/16?seed=123".parse().unwrap(); let resp_rx = client.get(url, None); - let data = resp_rx.recv().unwrap().unwrap(); - assert_eq!(data, vec![13, 22, 104, 27, 230, 9, 137, 85, 218, 40, 86, 85, 62, 0, 111, 22]); + let resp = resp_rx.recv().unwrap(); + let expect = vec![13, 22, 104, 27, 230, 9, 137, 85, 218, 40, 86, 85, 62, 0, 111, 22]; + match resp { + Response::Success(data) => assert_eq!(data.body, expect), + Response::Failed(data) => panic!("failed response: {}", data), + Response::Error(err) => panic!("error response: {}", err) + }; } #[test] @@ -268,9 +266,13 @@ mod tests { let client = get_client(); let url = "https://eu.httpbin.org/post".parse().unwrap(); let resp_rx = client.post(url, Some(br#"foo"#.to_vec())); - let body = resp_rx.recv().unwrap().unwrap(); - let resp = String::from_utf8(body).unwrap(); - let json = Json::from_str(&resp).unwrap(); + let resp = resp_rx.recv().unwrap(); + let body = match resp { + Response::Success(data) => String::from_utf8(data.body).unwrap(), + Response::Failed(data) => panic!("failed response: {}", data), + Response::Error(err) => panic!("error response: {}", err) + }; + let json = Json::from_str(&body).unwrap(); let obj = json.as_object().unwrap(); let data = obj.get("data").unwrap().as_string().unwrap(); assert_eq!(data, "foo"); diff --git a/src/http/http_client.rs b/src/http/http_client.rs index 492166c..b911b8d 100644 --- a/src/http/http_client.rs +++ b/src/http/http_client.rs @@ -1,5 +1,8 @@ use chan; use chan::{Sender, Receiver}; +use hyper::status::StatusCode; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::str; use datatype::{Error, Method, Url}; @@ -39,5 +42,42 @@ pub struct Request { pub body: Option<Vec<u8>> } -/// Return the body of an HTTP response on success, or an `Error` otherwise. -pub type Response = Result<Vec<u8>, Error>; + +/// A Response enumerates between a successful (e.g. 2xx) HTTP response, a failed +/// (e.g. 4xx/5xx) response, or an Error before receiving any response. +#[derive(Debug)] +pub enum Response { + Success(ResponseData), + Failed(ResponseData), + Error(Error) +} + +impl Display for Response { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match *self { + Response::Success(ref data) => write!(f, "{}", data), + Response::Failed(ref data) => write!(f, "{}", data), + Response::Error(ref err) => write!(f, "{}", err), + } + } +} + + +/// Wraps the HTTP Status Code as well as any returned body. +#[derive(Debug)] +pub struct ResponseData { + pub code: StatusCode, + pub body: Vec<u8> +} + +impl Display for ResponseData { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self.body.len() { + 0 => write!(f, "Response Code: {}", self.code), + n => match str::from_utf8(&self.body) { + Ok(text) => write!(f, "Response Code: {}, Body:\n{}", self.code, text), + Err(_) => write!(f, "Response Code: {}, Body: {} bytes", self.code, n), + } + } + } +} diff --git a/src/http/mod.rs b/src/http/mod.rs index 5e990a3..11b1e3a 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -5,7 +5,7 @@ pub mod openssl; pub mod test_client; pub use self::auth_client::{AuthClient, AuthHandler}; -pub use self::http_client::{Client, Request, Response}; +pub use self::http_client::{Client, Request, Response, ResponseData}; pub use self::http_server::{Server, ServerHandler}; pub use self::openssl::{get_openssl, set_ca_certificates}; pub use self::test_client::TestClient; diff --git a/src/http/test_client.rs b/src/http/test_client.rs index 7857e0f..1886fdf 100644 --- a/src/http/test_client.rs +++ b/src/http/test_client.rs @@ -1,8 +1,9 @@ use chan::Sender; +use hyper::status::StatusCode; use std::cell::RefCell; use datatype::Error; -use http::{Client, Request, Response}; +use http::{Client, Request, Response, ResponseData}; /// The `TestClient` will return HTTP responses from an existing list of strings. @@ -26,8 +27,11 @@ impl TestClient { impl Client for TestClient { fn chan_request(&self, req: Request, resp_tx: Sender<Response>) { match self.responses.borrow_mut().pop() { - Some(body) => resp_tx.send(Ok(body.as_bytes().to_vec())), - None => resp_tx.send(Err(Error::Client(req.url.to_string()))) + Some(body) => resp_tx.send(Response::Success(ResponseData { + code: StatusCode::Ok, + body: body.as_bytes().to_vec() + })), + None => resp_tx.send(Response::Error(Error::Client(req.url.to_string()))) } } |