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.
This commit is contained in:
hashmap 2018-08-03 23:48:54 +02:00 committed by Ignotus Peverell
parent 3df050cc93
commit 6a1d0e3354

View file

@ -23,10 +23,12 @@ use serde::{Deserialize, Serialize};
use serde_json; use serde_json;
use futures::future::{err, ok, Either}; use futures::future::{err, ok, Either};
use tokio_core::reactor::Core; use tokio::runtime::Runtime;
use rest::{Error, ErrorKind}; use rest::{Error, ErrorKind};
pub type ClientResponseFuture<T> = Box<Future<Item = T, Error = Error> + Send>;
/// Helper function to easily issue a HTTP GET request against a given URL that /// Helper function to easily issue a HTTP GET request against a given URL that
/// returns a JSON object. Handles request building, JSON deserialization and /// returns a JSON object. Handles request building, JSON deserialization and
/// response code checking. /// response code checking.
@ -34,19 +36,20 @@ pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
where where
for<'de> T: Deserialize<'de>, for<'de> T: Deserialize<'de>,
{ {
let uri = url.parse::<Uri>().map_err::<Error, _>(|e: InvalidUri| { handle_request(build_request(url, "GET", None)?)
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(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<T>
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 /// Helper function to easily issue a HTTP POST request with the provided JSON
@ -62,6 +65,22 @@ where
handle_request(req) 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<IN, OUT>(url: &str, input: &IN) -> ClientResponseFuture<OUT>
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 /// 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 /// object as body on a given URL that returns nothing. Handles request
/// building, JSON serialization, and response code /// building, JSON serialization, and response code
@ -75,6 +94,43 @@ where
Ok(()) 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<IN>(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<String>,
) -> Result<Request<Body>, Error> {
let uri = url.parse::<Uri>().map_err::<Error, _>(|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<IN>(url: &str, input: &IN) -> Result<Request<Body>, Error> fn create_post_request<IN>(url: &str, input: &IN) -> Result<Request<Body>, Error>
where where
IN: Serialize, IN: Serialize,
@ -82,20 +138,7 @@ where
let json = serde_json::to_string(input).context(ErrorKind::Internal( let json = serde_json::to_string(input).context(ErrorKind::Internal(
"Could not serialize data to JSON".to_owned(), "Could not serialize data to JSON".to_owned(),
))?; ))?;
let uri = url.parse::<Uri>().map_err::<Error, _>(|e: InvalidUri| { build_request(url, "POST", Some(json))
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::<Error, _>(|e| {
e.context(ErrorKind::RequestError("Bad request".to_owned()))
.into()
})
} }
fn handle_request<T>(req: Request<Body>) -> Result<T, Error> fn handle_request<T>(req: Request<Body>) -> Result<T, Error>
@ -109,29 +152,46 @@ where
}) })
} }
fn send_request(req: Request<Body>) -> Result<String, Error> { fn handle_request_async<T>(req: Request<Body>) -> ClientResponseFuture<T>
let mut event_loop = Core::new().unwrap(); where
let client = Client::new(); for<'de> T: Deserialize<'de> + Send + 'static,
{
let task = client Box::new(send_request_async(req).and_then(|data| {
.request(req) serde_json::from_str(&data).map_err(|e| {
.map_err(|_e| ErrorKind::RequestError("Cannot make request".to_owned())) e.context(ErrorKind::ResponseError("Cannot parse response".to_owned()))
.and_then(|resp| { .into()
if !resp.status().is_success() { })
Either::A(err(ErrorKind::RequestError( }))
"Wrong response code".to_owned(), }
)))
} else { fn send_request_async(req: Request<Body>) -> Box<Future<Item = String, Error = Error> + Send> {
Either::B( let client = Client::new();
resp.into_body() Box::new(
.map_err(|_e| { client
ErrorKind::RequestError("Cannot read response body".to_owned()) .request(req)
}) .map_err(|e| ErrorKind::RequestError(format!("Cannot make request: {}", e)).into())
.concat2() .and_then(|resp| {
.and_then(|ch| ok(String::from_utf8_lossy(&ch.to_vec()).to_string())), if !resp.status().is_success() {
) Either::A(err(ErrorKind::RequestError(
} "Wrong response code".to_owned(),
}); ).into()))
} else {
Ok(event_loop.run(task)?) 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<Body>) -> Result<String, Error> {
let task = send_request_async(req);
let mut rt = Runtime::new().unwrap();
Ok(rt.block_on(task)?)
} }