From 6a1d0e335442624eabcd5793e3385295fbf4be06 Mon Sep 17 00:00:00 2001 From: hashmap Date: Fri, 3 Aug 2018 23:48:54 +0200 Subject: [PATCH] Fix wallet owner_api on new Hyper (#1312) Fixes [#1308]. The main change is to switch from Core to Runtime inside the client. I also used this as an opportunity to provide async methods for get and post, so we can use it in places where futures are acceptable, which is not the case for the wallet. --- api/src/client.rs | 164 +++++++++++++++++++++++++++++++--------------- 1 file changed, 112 insertions(+), 52 deletions(-) diff --git a/api/src/client.rs b/api/src/client.rs index 6a49b6de0..4ae5d58f8 100644 --- a/api/src/client.rs +++ b/api/src/client.rs @@ -23,10 +23,12 @@ use serde::{Deserialize, Serialize}; use serde_json; use futures::future::{err, ok, Either}; -use tokio_core::reactor::Core; +use tokio::runtime::Runtime; use rest::{Error, ErrorKind}; +pub type ClientResponseFuture = Box + Send>; + /// Helper function to easily issue a HTTP GET request against a given URL that /// returns a JSON object. Handles request building, JSON deserialization and /// response code checking. @@ -34,19 +36,20 @@ pub fn get<'a, T>(url: &'a str) -> Result where for<'de> T: Deserialize<'de>, { - let uri = url.parse::().map_err::(|e: InvalidUri| { - e.context(ErrorKind::Argument(format!("Invalid url {}", url))) - .into() - })?; - let req = Request::builder() - .method("GET") - .uri(uri) - .header(USER_AGENT, "grin-client") - .header(ACCEPT, "application/json") - .body(Body::empty()) - .map_err(|_e| ErrorKind::RequestError("Bad request".to_owned()))?; + handle_request(build_request(url, "GET", None)?) +} - handle_request(req) +/// Helper function to easily issue an async HTTP GET request against a given +/// URL that returns a future. Handles request building, JSON deserialization +/// and response code checking. +pub fn get_async<'a, T>(url: &'a str) -> ClientResponseFuture +where + for<'de> T: Deserialize<'de> + Send + 'static, +{ + match build_request(url, "GET", None) { + Ok(req) => Box::new(handle_request_async(req)), + Err(e) => Box::new(err(e)), + } } /// Helper function to easily issue a HTTP POST request with the provided JSON @@ -62,6 +65,22 @@ where handle_request(req) } +/// Helper function to easily issue an async HTTP POST request with the +/// provided JSON object as body on a given URL that returns a future. Handles +/// request building, JSON serialization and deserialization, and response code +/// checking. +pub fn post_async(url: &str, input: &IN) -> ClientResponseFuture +where + IN: Serialize, + OUT: Send + 'static, + for<'de> OUT: Deserialize<'de>, +{ + match create_post_request(url, input) { + Ok(req) => Box::new(handle_request_async(req)), + Err(e) => Box::new(err(e)), + } +} + /// Helper function to easily issue a HTTP POST request with the provided JSON /// object as body on a given URL that returns nothing. Handles request /// building, JSON serialization, and response code @@ -75,6 +94,43 @@ where Ok(()) } +/// Helper function to easily issue an async HTTP POST request with the +/// provided JSON object as body on a given URL that returns a future. Handles +/// request building, JSON serialization and deserialization, and response code +/// checking. +pub fn post_no_ret_async(url: &str, input: &IN) -> ClientResponseFuture<()> +where + IN: Serialize, +{ + match create_post_request(url, input) { + Ok(req) => Box::new(send_request_async(req).and_then(|_| ok(()))), + Err(e) => Box::new(err(e)), + } +} + +fn build_request<'a>( + url: &'a str, + method: &str, + body: Option, +) -> Result, Error> { + let uri = url.parse::().map_err::(|e: InvalidUri| { + e.context(ErrorKind::Argument(format!("Invalid url {}", url))) + .into() + })?; + Request::builder() + .method(method) + .uri(uri) + .header(USER_AGENT, "grin-client") + .header(ACCEPT, "application/json") + .body(match body { + None => Body::empty(), + Some(json) => json.into(), + }) + .map_err(|e| { + ErrorKind::RequestError(format!("Bad request {} {}: {}", method, url, e)).into() + }) +} + fn create_post_request(url: &str, input: &IN) -> Result, Error> where IN: Serialize, @@ -82,20 +138,7 @@ where let json = serde_json::to_string(input).context(ErrorKind::Internal( "Could not serialize data to JSON".to_owned(), ))?; - let uri = url.parse::().map_err::(|e: InvalidUri| { - e.context(ErrorKind::Argument(format!("Invalid url {}", url))) - .into() - })?; - Request::builder() - .method("POST") - .uri(uri) - .header(USER_AGENT, "grin-client") - .header(ACCEPT, "application/json") - .body(json.into()) - .map_err::(|e| { - e.context(ErrorKind::RequestError("Bad request".to_owned())) - .into() - }) + build_request(url, "POST", Some(json)) } fn handle_request(req: Request) -> Result @@ -109,29 +152,46 @@ where }) } -fn send_request(req: Request) -> Result { - let mut event_loop = Core::new().unwrap(); - let client = Client::new(); - - let task = client - .request(req) - .map_err(|_e| ErrorKind::RequestError("Cannot make request".to_owned())) - .and_then(|resp| { - if !resp.status().is_success() { - Either::A(err(ErrorKind::RequestError( - "Wrong response code".to_owned(), - ))) - } else { - Either::B( - resp.into_body() - .map_err(|_e| { - ErrorKind::RequestError("Cannot read response body".to_owned()) - }) - .concat2() - .and_then(|ch| ok(String::from_utf8_lossy(&ch.to_vec()).to_string())), - ) - } - }); - - Ok(event_loop.run(task)?) +fn handle_request_async(req: Request) -> ClientResponseFuture +where + for<'de> T: Deserialize<'de> + Send + 'static, +{ + Box::new(send_request_async(req).and_then(|data| { + serde_json::from_str(&data).map_err(|e| { + e.context(ErrorKind::ResponseError("Cannot parse response".to_owned())) + .into() + }) + })) +} + +fn send_request_async(req: Request) -> Box + Send> { + let client = Client::new(); + Box::new( + client + .request(req) + .map_err(|e| ErrorKind::RequestError(format!("Cannot make request: {}", e)).into()) + .and_then(|resp| { + if !resp.status().is_success() { + Either::A(err(ErrorKind::RequestError( + "Wrong response code".to_owned(), + ).into())) + } else { + Either::B( + resp.into_body() + .map_err(|e| { + ErrorKind::RequestError(format!("Cannot read response body: {}", e)) + .into() + }) + .concat2() + .and_then(|ch| ok(String::from_utf8_lossy(&ch.to_vec()).to_string())), + ) + } + }), + ) +} + +fn send_request(req: Request) -> Result { + let task = send_request_async(req); + let mut rt = Runtime::new().unwrap(); + Ok(rt.block_on(task)?) }