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 futures::future::{err, ok, Either};
use tokio_core::reactor::Core;
use tokio::runtime::Runtime;
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
/// 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<T, Error>
where
for<'de> T: Deserialize<'de>,
{
let uri = url.parse::<Uri>().map_err::<Error, _>(|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<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
@ -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<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
/// 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<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>
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::<Uri>().map_err::<Error, _>(|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::<Error, _>(|e| {
e.context(ErrorKind::RequestError("Bad request".to_owned()))
.into()
})
build_request(url, "POST", Some(json))
}
fn handle_request<T>(req: Request<Body>) -> Result<T, Error>
@ -109,29 +152,46 @@ where
})
}
fn send_request(req: Request<Body>) -> Result<String, Error> {
let mut event_loop = Core::new().unwrap();
let client = Client::new();
fn handle_request_async<T>(req: Request<Body>) -> ClientResponseFuture<T>
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()
})
}))
}
let task = client
fn send_request_async(req: Request<Body>) -> Box<Future<Item = String, Error = Error> + Send> {
let client = Client::new();
Box::new(
client
.request(req)
.map_err(|_e| ErrorKind::RequestError("Cannot make request".to_owned()))
.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("Cannot read response body".to_owned())
.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())),
)
}
});
Ok(event_loop.run(task)?)
}),
)
}
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)?)
}