mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
Remove Iron dependency and update hyper to version 0.12 (#1241)
* Remove Iron dependecy and update hyper to version 0.12 #876 * REMOVE ME * Revert "REMOVE ME" This reverts commit e9a976eee98a2d5a4dfae5d9e1e4f5ed640c05d3. * Rebase and start updating libwallet Libwallet doesn't compile yet. * Wallet compiles * Grin compiles * No compilation errors in tests * All tests pass * Reeturn future from handler * Refactoring * Fix lifetime issue one more time I have to force push to rollback all the work done in last 2 days * Fix wallet send issue * Clean up
This commit is contained in:
parent
b040aaa434
commit
25e3d9e7d3
21 changed files with 1434 additions and 1252 deletions
603
Cargo.lock
generated
603
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -6,19 +6,21 @@ workspace = ".."
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
failure = "0.1"
|
failure = "0.1.1"
|
||||||
failure_derive = "0.1"
|
failure_derive = "0.1.1"
|
||||||
hyper = "0.10"
|
hyper = "0.12"
|
||||||
iron = "0.5"
|
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
mount = "0.3"
|
mount = "0.3"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
router = "0.5"
|
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
slog = { version = "~2.2", features = ["max_level_trace", "release_max_level_trace"] }
|
slog = { version = "~2.2", features = ["max_level_trace", "release_max_level_trace"] }
|
||||||
urlencoded = "0.5" # 0.6+ lacks trait `plugin::Plugin<iron::Request<'_, '_>>`
|
tokio = "0.1.7"
|
||||||
|
tokio-core = "0.1.17"
|
||||||
|
http = "0.1.5"
|
||||||
|
futures = "0.1.21"
|
||||||
|
url = "1.7.0"
|
||||||
|
|
||||||
grin_core = { path = "../core" }
|
grin_core = { path = "../core" }
|
||||||
grin_chain = { path = "../chain" }
|
grin_chain = { path = "../chain" }
|
||||||
|
|
|
@ -15,12 +15,15 @@
|
||||||
//! High level JSON/HTTP client API
|
//! High level JSON/HTTP client API
|
||||||
|
|
||||||
use failure::{Fail, ResultExt};
|
use failure::{Fail, ResultExt};
|
||||||
use hyper;
|
use http::uri::{InvalidUri, Uri};
|
||||||
use hyper::client::Response;
|
use hyper::header::{ACCEPT, USER_AGENT};
|
||||||
use hyper::status::{StatusClass, StatusCode};
|
use hyper::rt::{Future, Stream};
|
||||||
|
use hyper::{Body, Client, Request};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::io::Read;
|
|
||||||
|
use futures::future::{err, ok, Either};
|
||||||
|
use tokio_core::reactor::Core;
|
||||||
|
|
||||||
use rest::{Error, ErrorKind};
|
use rest::{Error, ErrorKind};
|
||||||
|
|
||||||
|
@ -31,63 +34,104 @@ pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
for<'de> T: Deserialize<'de>,
|
for<'de> T: Deserialize<'de>,
|
||||||
{
|
{
|
||||||
let client = hyper::Client::new();
|
let uri = url.parse::<Uri>().map_err::<Error, _>(|e: InvalidUri| {
|
||||||
let res = check_error(client.get(url).send())?;
|
e.context(ErrorKind::Argument(format!("Invalid url {}", url)))
|
||||||
serde_json::from_reader(res).map_err(|e| {
|
.into()
|
||||||
e.context(ErrorKind::Internal(
|
})?;
|
||||||
"Server returned invalid JSON".to_owned(),
|
let req = Request::builder()
|
||||||
)).into()
|
.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 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 a JSON object. Handles request
|
/// object as body on a given URL that returns a JSON object. Handles request
|
||||||
/// building, JSON serialization and deserialization, and response code
|
/// building, JSON serialization and deserialization, and response code
|
||||||
/// checking.
|
/// checking.
|
||||||
pub fn post<'a, IN>(url: &'a str, input: &IN) -> Result<(), Error>
|
pub fn post<IN, OUT>(url: &str, input: &IN) -> Result<OUT, Error>
|
||||||
|
where
|
||||||
|
IN: Serialize,
|
||||||
|
for<'de> OUT: Deserialize<'de>,
|
||||||
|
{
|
||||||
|
let req = create_post_request(url, input)?;
|
||||||
|
handle_request(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// checking.
|
||||||
|
pub fn post_no_ret<IN>(url: &str, input: &IN) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
IN: Serialize,
|
IN: Serialize,
|
||||||
{
|
{
|
||||||
let in_json = serde_json::to_string(input).context(ErrorKind::Internal(
|
let req = create_post_request(url, input)?;
|
||||||
"Could not serialize data to JSON".to_owned(),
|
send_request(req)?;
|
||||||
))?;
|
|
||||||
let client = hyper::Client::new();
|
|
||||||
let _res = check_error(client.post(url).body(&mut in_json.as_bytes()).send())?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert hyper error and check for non success response codes
|
fn create_post_request<IN>(url: &str, input: &IN) -> Result<Request<Body>, Error>
|
||||||
fn check_error(res: hyper::Result<Response>) -> Result<Response, Error> {
|
where
|
||||||
if let Err(e) = res {
|
IN: Serialize,
|
||||||
return Err(
|
{
|
||||||
e.context(ErrorKind::Internal("Error during request".to_owned()))
|
let json = serde_json::to_string(input).context(ErrorKind::Internal(
|
||||||
.into(),
|
"Could not serialize data to JSON".to_owned(),
|
||||||
);
|
))?;
|
||||||
}
|
let uri = url.parse::<Uri>().map_err::<Error, _>(|e: InvalidUri| {
|
||||||
let mut response = res.unwrap();
|
e.context(ErrorKind::Argument(format!("Invalid url {}", url)))
|
||||||
match response.status.class() {
|
.into()
|
||||||
StatusClass::Success => Ok(response),
|
})?;
|
||||||
StatusClass::ServerError => Err(ErrorKind::Internal(format!(
|
Request::builder()
|
||||||
"Server error: {}",
|
.method("POST")
|
||||||
err_msg(&mut response)
|
.uri(uri)
|
||||||
)))?,
|
.header(USER_AGENT, "grin-client")
|
||||||
StatusClass::ClientError => if response.status == StatusCode::NotFound {
|
.header(ACCEPT, "application/json")
|
||||||
Err(ErrorKind::NotFound)?
|
.body(json.into())
|
||||||
} else {
|
.map_err::<Error, _>(|e| {
|
||||||
Err(ErrorKind::Argument(format!(
|
e.context(ErrorKind::RequestError("Bad request".to_owned()))
|
||||||
"Argument error: {}",
|
.into()
|
||||||
err_msg(&mut response)
|
})
|
||||||
)))?
|
|
||||||
},
|
|
||||||
_ => Err(ErrorKind::Internal(format!("Unrecognized error.")))?,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn err_msg(resp: &mut Response) -> String {
|
fn handle_request<T>(req: Request<Body>) -> Result<T, Error>
|
||||||
let mut msg = String::new();
|
where
|
||||||
if let Err(_) = resp.read_to_string(&mut msg) {
|
for<'de> T: Deserialize<'de>,
|
||||||
"<no message>".to_owned()
|
{
|
||||||
} else {
|
let data = send_request(req)?;
|
||||||
msg
|
serde_json::from_str(&data).map_err(|e| {
|
||||||
}
|
e.context(ErrorKind::ResponseError("Cannot parse response".to_owned()))
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_request(req: Request<Body>) -> Result<String, Error> {
|
||||||
|
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)?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,16 +12,20 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::io::Read;
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::sync::{Arc, RwLock, Weak};
|
use std::sync::{Arc, RwLock, Weak};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use failure::{Fail, ResultExt};
|
use failure::ResultExt;
|
||||||
use iron::prelude::{IronError, IronResult, Plugin, Request, Response};
|
use futures::future::{err, ok};
|
||||||
use iron::{status, Handler};
|
use futures::{Future, Stream};
|
||||||
use serde::Serialize;
|
use hyper::{Body, Request, Response, StatusCode};
|
||||||
|
use rest::{Error, ErrorKind};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use urlencoded::UrlEncodedQuery;
|
|
||||||
|
|
||||||
use chain;
|
use chain;
|
||||||
use core::core::hash::{Hash, Hashed};
|
use core::core::hash::{Hash, Hashed};
|
||||||
|
@ -31,10 +35,13 @@ use p2p;
|
||||||
use p2p::types::ReasonForBan;
|
use p2p::types::ReasonForBan;
|
||||||
use pool;
|
use pool;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rest::{ApiServer, Error, ErrorKind};
|
use rest::*;
|
||||||
|
use router::{Handler, ResponseFuture, Router, RouterError};
|
||||||
use types::*;
|
use types::*;
|
||||||
|
use url::form_urlencoded;
|
||||||
|
use util;
|
||||||
use util::secp::pedersen::Commitment;
|
use util::secp::pedersen::Commitment;
|
||||||
use util::{self, LOGGER};
|
use util::LOGGER;
|
||||||
|
|
||||||
// All handlers use `Weak` references instead of `Arc` to avoid cycles that
|
// All handlers use `Weak` references instead of `Arc` to avoid cycles that
|
||||||
// can never be destroyed. These 2 functions are simple helpers to reduce the
|
// can never be destroyed. These 2 functions are simple helpers to reduce the
|
||||||
|
@ -52,7 +59,7 @@ struct IndexHandler {
|
||||||
impl IndexHandler {}
|
impl IndexHandler {}
|
||||||
|
|
||||||
impl Handler for IndexHandler {
|
impl Handler for IndexHandler {
|
||||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
json_response_pretty(&self.list)
|
json_response_pretty(&self.list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,14 +97,16 @@ impl OutputHandler {
|
||||||
Err(ErrorKind::NotFound)?
|
Err(ErrorKind::NotFound)?
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outputs_by_ids(&self, req: &mut Request) -> Vec<Output> {
|
fn outputs_by_ids(&self, req: &Request<Body>) -> Vec<Output> {
|
||||||
let mut commitments: Vec<&str> = vec![];
|
let mut commitments: Vec<String> = vec![];
|
||||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
let params = form_urlencoded::parse(req.uri().query().unwrap().as_bytes())
|
||||||
if let Some(ids) = params.get("id") {
|
.into_owned()
|
||||||
for id in ids {
|
.collect::<Vec<(String, String)>>();
|
||||||
|
|
||||||
|
for (k, id) in params {
|
||||||
|
if k == "id" {
|
||||||
for id in id.split(",") {
|
for id in id.split(",") {
|
||||||
commitments.push(id.clone());
|
commitments.push(id.to_owned());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +115,7 @@ impl OutputHandler {
|
||||||
|
|
||||||
let mut outputs: Vec<Output> = vec![];
|
let mut outputs: Vec<Output> = vec![];
|
||||||
for x in commitments {
|
for x in commitments {
|
||||||
if let Ok(output) = self.get_output(x) {
|
if let Ok(output) = self.get_output(&x) {
|
||||||
outputs.push(output);
|
outputs.push(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,13 +167,19 @@ impl OutputHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns outputs for a specified range of blocks
|
// returns outputs for a specified range of blocks
|
||||||
fn outputs_block_batch(&self, req: &mut Request) -> Vec<BlockOutputs> {
|
fn outputs_block_batch(&self, req: &Request<Body>) -> Vec<BlockOutputs> {
|
||||||
let mut commitments: Vec<Commitment> = vec![];
|
let mut commitments: Vec<Commitment> = vec![];
|
||||||
let mut start_height = 1;
|
let mut start_height = 1;
|
||||||
let mut end_height = 1;
|
let mut end_height = 1;
|
||||||
let mut include_rp = false;
|
let mut include_rp = false;
|
||||||
|
|
||||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
let params = form_urlencoded::parse(req.uri().query().unwrap().as_bytes())
|
||||||
|
.into_owned()
|
||||||
|
.fold(HashMap::new(), |mut hm, (k, v)| {
|
||||||
|
hm.entry(k).or_insert(vec![]).push(v);
|
||||||
|
hm
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(ids) = params.get("id") {
|
if let Some(ids) = params.get("id") {
|
||||||
for id in ids {
|
for id in ids {
|
||||||
for id in id.split(",") {
|
for id in id.split(",") {
|
||||||
|
@ -187,7 +202,6 @@ impl OutputHandler {
|
||||||
if let Some(_) = params.get("include_rp") {
|
if let Some(_) = params.get("include_rp") {
|
||||||
include_rp = true;
|
include_rp = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
|
@ -211,16 +225,17 @@ impl OutputHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for OutputHandler {
|
impl Handler for OutputHandler {
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn get(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
let url = req.url.clone();
|
match req.uri()
|
||||||
let mut path_elems = url.path();
|
.path()
|
||||||
if *path_elems.last().unwrap() == "" {
|
.trim_right_matches("/")
|
||||||
path_elems.pop();
|
.rsplit("/")
|
||||||
}
|
.next()
|
||||||
match *path_elems.last().unwrap() {
|
.unwrap()
|
||||||
"byids" => json_response(&self.outputs_by_ids(req)),
|
{
|
||||||
"byheight" => json_response(&self.outputs_block_batch(req)),
|
"byids" => json_response(&self.outputs_by_ids(&req)),
|
||||||
_ => Ok(Response::with((status::BadRequest, ""))),
|
"byheight" => json_response(&self.outputs_block_batch(&req)),
|
||||||
|
_ => response(StatusCode::BAD_REQUEST, ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,18 +321,20 @@ impl TxHashSetHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for TxHashSetHandler {
|
impl Handler for TxHashSetHandler {
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn get(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
let url = req.url.clone();
|
|
||||||
let mut path_elems = url.path();
|
|
||||||
let mut start_index = 1;
|
let mut start_index = 1;
|
||||||
let mut max = 100;
|
let mut max = 100;
|
||||||
let mut id = "";
|
let mut id = "".to_owned();
|
||||||
if *path_elems.last().unwrap() == "" {
|
|
||||||
path_elems.pop();
|
|
||||||
}
|
|
||||||
// TODO: probably need to set a reasonable max limit here
|
// TODO: probably need to set a reasonable max limit here
|
||||||
let mut last_n = 10;
|
let mut last_n = 10;
|
||||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
if let Some(query_string) = req.uri().query() {
|
||||||
|
let params = form_urlencoded::parse(query_string.as_bytes())
|
||||||
|
.into_owned()
|
||||||
|
.fold(HashMap::new(), |mut hm, (k, v)| {
|
||||||
|
hm.entry(k).or_insert(vec![]).push(v);
|
||||||
|
hm
|
||||||
|
});
|
||||||
if let Some(nums) = params.get("n") {
|
if let Some(nums) = params.get("n") {
|
||||||
for num in nums {
|
for num in nums {
|
||||||
if let Ok(n) = str::parse(num) {
|
if let Ok(n) = str::parse(num) {
|
||||||
|
@ -340,19 +357,26 @@ impl Handler for TxHashSetHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ids) = params.get("id") {
|
if let Some(ids) = params.get("id") {
|
||||||
for i in ids {
|
if !ids.is_empty() {
|
||||||
id = i;
|
id = ids.last().unwrap().to_owned();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match *path_elems.last().unwrap() {
|
match req.uri()
|
||||||
|
.path()
|
||||||
|
.trim_right()
|
||||||
|
.trim_right_matches("/")
|
||||||
|
.rsplit("/")
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
"roots" => json_response_pretty(&self.get_roots()),
|
"roots" => json_response_pretty(&self.get_roots()),
|
||||||
"lastoutputs" => json_response_pretty(&self.get_last_n_output(last_n)),
|
"lastoutputs" => json_response_pretty(&self.get_last_n_output(last_n)),
|
||||||
"lastrangeproofs" => json_response_pretty(&self.get_last_n_rangeproof(last_n)),
|
"lastrangeproofs" => json_response_pretty(&self.get_last_n_rangeproof(last_n)),
|
||||||
"lastkernels" => json_response_pretty(&self.get_last_n_kernel(last_n)),
|
"lastkernels" => json_response_pretty(&self.get_last_n_kernel(last_n)),
|
||||||
"outputs" => json_response_pretty(&self.outputs(start_index, max)),
|
"outputs" => json_response_pretty(&self.outputs(start_index, max)),
|
||||||
"merkleproof" => json_response_pretty(&self.get_merkle_proof_for_output(id).unwrap()),
|
"merkleproof" => json_response_pretty(&self.get_merkle_proof_for_output(&id).unwrap()),
|
||||||
_ => Ok(Response::with((status::BadRequest, ""))),
|
_ => response(StatusCode::BAD_REQUEST, ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,7 +386,7 @@ pub struct PeersAllHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for PeersAllHandler {
|
impl Handler for PeersAllHandler {
|
||||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
let peers = &w(&self.peers).all_peers();
|
let peers = &w(&self.peers).all_peers();
|
||||||
json_response_pretty(&peers)
|
json_response_pretty(&peers)
|
||||||
}
|
}
|
||||||
|
@ -373,7 +397,7 @@ pub struct PeersConnectedHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for PeersConnectedHandler {
|
impl Handler for PeersConnectedHandler {
|
||||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
let mut peers = vec![];
|
let mut peers = vec![];
|
||||||
for p in &w(&self.peers).connected_peers() {
|
for p in &w(&self.peers).connected_peers() {
|
||||||
let p = p.read().unwrap();
|
let p = p.read().unwrap();
|
||||||
|
@ -385,62 +409,54 @@ impl Handler for PeersConnectedHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer operations
|
/// Peer operations
|
||||||
|
/// GET /v1/peers/10.12.12.13
|
||||||
/// POST /v1/peers/10.12.12.13/ban
|
/// POST /v1/peers/10.12.12.13/ban
|
||||||
/// POST /v1/peers/10.12.12.13/unban
|
/// POST /v1/peers/10.12.12.13/unban
|
||||||
pub struct PeerPostHandler {
|
pub struct PeerHandler {
|
||||||
pub peers: Weak<p2p::Peers>,
|
pub peers: Weak<p2p::Peers>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for PeerPostHandler {
|
impl Handler for PeerHandler {
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn get(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
let url = req.url.clone();
|
if let Ok(addr) = req.uri()
|
||||||
let mut path_elems = url.path();
|
.path()
|
||||||
if *path_elems.last().unwrap() == "" {
|
.trim_right_matches("/")
|
||||||
path_elems.pop();
|
.rsplit("/")
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
{
|
||||||
|
match w(&self.peers).get_peer(addr) {
|
||||||
|
Ok(peer) => json_response(&peer),
|
||||||
|
Err(_) => response(StatusCode::BAD_REQUEST, ""),
|
||||||
}
|
}
|
||||||
match *path_elems.last().unwrap() {
|
|
||||||
"ban" => {
|
|
||||||
path_elems.pop();
|
|
||||||
if let Ok(addr) = path_elems.last().unwrap().parse() {
|
|
||||||
w(&self.peers).ban_peer(&addr, ReasonForBan::ManualBan);
|
|
||||||
Ok(Response::with((status::Ok, "")))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(Response::with((status::BadRequest, "")))
|
response(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
format!("url unrecognized: {}", req.uri().path()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn post(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
|
let mut path_elems = req.uri().path().trim_right_matches("/").rsplit("/");
|
||||||
|
match path_elems.next().unwrap() {
|
||||||
|
"ban" => {
|
||||||
|
if let Ok(addr) = path_elems.next().unwrap().parse() {
|
||||||
|
w(&self.peers).ban_peer(&addr, ReasonForBan::ManualBan);
|
||||||
|
response(StatusCode::OK, "")
|
||||||
|
} else {
|
||||||
|
response(StatusCode::BAD_REQUEST, "bad address to ban")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"unban" => {
|
"unban" => {
|
||||||
path_elems.pop();
|
if let Ok(addr) = path_elems.next().unwrap().parse() {
|
||||||
if let Ok(addr) = path_elems.last().unwrap().parse() {
|
|
||||||
w(&self.peers).unban_peer(&addr);
|
w(&self.peers).unban_peer(&addr);
|
||||||
Ok(Response::with((status::Ok, "")))
|
response(StatusCode::OK, "")
|
||||||
} else {
|
} else {
|
||||||
Ok(Response::with((status::BadRequest, "")))
|
response(StatusCode::BAD_REQUEST, "bad address to unban")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Ok(Response::with((status::BadRequest, ""))),
|
_ => response(StatusCode::BAD_REQUEST, "unrecognized command"),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get details about a given peer
|
|
||||||
pub struct PeerGetHandler {
|
|
||||||
pub peers: Weak<p2p::Peers>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler for PeerGetHandler {
|
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
|
||||||
let url = req.url.clone();
|
|
||||||
let mut path_elems = url.path();
|
|
||||||
if *path_elems.last().unwrap() == "" {
|
|
||||||
path_elems.pop();
|
|
||||||
}
|
|
||||||
if let Ok(addr) = path_elems.last().unwrap().parse() {
|
|
||||||
match w(&self.peers).get_peer(addr) {
|
|
||||||
Ok(peer) => json_response(&peer),
|
|
||||||
Err(_) => Ok(Response::with((status::BadRequest, ""))),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(Response::with((status::BadRequest, "")))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -459,7 +475,7 @@ impl StatusHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for StatusHandler {
|
impl Handler for StatusHandler {
|
||||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
json_response(&self.get_status())
|
json_response(&self.get_status())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -477,7 +493,7 @@ impl ChainHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for ChainHandler {
|
impl Handler for ChainHandler {
|
||||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
json_response(&self.get_tip())
|
json_response(&self.get_tip())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -489,10 +505,10 @@ pub struct ChainValidationHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for ChainValidationHandler {
|
impl Handler for ChainValidationHandler {
|
||||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
// TODO - read skip_rproofs from query params
|
// TODO - read skip_rproofs from query params
|
||||||
w(&self.chain).validate(true).unwrap();
|
w(&self.chain).validate(true).unwrap();
|
||||||
Ok(Response::with((status::Ok, "{}")))
|
response(StatusCode::OK, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,9 +520,9 @@ pub struct ChainCompactHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for ChainCompactHandler {
|
impl Handler for ChainCompactHandler {
|
||||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
w(&self.chain).compact().unwrap();
|
w(&self.chain).compact().unwrap();
|
||||||
Ok(Response::with((status::Ok, "{}")))
|
response(StatusCode::OK, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,7 +545,9 @@ impl HeaderHandler {
|
||||||
check_block_param(&input)?;
|
check_block_param(&input)?;
|
||||||
let vec = util::from_hex(input).unwrap();
|
let vec = util::from_hex(input).unwrap();
|
||||||
let h = Hash::from_vec(&vec);
|
let h = Hash::from_vec(&vec);
|
||||||
let header = w(&self.chain).get_block_header(&h).context(ErrorKind::NotFound)?;
|
let header = w(&self.chain)
|
||||||
|
.get_block_header(&h)
|
||||||
|
.context(ErrorKind::NotFound)?;
|
||||||
Ok(BlockHeaderPrintable::from_header(&header))
|
Ok(BlockHeaderPrintable::from_header(&header))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -581,50 +599,74 @@ fn check_block_param(input: &String) -> Result<(), Error> {
|
||||||
"Not a valid hash or height.".to_owned(),
|
"Not a valid hash or height.".to_owned(),
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for BlockHandler {
|
impl Handler for BlockHandler {
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn get(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
let url = req.url.clone();
|
let el = req.uri()
|
||||||
let mut path_elems = url.path();
|
.path()
|
||||||
if *path_elems.last().unwrap() == "" {
|
.trim_right_matches("/")
|
||||||
path_elems.pop();
|
.rsplit("/")
|
||||||
}
|
.next()
|
||||||
let el = *path_elems.last().unwrap();
|
.unwrap();
|
||||||
let h = self.parse_input(el.to_string())
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
|
|
||||||
|
|
||||||
let mut compact = false;
|
let h = self.parse_input(el.to_string());
|
||||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
if h.is_err() {
|
||||||
if let Some(_) = params.get("compact") {
|
error!(LOGGER, "block_handler: bad parameter {}", el.to_string());
|
||||||
compact = true;
|
return response(StatusCode::BAD_REQUEST, "");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if compact {
|
let h = h.unwrap();
|
||||||
let b = self.get_compact_block(&h)
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::InternalServerError))?;
|
if let Some(param) = req.uri().query() {
|
||||||
json_response(&b)
|
if param == "compact" {
|
||||||
|
match self.get_compact_block(&h) {
|
||||||
|
Ok(b) => json_response(&b),
|
||||||
|
Err(_) => {
|
||||||
|
error!(LOGGER, "block_handler: can not get compact block {}", h);
|
||||||
|
response(StatusCode::INTERNAL_SERVER_ERROR, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let b = self.get_block(&h)
|
debug!(
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::InternalServerError))?;
|
LOGGER,
|
||||||
json_response(&b)
|
"block_handler: unsupported query parameter {}", param
|
||||||
|
);
|
||||||
|
response(StatusCode::BAD_REQUEST, "")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self.get_block(&h) {
|
||||||
|
Ok(b) => json_response(&b),
|
||||||
|
Err(_) => {
|
||||||
|
error!(LOGGER, "block_handler: can not get block {}", h);
|
||||||
|
response(StatusCode::INTERNAL_SERVER_ERROR, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for HeaderHandler {
|
impl Handler for HeaderHandler {
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn get(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
let url = req.url.clone();
|
let el = req.uri()
|
||||||
let mut path_elems = url.path();
|
.path()
|
||||||
if *path_elems.last().unwrap() == "" {
|
.trim_right_matches("/")
|
||||||
path_elems.pop();
|
.rsplit("/")
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match self.get_header(el.to_string()) {
|
||||||
|
Ok(h) => json_response(&h),
|
||||||
|
Err(_) => {
|
||||||
|
error!(
|
||||||
|
LOGGER,
|
||||||
|
"header_handler: can not get header {}",
|
||||||
|
el.to_string()
|
||||||
|
);
|
||||||
|
response(StatusCode::INTERNAL_SERVER_ERROR, "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let el = *path_elems.last().unwrap();
|
|
||||||
let h = self.get_header(el.to_string())
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::InternalServerError))?;
|
|
||||||
json_response(&h)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -635,9 +677,9 @@ struct PoolInfoHandler<T> {
|
||||||
|
|
||||||
impl<T> Handler for PoolInfoHandler<T>
|
impl<T> Handler for PoolInfoHandler<T>
|
||||||
where
|
where
|
||||||
T: pool::BlockChain + Send + Sync + 'static,
|
T: pool::BlockChain + Send + Sync,
|
||||||
{
|
{
|
||||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
let pool_arc = w(&self.tx_pool);
|
let pool_arc = w(&self.tx_pool);
|
||||||
let pool = pool_arc.read().unwrap();
|
let pool = pool_arc.read().unwrap();
|
||||||
|
|
||||||
|
@ -658,20 +700,35 @@ struct PoolPushHandler<T> {
|
||||||
tx_pool: Weak<RwLock<pool::TransactionPool<T>>>,
|
tx_pool: Weak<RwLock<pool::TransactionPool<T>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Handler for PoolPushHandler<T>
|
impl<T> PoolPushHandler<T>
|
||||||
where
|
where
|
||||||
T: pool::BlockChain + Send + Sync + 'static,
|
T: pool::BlockChain + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn update_pool(&self, req: Request<Body>) -> Box<Future<Item = (), Error = Error> + Send> {
|
||||||
let wrapper: TxWrapper = serde_json::from_reader(req.body.by_ref())
|
let params = match req.uri().query() {
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
|
Some(query_string) => form_urlencoded::parse(query_string.as_bytes())
|
||||||
|
.into_owned()
|
||||||
|
.fold(HashMap::new(), |mut hm, (k, v)| {
|
||||||
|
hm.entry(k).or_insert(vec![]).push(v);
|
||||||
|
hm
|
||||||
|
}),
|
||||||
|
None => HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
let tx_bin = util::from_hex(wrapper.tx_hex)
|
let fluff = params.get("fluff").is_some();
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
|
let pool_arc = w(&self.tx_pool).clone();
|
||||||
|
|
||||||
let tx: Transaction = ser::deserialize(&mut &tx_bin[..])
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
|
|
||||||
|
|
||||||
|
Box::new(
|
||||||
|
parse_body(req)
|
||||||
|
.and_then(move |wrapper: TxWrapper| {
|
||||||
|
util::from_hex(wrapper.tx_hex)
|
||||||
|
.map_err(|_| ErrorKind::RequestError("Bad request".to_owned()).into())
|
||||||
|
})
|
||||||
|
.and_then(move |tx_bin| {
|
||||||
|
ser::deserialize(&mut &tx_bin[..])
|
||||||
|
.map_err(|_| ErrorKind::RequestError("Bad request".to_owned()).into())
|
||||||
|
})
|
||||||
|
.and_then(move |tx: Transaction| {
|
||||||
let source = pool::TxSource {
|
let source = pool::TxSource {
|
||||||
debug_name: "push-api".to_string(),
|
debug_name: "push-api".to_string(),
|
||||||
identifier: "?.?.?.?".to_string(),
|
identifier: "?.?.?.?".to_string(),
|
||||||
|
@ -683,53 +740,66 @@ where
|
||||||
tx.outputs.len()
|
tx.outputs.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut fluff = false;
|
|
||||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
|
||||||
if let Some(_) = params.get("fluff") {
|
|
||||||
fluff = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push to tx pool.
|
// Push to tx pool.
|
||||||
let res = {
|
|
||||||
let pool_arc = w(&self.tx_pool);
|
|
||||||
let mut tx_pool = pool_arc.write().unwrap();
|
let mut tx_pool = pool_arc.write().unwrap();
|
||||||
tx_pool.add_to_pool(source, tx, !fluff)
|
tx_pool
|
||||||
};
|
.add_to_pool(source, tx, !fluff)
|
||||||
|
.map_err(|_| ErrorKind::RequestError("Bad request".to_owned()).into())
|
||||||
match res {
|
}),
|
||||||
Ok(()) => Ok(Response::with(status::Ok)),
|
)
|
||||||
Err(e) => {
|
|
||||||
debug!(LOGGER, "error - {:?}", e);
|
|
||||||
Err(IronError::new(Fail::compat(e), status::BadRequest))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility to serialize a struct into JSON and produce a sensible IronResult
|
impl<T> Handler for PoolPushHandler<T>
|
||||||
|
where
|
||||||
|
T: pool::BlockChain + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn post(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
|
Box::new(
|
||||||
|
self.update_pool(req)
|
||||||
|
.and_then(|_| ok(just_response(StatusCode::OK, "")))
|
||||||
|
.or_else(|_| ok(just_response(StatusCode::BAD_REQUEST, ""))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility to serialize a struct into JSON and produce a sensible Response
|
||||||
// out of it.
|
// out of it.
|
||||||
fn json_response<T>(s: &T) -> IronResult<Response>
|
fn json_response<T>(s: &T) -> ResponseFuture
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
match serde_json::to_string(s) {
|
match serde_json::to_string(s) {
|
||||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
Ok(json) => response(StatusCode::OK, json),
|
||||||
Err(_) => Ok(Response::with((status::InternalServerError, ""))),
|
Err(_) => response(StatusCode::INTERNAL_SERVER_ERROR, ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pretty-printed version of above
|
// pretty-printed version of above
|
||||||
fn json_response_pretty<T>(s: &T) -> IronResult<Response>
|
fn json_response_pretty<T>(s: &T) -> ResponseFuture
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
match serde_json::to_string_pretty(s) {
|
match serde_json::to_string_pretty(s) {
|
||||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
Ok(json) => response(StatusCode::OK, json),
|
||||||
Err(_) => Ok(Response::with((status::InternalServerError, ""))),
|
Err(_) => response(StatusCode::INTERNAL_SERVER_ERROR, ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Start all server HTTP handlers. Register all of them with Iron
|
|
||||||
|
fn response<T: Into<Body> + Debug>(status: StatusCode, text: T) -> ResponseFuture {
|
||||||
|
Box::new(ok(just_response(status, text)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn just_response<T: Into<Body> + Debug>(status: StatusCode, text: T) -> Response<Body> {
|
||||||
|
debug!(LOGGER, "HTTP API -> status: {}, text: {:?}", status, text);
|
||||||
|
let mut resp = Response::new(text.into());
|
||||||
|
*resp.status_mut() = status;
|
||||||
|
resp
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local!( static ROUTER: RefCell<Option<Router>> = RefCell::new(None) );
|
||||||
|
|
||||||
|
/// Start all server HTTP handlers. Register all of them with Router
|
||||||
/// and runs the corresponding HTTP server.
|
/// and runs the corresponding HTTP server.
|
||||||
///
|
///
|
||||||
/// Hyper currently has a bug that prevents clean shutdown. In order
|
/// Hyper currently has a bug that prevents clean shutdown. In order
|
||||||
|
@ -748,10 +818,80 @@ pub fn start_rest_apis<T>(
|
||||||
let _ = thread::Builder::new()
|
let _ = thread::Builder::new()
|
||||||
.name("apis".to_string())
|
.name("apis".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
// build handlers and register them under the appropriate endpoint
|
let mut apis = ApiServer::new();
|
||||||
|
|
||||||
|
ROUTER.with(|router| {
|
||||||
|
*router.borrow_mut() =
|
||||||
|
Some(build_router(chain, tx_pool, peers).expect("unbale to build API router"));
|
||||||
|
|
||||||
|
info!(LOGGER, "Starting HTTP API server at {}.", addr);
|
||||||
|
let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address");
|
||||||
|
apis.start(socket_addr, &handle).unwrap_or_else(|e| {
|
||||||
|
error!(LOGGER, "Failed to start API HTTP server: {}.", e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(req: Request<Body>) -> ResponseFuture {
|
||||||
|
ROUTER.with(|router| match *router.borrow() {
|
||||||
|
Some(ref h) => h.handle(req),
|
||||||
|
None => {
|
||||||
|
error!(LOGGER, "No HTTP API router configured");
|
||||||
|
response(StatusCode::INTERNAL_SERVER_ERROR, "No router configured")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_body<T>(req: Request<Body>) -> Box<Future<Item = T, Error = Error> + Send>
|
||||||
|
where
|
||||||
|
for<'de> T: Deserialize<'de> + Send + 'static,
|
||||||
|
{
|
||||||
|
Box::new(
|
||||||
|
req.into_body()
|
||||||
|
.concat2()
|
||||||
|
.map_err(|_e| ErrorKind::RequestError("Failed to read request".to_owned()).into())
|
||||||
|
.and_then(|body| match serde_json::from_reader(&body.to_vec()[..]) {
|
||||||
|
Ok(obj) => ok(obj),
|
||||||
|
Err(_) => err(ErrorKind::RequestError("Invalid request body".to_owned()).into()),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_router<T>(
|
||||||
|
chain: Weak<chain::Chain>,
|
||||||
|
tx_pool: Weak<RwLock<pool::TransactionPool<T>>>,
|
||||||
|
peers: Weak<p2p::Peers>,
|
||||||
|
) -> Result<Router, RouterError>
|
||||||
|
where
|
||||||
|
T: pool::BlockChain + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let route_list = vec![
|
||||||
|
"get blocks".to_string(),
|
||||||
|
"get chain".to_string(),
|
||||||
|
"get chain/compact".to_string(),
|
||||||
|
"get chain/validate".to_string(),
|
||||||
|
"get chain/outputs".to_string(),
|
||||||
|
"get status".to_string(),
|
||||||
|
"get txhashset/roots".to_string(),
|
||||||
|
"get txhashset/lastoutputs?n=10".to_string(),
|
||||||
|
"get txhashset/lastrangeproofs".to_string(),
|
||||||
|
"get txhashset/lastkernels".to_string(),
|
||||||
|
"get txhashset/outputs?start_index=1&max=100".to_string(),
|
||||||
|
"get pool".to_string(),
|
||||||
|
"post pool/push".to_string(),
|
||||||
|
"post peers/a.b.c.d:p/ban".to_string(),
|
||||||
|
"post peers/a.b.c.d:p/unban".to_string(),
|
||||||
|
"get peers/all".to_string(),
|
||||||
|
"get peers/connected".to_string(),
|
||||||
|
"get peers/a.b.c.d".to_string(),
|
||||||
|
];
|
||||||
|
let index_handler = IndexHandler { list: route_list };
|
||||||
|
|
||||||
let output_handler = OutputHandler {
|
let output_handler = OutputHandler {
|
||||||
chain: chain.clone(),
|
chain: chain.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let block_handler = BlockHandler {
|
let block_handler = BlockHandler {
|
||||||
chain: chain.clone(),
|
chain: chain.clone(),
|
||||||
};
|
};
|
||||||
|
@ -786,60 +926,24 @@ pub fn start_rest_apis<T>(
|
||||||
let peers_connected_handler = PeersConnectedHandler {
|
let peers_connected_handler = PeersConnectedHandler {
|
||||||
peers: peers.clone(),
|
peers: peers.clone(),
|
||||||
};
|
};
|
||||||
let peer_post_handler = PeerPostHandler {
|
let peer_handler = PeerHandler {
|
||||||
peers: peers.clone(),
|
|
||||||
};
|
|
||||||
let peer_get_handler = PeerGetHandler {
|
|
||||||
peers: peers.clone(),
|
peers: peers.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let route_list = vec![
|
let mut router = Router::new();
|
||||||
"get blocks".to_string(),
|
router.add_route("/v1/", Box::new(index_handler))?;
|
||||||
"get chain".to_string(),
|
router.add_route("/v1/blocks/*", Box::new(block_handler))?;
|
||||||
"get chain/compact".to_string(),
|
router.add_route("/v1/headers/*", Box::new(header_handler))?;
|
||||||
"get chain/validate".to_string(),
|
router.add_route("/v1/chain", Box::new(chain_tip_handler))?;
|
||||||
"get chain/outputs".to_string(),
|
router.add_route("/v1/chain/outputs/*", Box::new(output_handler))?;
|
||||||
"post chain/height-index".to_string(),
|
router.add_route("/v1/chain/compact", Box::new(chain_compact_handler))?;
|
||||||
"get status".to_string(),
|
router.add_route("/v1/chain/validate", Box::new(chain_validation_handler))?;
|
||||||
"get txhashset/roots".to_string(),
|
router.add_route("/v1/txhashset/*", Box::new(txhashset_handler))?;
|
||||||
"get txhashset/lastoutputs?n=10".to_string(),
|
router.add_route("/v1/status", Box::new(status_handler))?;
|
||||||
"get txhashset/lastrangeproofs".to_string(),
|
router.add_route("/v1/pool", Box::new(pool_info_handler))?;
|
||||||
"get txhashset/lastkernels".to_string(),
|
router.add_route("/v1/pool/push", Box::new(pool_push_handler))?;
|
||||||
"get txhashset/outputs?start_index=1&max=100".to_string(),
|
router.add_route("/v1/peers/all", Box::new(peers_all_handler))?;
|
||||||
"get pool".to_string(),
|
router.add_route("/v1/peers/connected", Box::new(peers_connected_handler))?;
|
||||||
"post pool/push".to_string(),
|
router.add_route("/v1/peers/**", Box::new(peer_handler))?;
|
||||||
"post peers/a.b.c.d:p/ban".to_string(),
|
Ok(router)
|
||||||
"post peers/a.b.c.d:p/unban".to_string(),
|
|
||||||
"get peers/all".to_string(),
|
|
||||||
"get peers/connected".to_string(),
|
|
||||||
"get peers/a.b.c.d".to_string(),
|
|
||||||
];
|
|
||||||
let index_handler = IndexHandler { list: route_list };
|
|
||||||
|
|
||||||
let router = router!(
|
|
||||||
index: get "/" => index_handler,
|
|
||||||
blocks: get "/blocks/*" => block_handler,
|
|
||||||
headers: get "/headers/*" => header_handler,
|
|
||||||
chain_tip: get "/chain" => chain_tip_handler,
|
|
||||||
chain_compact: get "/chain/compact" => chain_compact_handler,
|
|
||||||
chain_validate: get "/chain/validate" => chain_validation_handler,
|
|
||||||
chain_outputs: get "/chain/outputs/*" => output_handler,
|
|
||||||
status: get "/status" => status_handler,
|
|
||||||
txhashset_roots: get "/txhashset/*" => txhashset_handler,
|
|
||||||
pool_info: get "/pool" => pool_info_handler,
|
|
||||||
pool_push: post "/pool/push" => pool_push_handler,
|
|
||||||
peers_all: get "/peers/all" => peers_all_handler,
|
|
||||||
peers_connected: get "/peers/connected" => peers_connected_handler,
|
|
||||||
peer: post "/peers/*" => peer_post_handler,
|
|
||||||
peer: get "/peers/*" => peer_get_handler
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut apis = ApiServer::new("/v1".to_string());
|
|
||||||
apis.register_handler(router);
|
|
||||||
|
|
||||||
info!(LOGGER, "Starting HTTP API server at {}.", addr);
|
|
||||||
apis.start(&addr[..]).unwrap_or_else(|e| {
|
|
||||||
error!(LOGGER, "Failed to start API HTTP server: {}.", e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,31 +18,34 @@ extern crate grin_p2p as p2p;
|
||||||
extern crate grin_pool as pool;
|
extern crate grin_pool as pool;
|
||||||
extern crate grin_store as store;
|
extern crate grin_store as store;
|
||||||
extern crate grin_util as util;
|
extern crate grin_util as util;
|
||||||
|
extern crate url;
|
||||||
|
|
||||||
extern crate failure;
|
extern crate failure;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate failure_derive;
|
extern crate failure_derive;
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
extern crate iron;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
extern crate mount;
|
extern crate mount;
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
#[macro_use]
|
|
||||||
extern crate router;
|
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate slog;
|
extern crate slog;
|
||||||
extern crate urlencoded;
|
extern crate futures;
|
||||||
|
extern crate http;
|
||||||
|
extern crate tokio;
|
||||||
|
extern crate tokio_core;
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
mod rest;
|
mod rest;
|
||||||
|
mod router;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
pub use handlers::start_rest_apis;
|
pub use handlers::start_rest_apis;
|
||||||
pub use rest::*;
|
pub use rest::*;
|
||||||
|
pub use router::*;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
|
@ -18,20 +18,17 @@
|
||||||
//! To use it, just have your service(s) implement the ApiEndpoint trait and
|
//! To use it, just have your service(s) implement the ApiEndpoint trait and
|
||||||
//! register them on a ApiServer.
|
//! register them on a ApiServer.
|
||||||
|
|
||||||
|
use hyper::rt::Future;
|
||||||
|
use hyper::service::service_fn;
|
||||||
|
use hyper::{Body, Request, Server};
|
||||||
|
use router::ResponseFuture;
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::mem;
|
use std::net::SocketAddr;
|
||||||
use std::net::ToSocketAddrs;
|
use tokio::runtime::current_thread::Runtime;
|
||||||
use std::string::ToString;
|
|
||||||
|
|
||||||
use failure::{Backtrace, Context, Fail};
|
use failure::{Backtrace, Context, Fail};
|
||||||
use iron::Listening;
|
|
||||||
use iron::middleware::Handler;
|
|
||||||
use iron::prelude::Iron;
|
|
||||||
use mount::Mount;
|
|
||||||
use router::Router;
|
|
||||||
|
|
||||||
/// Errors that can be returned by an ApiEndpoint implementation.
|
/// Errors that can be returned by an ApiEndpoint implementation.
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
inner: Context<ErrorKind>,
|
inner: Context<ErrorKind>,
|
||||||
|
@ -45,6 +42,10 @@ pub enum ErrorKind {
|
||||||
Argument(String),
|
Argument(String),
|
||||||
#[fail(display = "Not found.")]
|
#[fail(display = "Not found.")]
|
||||||
NotFound,
|
NotFound,
|
||||||
|
#[fail(display = "Request error: {}", _0)]
|
||||||
|
RequestError(String),
|
||||||
|
#[fail(display = "ResponseError error: {}", _0)]
|
||||||
|
ResponseError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fail for Error {
|
impl Fail for Error {
|
||||||
|
@ -84,45 +85,37 @@ impl From<Context<ErrorKind>> for Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HTTP server allowing the registration of ApiEndpoint implementations.
|
/// HTTP server allowing the registration of ApiEndpoint implementations.
|
||||||
pub struct ApiServer {
|
pub struct ApiServer {}
|
||||||
root: String,
|
|
||||||
router: Router,
|
|
||||||
mount: Mount,
|
|
||||||
server_listener: Option<Listening>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApiServer {
|
impl ApiServer {
|
||||||
/// Creates a new ApiServer that will serve ApiEndpoint implementations
|
/// Creates a new ApiServer that will serve ApiEndpoint implementations
|
||||||
/// under the root URL.
|
/// under the root URL.
|
||||||
pub fn new(root: String) -> ApiServer {
|
pub fn new() -> ApiServer {
|
||||||
ApiServer {
|
ApiServer {}
|
||||||
root: root,
|
|
||||||
router: Router::new(),
|
|
||||||
mount: Mount::new(),
|
|
||||||
server_listener: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts the ApiServer at the provided address.
|
/// Starts the ApiServer at the provided address.
|
||||||
pub fn start<A: ToSocketAddrs>(&mut self, addr: A) -> Result<(), String> {
|
pub fn start<F>(&mut self, addr: SocketAddr, f: &'static F) -> Result<(), String>
|
||||||
// replace this value to satisfy borrow checker
|
where
|
||||||
let r = mem::replace(&mut self.router, Router::new());
|
F: Fn(Request<Body>) -> ResponseFuture + Send + Sync + 'static,
|
||||||
let mut m = mem::replace(&mut self.mount, Mount::new());
|
{
|
||||||
m.mount("/", r);
|
let server = Server::bind(&addr)
|
||||||
let result = Iron::new(m).http(addr);
|
.serve(move || service_fn(f))
|
||||||
let return_value = result.as_ref().map(|_| ()).map_err(|e| e.to_string());
|
.map_err(|e| eprintln!("server error: {}", e));
|
||||||
self.server_listener = Some(result.unwrap());
|
|
||||||
return_value
|
let mut rt = Runtime::new().unwrap();
|
||||||
|
if rt.block_on(server).is_err() {
|
||||||
|
return Err("tokio block_on error".to_owned());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops the API server
|
/// Stops the API server
|
||||||
pub fn stop(&mut self) {
|
pub fn stop(&mut self) {
|
||||||
let r = mem::replace(&mut self.server_listener, None);
|
// TODO implement proper stop, the following method doesn't
|
||||||
r.unwrap().close().unwrap();
|
// work for current_thread runtime.
|
||||||
}
|
// if let Some(rt) = self.rt.take() {
|
||||||
|
// rt.shutdown_now().wait().unwrap();
|
||||||
/// Registers an iron handler (via mount)
|
// }
|
||||||
pub fn register_handler<H: Handler>(&mut self, handler: H) -> &mut Mount {
|
|
||||||
self.mount.mount(&self.root, handler)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
309
api/src/router.rs
Normal file
309
api/src/router.rs
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
use futures::future;
|
||||||
|
use hyper;
|
||||||
|
use hyper::rt::Future;
|
||||||
|
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use util::LOGGER;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref WILDCARD_HASH: u64 = calculate_hash(&"*");
|
||||||
|
static ref WILDCARD_STOP_HASH: u64 = calculate_hash(&"**");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ResponseFuture = Box<Future<Item = Response<Body>, Error = hyper::Error> + Send>;
|
||||||
|
|
||||||
|
pub trait Handler {
|
||||||
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
not_found()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
not_found()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
not_found()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn patch(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
not_found()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
not_found()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn head(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
not_found()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn options(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
not_found()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trace(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
not_found()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
not_found()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum RouterError {
|
||||||
|
#[fail(display = "Route already exists")]
|
||||||
|
RouteAlreadyExists,
|
||||||
|
#[fail(display = "Route not found")]
|
||||||
|
RouteNotFound,
|
||||||
|
#[fail(display = "Value not found")]
|
||||||
|
NoValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Router {
|
||||||
|
nodes: Vec<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct NodeId(usize);
|
||||||
|
|
||||||
|
const MAX_CHILDREN: usize = 16;
|
||||||
|
|
||||||
|
type HandlerObj = Box<Handler>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Node {
|
||||||
|
key: u64,
|
||||||
|
value: Option<Arc<HandlerObj>>,
|
||||||
|
children: [NodeId; MAX_CHILDREN],
|
||||||
|
children_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Router {
|
||||||
|
pub fn new() -> Router {
|
||||||
|
let root = Node::new(calculate_hash(&""), None);
|
||||||
|
let mut nodes = vec![];
|
||||||
|
nodes.push(root);
|
||||||
|
Router { nodes }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root(&self) -> NodeId {
|
||||||
|
NodeId(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node(&self, id: NodeId) -> &Node {
|
||||||
|
&self.nodes[id.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_mut(&mut self, id: NodeId) -> &mut Node {
|
||||||
|
&mut self.nodes[id.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find(&self, parent: NodeId, key: u64) -> Option<NodeId> {
|
||||||
|
let node = self.node(parent);
|
||||||
|
node.children
|
||||||
|
.iter()
|
||||||
|
.find(|&id| {
|
||||||
|
let node_key = self.node(*id).key;
|
||||||
|
node_key == key || node_key == *WILDCARD_HASH || node_key == *WILDCARD_STOP_HASH
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_empty_node(&mut self, parent: NodeId, key: u64) -> NodeId {
|
||||||
|
let id = NodeId(self.nodes.len());
|
||||||
|
self.nodes.push(Node::new(key, None));
|
||||||
|
self.node_mut(parent).add_child(id);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_route(&mut self, route: &'static str, value: HandlerObj) -> Result<(), RouterError> {
|
||||||
|
let keys = generate_path(route);
|
||||||
|
let mut node_id = self.root();
|
||||||
|
for key in keys {
|
||||||
|
node_id = self.find(node_id, key)
|
||||||
|
.unwrap_or_else(|| self.add_empty_node(node_id, key));
|
||||||
|
}
|
||||||
|
match self.node(node_id).value() {
|
||||||
|
None => {
|
||||||
|
self.node_mut(node_id).set_value(value);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Some(_) => Err(RouterError::RouteAlreadyExists),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, path: &str) -> Result<Arc<HandlerObj>, RouterError> {
|
||||||
|
let keys = generate_path(path);
|
||||||
|
let mut node_id = self.root();
|
||||||
|
for key in keys {
|
||||||
|
node_id = self.find(node_id, key).ok_or(RouterError::RouteNotFound)?;
|
||||||
|
if self.node(node_id).key == *WILDCARD_STOP_HASH {
|
||||||
|
debug!(LOGGER, "ROUTER stop card");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.node(node_id).value().ok_or(RouterError::NoValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
|
match self.get(req.uri().path()) {
|
||||||
|
Err(_) => not_found(),
|
||||||
|
Ok(h) => match req.method() {
|
||||||
|
&Method::GET => h.get(req),
|
||||||
|
&Method::POST => h.post(req),
|
||||||
|
&Method::PUT => h.put(req),
|
||||||
|
&Method::DELETE => h.delete(req),
|
||||||
|
&Method::PATCH => h.patch(req),
|
||||||
|
&Method::OPTIONS => h.options(req),
|
||||||
|
&Method::CONNECT => h.connect(req),
|
||||||
|
&Method::TRACE => h.trace(req),
|
||||||
|
&Method::HEAD => h.head(req),
|
||||||
|
_ => not_found(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
fn new(key: u64, value: Option<Arc<HandlerObj>>) -> Node {
|
||||||
|
Node {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
children: [NodeId(0); MAX_CHILDREN],
|
||||||
|
children_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> Option<Arc<HandlerObj>> {
|
||||||
|
match &self.value {
|
||||||
|
None => None,
|
||||||
|
Some(v) => Some(v.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_value(&mut self, value: HandlerObj) {
|
||||||
|
self.value = Some(Arc::new(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_child(&mut self, child_id: NodeId) {
|
||||||
|
if self.children_count == MAX_CHILDREN {
|
||||||
|
panic!("Can't add a route, children limit exceeded");
|
||||||
|
}
|
||||||
|
self.children[self.children_count] = child_id;
|
||||||
|
self.children_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_found() -> ResponseFuture {
|
||||||
|
let mut response = Response::new(Body::empty());
|
||||||
|
*response.status_mut() = StatusCode::NOT_FOUND;
|
||||||
|
Box::new(future::ok(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_hash<T: Hash>(t: &T) -> u64 {
|
||||||
|
let mut s = DefaultHasher::new();
|
||||||
|
t.hash(&mut s);
|
||||||
|
s.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_path(route: &str) -> Vec<u64> {
|
||||||
|
route
|
||||||
|
.split('/')
|
||||||
|
.skip(1)
|
||||||
|
.map(|path| calculate_hash(&path))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use tokio::prelude::future::ok;
|
||||||
|
use tokio_core::reactor::Core;
|
||||||
|
|
||||||
|
struct HandlerImpl(u16);
|
||||||
|
|
||||||
|
impl Handler for HandlerImpl {
|
||||||
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
Box::new(future::ok(
|
||||||
|
Response::builder()
|
||||||
|
.status(self.0)
|
||||||
|
.body(Body::default())
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_route() {
|
||||||
|
let mut routes = Router::new();
|
||||||
|
routes
|
||||||
|
.add_route("/v1/users", Box::new(HandlerImpl(1)))
|
||||||
|
.unwrap();
|
||||||
|
assert!(
|
||||||
|
routes
|
||||||
|
.add_route("/v1/users", Box::new(HandlerImpl(2)))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
routes
|
||||||
|
.add_route("/v1/users/xxx", Box::new(HandlerImpl(3)))
|
||||||
|
.unwrap();
|
||||||
|
routes
|
||||||
|
.add_route("/v1/users/xxx/yyy", Box::new(HandlerImpl(3)))
|
||||||
|
.unwrap();
|
||||||
|
routes
|
||||||
|
.add_route("/v1/zzz/*", Box::new(HandlerImpl(3)))
|
||||||
|
.unwrap();
|
||||||
|
assert!(
|
||||||
|
routes
|
||||||
|
.add_route("/v1/zzz/ccc", Box::new(HandlerImpl(2)))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
routes
|
||||||
|
.add_route("/v1/zzz/*/zzz", Box::new(HandlerImpl(6)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get() {
|
||||||
|
let mut routes = Router::new();
|
||||||
|
routes
|
||||||
|
.add_route("/v1/users", Box::new(HandlerImpl(101)))
|
||||||
|
.unwrap();
|
||||||
|
routes
|
||||||
|
.add_route("/v1/users/xxx", Box::new(HandlerImpl(103)))
|
||||||
|
.unwrap();
|
||||||
|
routes
|
||||||
|
.add_route("/v1/users/xxx/yyy", Box::new(HandlerImpl(103)))
|
||||||
|
.unwrap();
|
||||||
|
routes
|
||||||
|
.add_route("/v1/zzz/*", Box::new(HandlerImpl(103)))
|
||||||
|
.unwrap();
|
||||||
|
routes
|
||||||
|
.add_route("/v1/zzz/*/zzz", Box::new(HandlerImpl(106)))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let call_handler = |url| {
|
||||||
|
let mut event_loop = Core::new().unwrap();
|
||||||
|
let task = routes
|
||||||
|
.get(url)
|
||||||
|
.unwrap()
|
||||||
|
.get(Request::new(Body::default()))
|
||||||
|
.and_then(|resp| ok(resp.status().as_u16()));
|
||||||
|
event_loop.run(task).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(call_handler("/v1/users"), 101);
|
||||||
|
assert_eq!(call_handler("/v1/users/xxx"), 103);
|
||||||
|
assert!(routes.get("/v1/users/yyy").is_err());
|
||||||
|
assert_eq!(call_handler("/v1/users/xxx/yyy"), 103);
|
||||||
|
assert!(routes.get("/v1/zzz").is_err());
|
||||||
|
assert_eq!(call_handler("/v1/zzz/1"), 103);
|
||||||
|
assert_eq!(call_handler("/v1/zzz/2"), 103);
|
||||||
|
assert_eq!(call_handler("/v1/zzz/2/zzz"), 106);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,8 +27,10 @@ use util::LOGGER;
|
||||||
|
|
||||||
use peer::Peer;
|
use peer::Peer;
|
||||||
use store::{PeerData, PeerStore, State};
|
use store::{PeerData, PeerStore, State};
|
||||||
use types::{Capabilities, ChainAdapter, Direction, Error, NetAdapter, P2PConfig, ReasonForBan,
|
use types::{
|
||||||
TxHashSetRead, MAX_PEER_ADDRS};
|
Capabilities, ChainAdapter, Direction, Error, NetAdapter, P2PConfig, ReasonForBan,
|
||||||
|
TxHashSetRead, MAX_PEER_ADDRS,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Peers {
|
pub struct Peers {
|
||||||
pub adapter: Arc<ChainAdapter>,
|
pub adapter: Arc<ChainAdapter>,
|
||||||
|
@ -615,17 +617,8 @@ impl ChainAdapter for Peers {
|
||||||
self.adapter.txhashset_receive_ready()
|
self.adapter.txhashset_receive_ready()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn txhashset_write(
|
fn txhashset_write(&self, h: Hash, txhashset_data: File, peer_addr: SocketAddr) -> bool {
|
||||||
&self,
|
if !self.adapter.txhashset_write(h, txhashset_data, peer_addr) {
|
||||||
h: Hash,
|
|
||||||
txhashset_data: File,
|
|
||||||
peer_addr: SocketAddr,
|
|
||||||
) -> bool {
|
|
||||||
if !self.adapter.txhashset_write(
|
|
||||||
h,
|
|
||||||
txhashset_data,
|
|
||||||
peer_addr,
|
|
||||||
) {
|
|
||||||
debug!(
|
debug!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"Received a bad txhashset data from {}, the peer will be banned", &peer_addr
|
"Received a bad txhashset data from {}, the peer will be banned", &peer_addr
|
||||||
|
|
|
@ -20,8 +20,10 @@ use std::sync::Arc;
|
||||||
use conn::{Message, MessageHandler, Response};
|
use conn::{Message, MessageHandler, Response};
|
||||||
use core::core;
|
use core::core;
|
||||||
use core::core::hash::{Hash, Hashed};
|
use core::core::hash::{Hash, Hashed};
|
||||||
use msg::{BanReason, GetPeerAddrs, Headers, Locator, PeerAddrs, Ping, Pong, SockAddr,
|
use msg::{
|
||||||
TxHashSetArchive, TxHashSetRequest, Type};
|
BanReason, GetPeerAddrs, Headers, Locator, PeerAddrs, Ping, Pong, SockAddr, TxHashSetArchive,
|
||||||
|
TxHashSetRequest, Type,
|
||||||
|
};
|
||||||
use rand::{self, Rng};
|
use rand::{self, Rng};
|
||||||
use types::{Error, NetAdapter};
|
use types::{Error, NetAdapter};
|
||||||
use util::LOGGER;
|
use util::LOGGER;
|
||||||
|
@ -241,7 +243,6 @@ impl MessageHandler for Protocol {
|
||||||
);
|
);
|
||||||
return Err(Error::BadMessage);
|
return Err(Error::BadMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut tmp = env::temp_dir();
|
let mut tmp = env::temp_dir();
|
||||||
tmp.push("txhashset.zip");
|
tmp.push("txhashset.zip");
|
||||||
let mut save_txhashset_to_file = |file| -> Result<(), Error> {
|
let mut save_txhashset_to_file = |file| -> Result<(), Error> {
|
||||||
|
@ -251,11 +252,10 @@ impl MessageHandler for Protocol {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = save_txhashset_to_file(tmp.clone()){
|
if let Err(e) = save_txhashset_to_file(tmp.clone()) {
|
||||||
error!(
|
error!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"handle_payload: txhashset archive save to file fail. err={:?}",
|
"handle_payload: txhashset archive save to file fail. err={:?}", e
|
||||||
e
|
|
||||||
);
|
);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
@ -267,11 +267,8 @@ impl MessageHandler for Protocol {
|
||||||
);
|
);
|
||||||
|
|
||||||
let tmp_zip = File::open(tmp)?;
|
let tmp_zip = File::open(tmp)?;
|
||||||
let res = self.adapter.txhashset_write(
|
let res = self.adapter
|
||||||
sm_arch.hash,
|
.txhashset_write(sm_arch.hash, tmp_zip, self.addr);
|
||||||
tmp_zip,
|
|
||||||
self.addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
|
|
|
@ -254,12 +254,7 @@ impl ChainAdapter for DummyAdapter {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn txhashset_write(
|
fn txhashset_write(&self, _h: Hash, _txhashset_data: File, _peer_addr: SocketAddr) -> bool {
|
||||||
&self,
|
|
||||||
_h: Hash,
|
|
||||||
_txhashset_data: File,
|
|
||||||
_peer_addr: SocketAddr,
|
|
||||||
) -> bool {
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,10 @@ workspace = ".."
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hyper = "0.10"
|
hyper = "0.12"
|
||||||
itertools = "0.7"
|
itertools = "0.7"
|
||||||
lmdb-zero = "0.4.4"
|
lmdb-zero = "0.4.4"
|
||||||
rand = "0.3"
|
rand = "0.3"
|
||||||
router = "0.6"
|
|
||||||
slog = { version = "~2.2", features = ["max_level_trace", "release_max_level_trace"] }
|
slog = { version = "~2.2", features = ["max_level_trace", "release_max_level_trace"] }
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
//! a mining worker implementation
|
//! a mining worker implementation
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use std::io::Read;
|
|
||||||
use std::net::{SocketAddr, ToSocketAddrs};
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
@ -26,7 +25,7 @@ use std::time;
|
||||||
use chrono::prelude::{Utc};
|
use chrono::prelude::{Utc};
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
|
|
||||||
use hyper;
|
use api;
|
||||||
|
|
||||||
use p2p;
|
use p2p;
|
||||||
use pool::DandelionConfig;
|
use pool::DandelionConfig;
|
||||||
|
@ -268,22 +267,7 @@ pub fn dns_seeds() -> Box<Fn() -> Vec<SocketAddr> + Send> {
|
||||||
/// http. Easy method until we have a set of DNS names we can rely on.
|
/// http. Easy method until we have a set of DNS names we can rely on.
|
||||||
pub fn web_seeds() -> Box<Fn() -> Vec<SocketAddr> + Send> {
|
pub fn web_seeds() -> Box<Fn() -> Vec<SocketAddr> + Send> {
|
||||||
Box::new(|| {
|
Box::new(|| {
|
||||||
let client = hyper::Client::new();
|
let text: String = api::client::get(SEEDS_URL).expect("Failed to resolve seeds");
|
||||||
debug!(LOGGER, "Retrieving seed nodes from {}", &SEEDS_URL);
|
|
||||||
|
|
||||||
// http get, filtering out non 200 results
|
|
||||||
let mut res = client
|
|
||||||
.get(SEEDS_URL)
|
|
||||||
.send()
|
|
||||||
.expect("Failed to resolve seeds.");
|
|
||||||
if res.status != hyper::Ok {
|
|
||||||
panic!("Failed to resolve seeds, got status {}.", res.status);
|
|
||||||
}
|
|
||||||
let mut buf = vec![];
|
|
||||||
res.read_to_end(&mut buf)
|
|
||||||
.expect("Could not read seed list.");
|
|
||||||
|
|
||||||
let text = str::from_utf8(&buf[..]).expect("Corrupted seed list.");
|
|
||||||
let addrs = text.split_whitespace()
|
let addrs = text.split_whitespace()
|
||||||
.map(|s| s.parse().unwrap())
|
.map(|s| s.parse().unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
|
@ -188,15 +188,19 @@ fn test_p2p() {
|
||||||
let base_addr = server_config_one.base_addr;
|
let base_addr = server_config_one.base_addr;
|
||||||
let api_server_port = server_config_one.api_server_port;
|
let api_server_port = server_config_one.api_server_port;
|
||||||
|
|
||||||
// Check that when we get peer connected the peer is here
|
|
||||||
let peers_connected = get_connected_peers(&base_addr, api_server_port);
|
|
||||||
assert!(peers_connected.is_ok());
|
|
||||||
assert_eq!(peers_connected.unwrap().len(), 1);
|
|
||||||
|
|
||||||
// Check that peer all is also working
|
// Check that peer all is also working
|
||||||
let mut peers_all = get_all_peers(&base_addr, api_server_port);
|
let mut peers_all = get_all_peers(&base_addr, api_server_port);
|
||||||
assert!(peers_all.is_ok());
|
assert!(peers_all.is_ok());
|
||||||
assert_eq!(peers_all.unwrap().len(), 1);
|
let pall = peers_all.unwrap();
|
||||||
|
println!("Peers: {:?}", &pall);
|
||||||
|
assert_eq!(pall.len(), 1);
|
||||||
|
|
||||||
|
// Check that when we get peer connected the peer is here
|
||||||
|
let peers_connected = get_connected_peers(&base_addr, api_server_port);
|
||||||
|
assert!(peers_connected.is_ok());
|
||||||
|
let pc = peers_connected.unwrap();
|
||||||
|
println!("Peers connected: {:?}", &pc);
|
||||||
|
assert_eq!(pc.len(), 1);
|
||||||
|
|
||||||
// Check that the peer status is Healthy
|
// Check that the peer status is Healthy
|
||||||
let addr = format!(
|
let addr = format!(
|
||||||
|
@ -431,7 +435,7 @@ pub fn ban_peer(base_addr: &String, api_server_port: u16, peer_addr: &String) ->
|
||||||
"http://{}:{}/v1/peers/{}/ban",
|
"http://{}:{}/v1/peers/{}/ban",
|
||||||
base_addr, api_server_port, peer_addr
|
base_addr, api_server_port, peer_addr
|
||||||
);
|
);
|
||||||
api::client::post(url.as_str(), &"").map_err(|e| Error::API(e))
|
api::client::post_no_ret(url.as_str(), &"").map_err(|e| Error::API(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unban_peer(
|
pub fn unban_peer(
|
||||||
|
@ -443,7 +447,7 @@ pub fn unban_peer(
|
||||||
"http://{}:{}/v1/peers/{}/unban",
|
"http://{}:{}/v1/peers/{}/unban",
|
||||||
base_addr, api_server_port, peer_addr
|
base_addr, api_server_port, peer_addr
|
||||||
);
|
);
|
||||||
api::client::post(url.as_str(), &"").map_err(|e| Error::API(e))
|
api::client::post_no_ret(url.as_str(), &"").map_err(|e| Error::API(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_peer(
|
pub fn get_peer(
|
||||||
|
@ -455,7 +459,10 @@ pub fn get_peer(
|
||||||
"http://{}:{}/v1/peers/{}",
|
"http://{}:{}/v1/peers/{}",
|
||||||
base_addr, api_server_port, peer_addr
|
base_addr, api_server_port, peer_addr
|
||||||
);
|
);
|
||||||
api::client::get::<p2p::PeerData>(url.as_str()).map_err(|e| Error::API(e))
|
api::client::get::<p2p::PeerData>(url.as_str()).map_err(|e| {
|
||||||
|
println!("got error {:}", e);
|
||||||
|
Error::API(e)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_connected_peers(
|
pub fn get_connected_peers(
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
extern crate router;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate slog;
|
extern crate slog;
|
||||||
|
|
||||||
|
|
|
@ -337,9 +337,7 @@ impl LocalServerContainer {
|
||||||
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
||||||
wallet.keychain = Some(keychain);
|
wallet.keychain = Some(keychain);
|
||||||
let _ =
|
let _ =
|
||||||
wallet::controller::owner_single_use(
|
wallet::controller::owner_single_use(Arc::new(Mutex::new(Box::new(wallet))), |api| {
|
||||||
Arc::new(Mutex::new(Box::new(wallet))),
|
|
||||||
|api| {
|
|
||||||
let result = api.issue_send_tx(
|
let result = api.issue_send_tx(
|
||||||
amount,
|
amount,
|
||||||
minimum_confirmations,
|
minimum_confirmations,
|
||||||
|
@ -359,8 +357,7 @@ impl LocalServerContainer {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}).unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
||||||
).unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops the running wallet server
|
/// Stops the running wallet server
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
extern crate router;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate slog;
|
extern crate slog;
|
||||||
|
|
||||||
|
@ -59,7 +58,6 @@ fn basic_wallet_transactions() {
|
||||||
let coinbase_wallet_config = { coinbase_wallet.lock().unwrap().wallet_config.clone() };
|
let coinbase_wallet_config = { coinbase_wallet.lock().unwrap().wallet_config.clone() };
|
||||||
|
|
||||||
let coinbase_seed = LocalServerContainer::get_wallet_seed(&coinbase_wallet_config);
|
let coinbase_seed = LocalServerContainer::get_wallet_seed(&coinbase_wallet_config);
|
||||||
|
|
||||||
let _ = thread::spawn(move || {
|
let _ = thread::spawn(move || {
|
||||||
let mut w = coinbase_wallet.lock().unwrap();
|
let mut w = coinbase_wallet.lock().unwrap();
|
||||||
w.run_wallet(0);
|
w.run_wallet(0);
|
||||||
|
@ -72,7 +70,6 @@ fn basic_wallet_transactions() {
|
||||||
let target_wallet = Arc::new(Mutex::new(LocalServerContainer::new(recp_config).unwrap()));
|
let target_wallet = Arc::new(Mutex::new(LocalServerContainer::new(recp_config).unwrap()));
|
||||||
let target_wallet_cloned = target_wallet.clone();
|
let target_wallet_cloned = target_wallet.clone();
|
||||||
let recp_wallet_config = { target_wallet.lock().unwrap().wallet_config.clone() };
|
let recp_wallet_config = { target_wallet.lock().unwrap().wallet_config.clone() };
|
||||||
|
|
||||||
let recp_seed = LocalServerContainer::get_wallet_seed(&recp_wallet_config);
|
let recp_seed = LocalServerContainer::get_wallet_seed(&recp_wallet_config);
|
||||||
//Start up a second wallet, to receive
|
//Start up a second wallet, to receive
|
||||||
let _ = thread::spawn(move || {
|
let _ = thread::spawn(move || {
|
||||||
|
@ -80,8 +77,6 @@ fn basic_wallet_transactions() {
|
||||||
w.run_wallet(0);
|
w.run_wallet(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Spawn server and let it run for a bit
|
|
||||||
let _ = thread::spawn(move || {
|
|
||||||
let mut server_config = LocalServerContainerConfig::default();
|
let mut server_config = LocalServerContainerConfig::default();
|
||||||
server_config.name = String::from("server_one");
|
server_config.name = String::from("server_one");
|
||||||
server_config.p2p_server_port = 30000;
|
server_config.p2p_server_port = 30000;
|
||||||
|
@ -90,6 +85,8 @@ fn basic_wallet_transactions() {
|
||||||
server_config.start_wallet = false;
|
server_config.start_wallet = false;
|
||||||
server_config.coinbase_wallet_address =
|
server_config.coinbase_wallet_address =
|
||||||
String::from(format!("http://{}:{}", server_config.base_addr, 10002));
|
String::from(format!("http://{}:{}", server_config.base_addr, 10002));
|
||||||
|
// Spawn server and let it run for a bit
|
||||||
|
let _ = thread::spawn(move || {
|
||||||
let mut server_one = LocalServerContainer::new(server_config).unwrap();
|
let mut server_one = LocalServerContainer::new(server_config).unwrap();
|
||||||
server_one.run_server(120);
|
server_one.run_server(120);
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub fn ban_peer(config: &ServerConfig, peer_addr: &SocketAddr) {
|
||||||
config.api_http_addr,
|
config.api_http_addr,
|
||||||
peer_addr.to_string()
|
peer_addr.to_string()
|
||||||
);
|
);
|
||||||
match api::client::post(url.as_str(), ¶ms).map_err(|e| Error::API(e)) {
|
match api::client::post_no_ret(url.as_str(), ¶ms).map_err(|e| Error::API(e)) {
|
||||||
Ok(_) => writeln!(e, "Successfully banned peer {}", peer_addr.to_string()).unwrap(),
|
Ok(_) => writeln!(e, "Successfully banned peer {}", peer_addr.to_string()).unwrap(),
|
||||||
Err(_) => writeln!(e, "Failed to ban peer {}", peer_addr).unwrap(),
|
Err(_) => writeln!(e, "Failed to ban peer {}", peer_addr).unwrap(),
|
||||||
};
|
};
|
||||||
|
@ -71,7 +71,7 @@ pub fn unban_peer(config: &ServerConfig, peer_addr: &SocketAddr) {
|
||||||
config.api_http_addr,
|
config.api_http_addr,
|
||||||
peer_addr.to_string()
|
peer_addr.to_string()
|
||||||
);
|
);
|
||||||
match api::client::post(url.as_str(), ¶ms).map_err(|e| Error::API(e)) {
|
match api::client::post_no_ret(url.as_str(), ¶ms).map_err(|e| Error::API(e)) {
|
||||||
Ok(_) => writeln!(e, "Successfully unbanned peer {}", peer_addr).unwrap(),
|
Ok(_) => writeln!(e, "Successfully unbanned peer {}", peer_addr).unwrap(),
|
||||||
Err(_) => writeln!(e, "Failed to unban peer {}", peer_addr).unwrap(),
|
Err(_) => writeln!(e, "Failed to unban peer {}", peer_addr).unwrap(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,20 +12,19 @@ byteorder = "1"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
failure_derive = "0.1"
|
failure_derive = "0.1"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
hyper = "0.11"
|
hyper = "0.12"
|
||||||
iron = "0.5"
|
|
||||||
prettytable-rs = "0.7"
|
prettytable-rs = "0.7"
|
||||||
rand = "0.3"
|
rand = "0.3"
|
||||||
router = "0.5"
|
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
slog = { version = "~2.2", features = ["max_level_trace", "release_max_level_trace"] }
|
slog = { version = "~2.2", features = ["max_level_trace", "release_max_level_trace"] }
|
||||||
term = "0.5"
|
term = "0.5"
|
||||||
|
tokio = "0.1.7"
|
||||||
tokio-core = "0.1"
|
tokio-core = "0.1"
|
||||||
tokio-retry = "0.1"
|
tokio-retry = "0.1"
|
||||||
uuid = { version = "0.6", features = ["serde", "v4"] }
|
uuid = { version = "0.6", features = ["serde", "v4"] }
|
||||||
urlencoded = "0.5"
|
url = "1.7.0"
|
||||||
chrono = { version = "0.4.4", features = ["serde"] }
|
chrono = { version = "0.4.4", features = ["serde"] }
|
||||||
|
|
||||||
grin_api = { path = "../api" }
|
grin_api = { path = "../api" }
|
||||||
|
|
|
@ -18,13 +18,6 @@
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
use libwallet::types::*;
|
use libwallet::types::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
use hyper::header::ContentType;
|
|
||||||
use hyper::{self, Method, Request};
|
|
||||||
use serde_json;
|
|
||||||
use tokio_core::reactor;
|
|
||||||
|
|
||||||
use api;
|
use api;
|
||||||
use error::{Error, ErrorKind};
|
use error::{Error, ErrorKind};
|
||||||
|
@ -90,38 +83,9 @@ impl WalletClient for HTTPWalletClient {
|
||||||
let url = format!("{}/v1/wallet/foreign/receive_tx", dest);
|
let url = format!("{}/v1/wallet/foreign/receive_tx", dest);
|
||||||
debug!(LOGGER, "Posting transaction slate to {}", url);
|
debug!(LOGGER, "Posting transaction slate to {}", url);
|
||||||
|
|
||||||
let mut core = reactor::Core::new().context(libwallet::ErrorKind::ClientCallback(
|
let res = api::client::post(url.as_str(), slate).context(
|
||||||
"Sending transaction: Initialise API",
|
libwallet::ErrorKind::ClientCallback("Posting transaction slate"),
|
||||||
))?;
|
)?;
|
||||||
let client = hyper::Client::new(&core.handle());
|
|
||||||
|
|
||||||
let url_pool = url.to_owned();
|
|
||||||
|
|
||||||
let mut req = Request::new(
|
|
||||||
Method::Post,
|
|
||||||
url_pool
|
|
||||||
.parse::<hyper::Uri>()
|
|
||||||
.context(libwallet::ErrorKind::ClientCallback(
|
|
||||||
"Sending transaction: parsing URL",
|
|
||||||
))?,
|
|
||||||
);
|
|
||||||
req.headers_mut().set(ContentType::json());
|
|
||||||
let json = serde_json::to_string(&slate).context(libwallet::ErrorKind::ClientCallback(
|
|
||||||
"Sending transaction: parsing response",
|
|
||||||
))?;
|
|
||||||
req.set_body(json);
|
|
||||||
|
|
||||||
let work = client.request(req).and_then(|res| {
|
|
||||||
res.body().concat2().and_then(move |body| {
|
|
||||||
let slate: Slate = serde_json::from_slice(&body)
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
|
||||||
Ok(slate)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
let res = core.run(work)
|
|
||||||
.context(libwallet::ErrorKind::ClientCallback(
|
|
||||||
"Sending transaction: posting request",
|
|
||||||
))?;
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +98,7 @@ impl WalletClient for HTTPWalletClient {
|
||||||
} else {
|
} else {
|
||||||
url = format!("{}/v1/pool/push", dest);
|
url = format!("{}/v1/pool/push", dest);
|
||||||
}
|
}
|
||||||
api::client::post(url.as_str(), tx).context(libwallet::ErrorKind::ClientCallback(
|
api::client::post_no_ret(url.as_str(), tx).context(libwallet::ErrorKind::ClientCallback(
|
||||||
"Posting transaction to node",
|
"Posting transaction to node",
|
||||||
))?;
|
))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -247,30 +211,8 @@ pub fn create_coinbase(dest: &str, block_fees: &BlockFees) -> Result<CbData, Err
|
||||||
|
|
||||||
/// Makes a single request to the wallet API to create a new coinbase output.
|
/// Makes a single request to the wallet API to create a new coinbase output.
|
||||||
fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||||
let mut core = reactor::Core::new().context(ErrorKind::GenericError(
|
let res = api::client::post(url, block_fees).context(ErrorKind::GenericError(
|
||||||
"Could not create reactor".to_owned(),
|
"Posting create coinbase".to_string(),
|
||||||
))?;
|
))?;
|
||||||
let client = hyper::Client::new(&core.handle());
|
|
||||||
|
|
||||||
let mut req = Request::new(
|
|
||||||
Method::Post,
|
|
||||||
url.parse::<hyper::Uri>().context(ErrorKind::Uri)?,
|
|
||||||
);
|
|
||||||
req.headers_mut().set(ContentType::json());
|
|
||||||
let json = serde_json::to_string(&block_fees).context(ErrorKind::Format)?;
|
|
||||||
trace!(LOGGER, "Sending coinbase request: {:?}", json);
|
|
||||||
req.set_body(json);
|
|
||||||
|
|
||||||
let work = client.request(req).and_then(|res| {
|
|
||||||
res.body().concat2().and_then(move |body| {
|
|
||||||
trace!(LOGGER, "Returned Body: {:?}", body);
|
|
||||||
let coinbase: CbData =
|
|
||||||
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
|
||||||
Ok(coinbase)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let res = core.run(work)
|
|
||||||
.context(ErrorKind::GenericError("Could not run core".to_owned()))?;
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ extern crate serde_json;
|
||||||
extern crate slog;
|
extern crate slog;
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
extern crate term;
|
extern crate term;
|
||||||
extern crate urlencoded;
|
extern crate url;
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
|
|
||||||
extern crate bodyparser;
|
extern crate bodyparser;
|
||||||
|
@ -36,9 +36,7 @@ extern crate failure;
|
||||||
extern crate failure_derive;
|
extern crate failure_derive;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
extern crate iron;
|
extern crate tokio;
|
||||||
#[macro_use]
|
|
||||||
extern crate router;
|
|
||||||
extern crate tokio_core;
|
extern crate tokio_core;
|
||||||
extern crate tokio_retry;
|
extern crate tokio_retry;
|
||||||
|
|
||||||
|
|
|
@ -15,18 +15,18 @@
|
||||||
//! Controller for wallet.. instantiates and handles listeners (or single-run
|
//! Controller for wallet.. instantiates and handles listeners (or single-run
|
||||||
//! invocations) as needed.
|
//! invocations) as needed.
|
||||||
//! Still experimental
|
//! Still experimental
|
||||||
use api::ApiServer;
|
use api::{ApiServer, Handler, ResponseFuture, Router};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use bodyparser;
|
use futures::future::{err, ok};
|
||||||
use iron::prelude::{IronError, IronResult, Plugin, Request, Response};
|
use futures::{Future, Stream};
|
||||||
use iron::{status, Handler, Headers};
|
use hyper::{Body, Request, Response, StatusCode};
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use urlencoded::UrlEncodedQuery;
|
|
||||||
|
|
||||||
use failure::Fail;
|
|
||||||
|
|
||||||
use keychain::Keychain;
|
use keychain::Keychain;
|
||||||
use libtx::slate::Slate;
|
use libtx::slate::Slate;
|
||||||
|
@ -35,6 +35,7 @@ use libwallet::types::{
|
||||||
BlockFees, CbData, OutputData, SendTXArgs, TxLogEntry, WalletBackend, WalletClient, WalletInfo,
|
BlockFees, CbData, OutputData, SendTXArgs, TxLogEntry, WalletBackend, WalletClient, WalletInfo,
|
||||||
};
|
};
|
||||||
use libwallet::{Error, ErrorKind};
|
use libwallet::{Error, ErrorKind};
|
||||||
|
use url::form_urlencoded;
|
||||||
|
|
||||||
use util::LOGGER;
|
use util::LOGGER;
|
||||||
|
|
||||||
|
@ -64,72 +65,102 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thread_local!(static OWNER_ROUTER: RefCell<Option<Router>> = RefCell::new(None));
|
||||||
|
|
||||||
/// Listener version, providing same API but listening for requests on a
|
/// Listener version, providing same API but listening for requests on a
|
||||||
/// port and wrapping the calls
|
/// port and wrapping the calls
|
||||||
pub fn owner_listener<T: ?Sized, C, K>(wallet: Box<T>, addr: &str) -> Result<(), Error>
|
pub fn owner_listener<T: ?Sized, C, K>(wallet: Box<T>, addr: &str) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||||
OwnerAPIGetHandler<T, C, K>: Handler,
|
OwnerAPIHandler<T, C, K>: Handler,
|
||||||
OwnerAPIPostHandler<T, C, K>: Handler,
|
C: WalletClient + 'static,
|
||||||
C: WalletClient,
|
K: Keychain + 'static,
|
||||||
K: Keychain,
|
|
||||||
{
|
{
|
||||||
let wallet_arc = Arc::new(Mutex::new(wallet));
|
let wallet_arc = Arc::new(Mutex::new(wallet));
|
||||||
let api_get_handler = OwnerAPIGetHandler::new(wallet_arc.clone());
|
let api_handler = OwnerAPIHandler::new(wallet_arc);
|
||||||
let api_post_handler = OwnerAPIPostHandler::new(wallet_arc);
|
|
||||||
let api_options_handler = OwnerAPIOptionsHandler {};
|
|
||||||
|
|
||||||
let router = router!(
|
let mut orouter = Router::new();
|
||||||
owner_options: options "/wallet/owner/*" => api_options_handler,
|
orouter
|
||||||
owner_get: get "/wallet/owner/*" => api_get_handler,
|
.add_route("/v1/wallet/owner/**", Box::new(api_handler))
|
||||||
owner_post: post "/wallet/owner/*" => api_post_handler,
|
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
||||||
);
|
|
||||||
|
|
||||||
let mut apis = ApiServer::new("/v1".to_string());
|
OWNER_ROUTER.with(move |router| {
|
||||||
apis.register_handler(router);
|
*router.borrow_mut() = Some(orouter);
|
||||||
match apis.start(addr) {
|
let mut apis = ApiServer::new();
|
||||||
Err(e) => error!(
|
info!(LOGGER, "Starting HTTP Owner API server at {}.", addr);
|
||||||
LOGGER,
|
let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address");
|
||||||
"Failed to start Grin wallet owner API listener: {}.", e
|
apis.start(socket_addr, &handle_owner).unwrap_or_else(|e| {
|
||||||
),
|
error!(LOGGER, "Failed to start API HTTP server: {}.", e);
|
||||||
Ok(_) => info!(LOGGER, "Grin wallet owner API listener started at {}", addr),
|
})
|
||||||
};
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_owner(req: Request<Body>) -> ResponseFuture {
|
||||||
|
OWNER_ROUTER.with(|router| match *router.borrow() {
|
||||||
|
Some(ref h) => h.handle(req),
|
||||||
|
None => {
|
||||||
|
error!(LOGGER, "No HTTP API router configured");
|
||||||
|
Box::new(ok(response(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"No router configured",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local!(static FOREIGN_ROUTER: RefCell<Option<Router>> = RefCell::new(None));
|
||||||
|
|
||||||
/// Listener version, providing same API but listening for requests on a
|
/// Listener version, providing same API but listening for requests on a
|
||||||
/// port and wrapping the calls
|
/// port and wrapping the calls
|
||||||
pub fn foreign_listener<T: ?Sized, C, K>(wallet: Box<T>, addr: &str) -> Result<(), Error>
|
pub fn foreign_listener<T: ?Sized, C, K>(wallet: Box<T>, addr: &str) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||||
ForeignAPIHandler<T, C, K>: Handler,
|
C: WalletClient + 'static,
|
||||||
C: WalletClient,
|
K: Keychain + 'static,
|
||||||
K: Keychain,
|
|
||||||
{
|
{
|
||||||
let api_handler = ForeignAPIHandler::new(Arc::new(Mutex::new(wallet)));
|
let api_handler = ForeignAPIHandler::new(Arc::new(Mutex::new(wallet)));
|
||||||
|
|
||||||
let router = router!(
|
let mut router = Router::new();
|
||||||
receive_tx: post "/wallet/foreign/*" => api_handler,
|
router
|
||||||
);
|
.add_route("/v1/wallet/foreign/**", Box::new(api_handler))
|
||||||
|
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
||||||
|
|
||||||
|
FOREIGN_ROUTER.with(move |frouter| {
|
||||||
|
*frouter.borrow_mut() = Some(router);
|
||||||
|
let mut apis = ApiServer::new();
|
||||||
|
info!(LOGGER, "Starting HTTP Foreign API server at {}.", addr);
|
||||||
|
let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address");
|
||||||
|
apis.start(socket_addr, &handle_foreign)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
error!(LOGGER, "Failed to start API HTTP server: {}.", e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
let mut apis = ApiServer::new("/v1".to_string());
|
|
||||||
apis.register_handler(router);
|
|
||||||
match apis.start(addr) {
|
|
||||||
Err(e) => error!(
|
|
||||||
LOGGER,
|
|
||||||
"Failed to start Grin wallet foreign listener: {}.", e
|
|
||||||
),
|
|
||||||
Ok(_) => info!(LOGGER, "Grin wallet foreign listener started at {}", addr),
|
|
||||||
};
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
/// API Handler/Wrapper for owner functions
|
|
||||||
|
|
||||||
pub struct OwnerAPIGetHandler<T: ?Sized, C, K>
|
fn handle_foreign(req: Request<Body>) -> ResponseFuture {
|
||||||
|
FOREIGN_ROUTER.with(|router| match *router.borrow() {
|
||||||
|
Some(ref h) => h.handle(req),
|
||||||
|
None => {
|
||||||
|
error!(LOGGER, "No HTTP API router configured");
|
||||||
|
Box::new(ok(response(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"No router configured",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type WalletResponseFuture = Box<Future<Item = Response<Body>, Error = Error> + Send>;
|
||||||
|
|
||||||
|
/// API Handler/Wrapper for owner functions
|
||||||
|
pub struct OwnerAPIHandler<T: ?Sized, C, K>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||||
C: WalletClient,
|
C: WalletClient + 'static,
|
||||||
K: Keychain,
|
K: Keychain + 'static,
|
||||||
{
|
{
|
||||||
/// Wallet instance
|
/// Wallet instance
|
||||||
pub wallet: Arc<Mutex<Box<T>>>,
|
pub wallet: Arc<Mutex<Box<T>>>,
|
||||||
|
@ -137,15 +168,15 @@ where
|
||||||
phantom_c: PhantomData<C>,
|
phantom_c: PhantomData<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized, C, K> OwnerAPIGetHandler<T, C, K>
|
impl<T: ?Sized, C, K> OwnerAPIHandler<T, C, K>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||||
C: WalletClient,
|
C: WalletClient + 'static,
|
||||||
K: Keychain,
|
K: Keychain + 'static,
|
||||||
{
|
{
|
||||||
/// Create a new owner API handler for GET methods
|
/// Create a new owner API handler for GET methods
|
||||||
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> OwnerAPIGetHandler<T, C, K> {
|
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> OwnerAPIHandler<T, C, K> {
|
||||||
OwnerAPIGetHandler {
|
OwnerAPIHandler {
|
||||||
wallet,
|
wallet,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
phantom_c: PhantomData,
|
phantom_c: PhantomData,
|
||||||
|
@ -154,13 +185,14 @@ where
|
||||||
|
|
||||||
fn retrieve_outputs(
|
fn retrieve_outputs(
|
||||||
&self,
|
&self,
|
||||||
req: &mut Request,
|
req: &Request<Body>,
|
||||||
api: &mut APIOwner<T, C, K>,
|
api: APIOwner<T, C, K>,
|
||||||
) -> Result<(bool, Vec<OutputData>), Error> {
|
) -> Result<(bool, Vec<OutputData>), Error> {
|
||||||
let mut update_from_node = false;
|
let mut update_from_node = false;
|
||||||
let mut id = None;
|
let mut id = None;
|
||||||
let mut show_spent = false;
|
let mut show_spent = false;
|
||||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
let params = parse_params(req);
|
||||||
|
|
||||||
if let Some(_) = params.get("refresh") {
|
if let Some(_) = params.get("refresh") {
|
||||||
update_from_node = true;
|
update_from_node = true;
|
||||||
}
|
}
|
||||||
|
@ -172,18 +204,19 @@ where
|
||||||
id = Some(i.parse().unwrap());
|
id = Some(i.parse().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
api.retrieve_outputs(show_spent, update_from_node, id)
|
api.retrieve_outputs(show_spent, update_from_node, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn retrieve_txs(
|
fn retrieve_txs(
|
||||||
&self,
|
&self,
|
||||||
req: &mut Request,
|
req: &Request<Body>,
|
||||||
api: &mut APIOwner<T, C, K>,
|
api: APIOwner<T, C, K>,
|
||||||
) -> Result<(bool, Vec<TxLogEntry>), Error> {
|
) -> Result<(bool, Vec<TxLogEntry>), Error> {
|
||||||
let mut id = None;
|
let mut id = None;
|
||||||
let mut update_from_node = false;
|
let mut update_from_node = false;
|
||||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
|
||||||
|
let params = parse_params(req);
|
||||||
|
|
||||||
if let Some(_) = params.get("refresh") {
|
if let Some(_) = params.get("refresh") {
|
||||||
update_from_node = true;
|
update_from_node = true;
|
||||||
}
|
}
|
||||||
|
@ -192,218 +225,134 @@ where
|
||||||
id = Some(i.parse().unwrap());
|
id = Some(i.parse().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
api.retrieve_txs(update_from_node, id)
|
api.retrieve_txs(update_from_node, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn retrieve_summary_info(
|
fn retrieve_summary_info(
|
||||||
&self,
|
&self,
|
||||||
req: &mut Request,
|
req: &Request<Body>,
|
||||||
api: &mut APIOwner<T, C, K>,
|
mut api: APIOwner<T, C, K>,
|
||||||
) -> Result<(bool, WalletInfo), Error> {
|
) -> Result<(bool, WalletInfo), Error> {
|
||||||
let mut update_from_node = false;
|
let update_from_node = param_exists(req, "refresh");
|
||||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
|
||||||
if let Some(_) = params.get("refresh") {
|
|
||||||
update_from_node = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
api.retrieve_summary_info(update_from_node)
|
api.retrieve_summary_info(update_from_node)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_height(
|
fn node_height(
|
||||||
&self,
|
&self,
|
||||||
_req: &mut Request,
|
_req: &Request<Body>,
|
||||||
api: &mut APIOwner<T, C, K>,
|
mut api: APIOwner<T, C, K>,
|
||||||
) -> Result<(u64, bool), Error> {
|
) -> Result<(u64, bool), Error> {
|
||||||
api.node_height()
|
api.node_height()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_request(
|
fn handle_get_request(&self, req: &Request<Body>) -> Result<Response<Body>, Error> {
|
||||||
&self,
|
let api = APIOwner::new(self.wallet.clone());
|
||||||
req: &mut Request,
|
Ok(match req.uri()
|
||||||
api: &mut APIOwner<T, C, K>,
|
.path()
|
||||||
) -> IronResult<Response> {
|
.trim_right_matches("/")
|
||||||
let url = req.url.clone();
|
.rsplit("/")
|
||||||
let path_elems = url.path();
|
.next()
|
||||||
match *path_elems.last().unwrap() {
|
|
||||||
"retrieve_outputs" => json_response(&self.retrieve_outputs(req, api)
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
|
|
||||||
"retrieve_txs" => json_response(&self.retrieve_txs(req, api)
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
|
|
||||||
"retrieve_summary_info" => json_response(&self.retrieve_summary_info(req, api)
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
|
|
||||||
"node_height" => json_response(&self.node_height(req, api)
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
|
|
||||||
_ => Err(IronError::new(
|
|
||||||
Fail::compat(ErrorKind::Hyper),
|
|
||||||
status::BadRequest,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ?Sized, C, K> Handler for OwnerAPIGetHandler<T, C, K>
|
|
||||||
where
|
|
||||||
T: WalletBackend<C, K> + Send + Sync + 'static,
|
|
||||||
C: WalletClient + 'static,
|
|
||||||
K: Keychain + 'static,
|
|
||||||
{
|
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
|
||||||
let mut api = APIOwner::new(self.wallet.clone());
|
|
||||||
let mut resp_json = self.handle_request(req, &mut api);
|
|
||||||
if !resp_json.is_err() {
|
|
||||||
resp_json
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.headers
|
{
|
||||||
.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
|
"retrieve_outputs" => json_response(&self.retrieve_outputs(req, api)?),
|
||||||
}
|
"retrieve_summary_info" => json_response(&self.retrieve_summary_info(req, api)?),
|
||||||
resp_json
|
"node_height" => json_response(&self.node_height(req, api)?),
|
||||||
}
|
"retrieve_txs" => json_response(&self.retrieve_txs(req, api)?),
|
||||||
}
|
_ => response(StatusCode::BAD_REQUEST, ""),
|
||||||
|
})
|
||||||
/// Handles all owner API POST requests
|
|
||||||
pub struct OwnerAPIPostHandler<T: ?Sized, C, K>
|
|
||||||
where
|
|
||||||
T: WalletBackend<C, K>,
|
|
||||||
C: WalletClient,
|
|
||||||
K: Keychain,
|
|
||||||
{
|
|
||||||
/// Wallet instance
|
|
||||||
pub wallet: Arc<Mutex<Box<T>>>,
|
|
||||||
phantom: PhantomData<K>,
|
|
||||||
phantom_c: PhantomData<C>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ?Sized, C, K> OwnerAPIPostHandler<T, C, K>
|
|
||||||
where
|
|
||||||
T: WalletBackend<C, K>,
|
|
||||||
C: WalletClient,
|
|
||||||
K: Keychain,
|
|
||||||
{
|
|
||||||
/// New POST handler
|
|
||||||
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> OwnerAPIPostHandler<T, C, K> {
|
|
||||||
OwnerAPIPostHandler {
|
|
||||||
wallet,
|
|
||||||
phantom: PhantomData,
|
|
||||||
phantom_c: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn issue_send_tx(
|
fn issue_send_tx(
|
||||||
&self,
|
&self,
|
||||||
req: &mut Request,
|
req: Request<Body>,
|
||||||
api: &mut APIOwner<T, C, K>,
|
mut api: APIOwner<T, C, K>,
|
||||||
) -> Result<Slate, Error> {
|
) -> Box<Future<Item = Slate, Error = Error> + Send> {
|
||||||
let struct_body = req.get::<bodyparser::Struct<SendTXArgs>>();
|
Box::new(parse_body(req).and_then(move |args: SendTXArgs| {
|
||||||
match struct_body {
|
api.issue_send_tx(
|
||||||
Ok(Some(args)) => api.issue_send_tx(
|
|
||||||
args.amount,
|
args.amount,
|
||||||
args.minimum_confirmations,
|
args.minimum_confirmations,
|
||||||
&args.dest,
|
&args.dest,
|
||||||
args.max_outputs,
|
args.max_outputs,
|
||||||
args.selection_strategy_is_use_all,
|
args.selection_strategy_is_use_all,
|
||||||
),
|
)
|
||||||
Ok(None) => {
|
}))
|
||||||
error!(LOGGER, "Missing request body: issue_send_tx");
|
|
||||||
Err(ErrorKind::GenericError(
|
|
||||||
"Invalid request body: issue_send_tx".to_owned(),
|
|
||||||
))?
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(LOGGER, "Invalid request body: issue_send_tx {:?}", e);
|
|
||||||
Err(ErrorKind::GenericError(
|
|
||||||
"Invalid request body: issue_send_tx".to_owned(),
|
|
||||||
))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn issue_burn_tx(&self, _req: &mut Request, api: &mut APIOwner<T, C, K>) -> Result<(), Error> {
|
fn issue_burn_tx(
|
||||||
// TODO: Args
|
|
||||||
api.issue_burn_tx(60, 10, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_request(
|
|
||||||
&self,
|
&self,
|
||||||
req: &mut Request,
|
_req: Request<Body>,
|
||||||
api: &mut APIOwner<T, C, K>,
|
mut api: APIOwner<T, C, K>,
|
||||||
) -> Result<String, Error> {
|
) -> Box<Future<Item = (), Error = Error> + Send> {
|
||||||
let url = req.url.clone();
|
// TODO: Args
|
||||||
let path_elems = url.path();
|
Box::new(match api.issue_burn_tx(60, 10, 1000) {
|
||||||
match *path_elems.last().unwrap() {
|
Ok(_) => ok(()),
|
||||||
"issue_send_tx" => json_response_pretty(&self.issue_send_tx(req, api)?),
|
Err(e) => err(e),
|
||||||
"issue_burn_tx" => json_response_pretty(&self.issue_burn_tx(req, api)?),
|
})
|
||||||
_ => Err(ErrorKind::GenericError(
|
}
|
||||||
|
|
||||||
|
fn handle_post_request(&self, req: Request<Body>) -> WalletResponseFuture {
|
||||||
|
let api = APIOwner::new(self.wallet.clone());
|
||||||
|
match req.uri()
|
||||||
|
.path()
|
||||||
|
.trim_right_matches("/")
|
||||||
|
.rsplit("/")
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
"issue_send_tx" => Box::new(
|
||||||
|
self.issue_send_tx(req, api)
|
||||||
|
.and_then(|slate| ok(json_response_pretty(&slate))),
|
||||||
|
),
|
||||||
|
"issue_burn_tx" => Box::new(
|
||||||
|
self.issue_burn_tx(req, api)
|
||||||
|
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
||||||
|
),
|
||||||
|
_ => Box::new(err(ErrorKind::GenericError(
|
||||||
"Unknown error handling post request".to_owned(),
|
"Unknown error handling post request".to_owned(),
|
||||||
))?,
|
).into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_error_response(&self, e: Error) -> IronResult<Response> {
|
|
||||||
let mut headers = Headers::new();
|
|
||||||
headers.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
|
|
||||||
headers.set_raw(
|
|
||||||
"access-control-allow-headers",
|
|
||||||
vec![b"Content-Type".to_vec()],
|
|
||||||
);
|
|
||||||
let message = format!("{}", e.kind());
|
|
||||||
let mut r = Response::with((status::InternalServerError, message));
|
|
||||||
r.headers = headers;
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_ok_response(&self, json: &str) -> IronResult<Response> {
|
|
||||||
let mut headers = Headers::new();
|
|
||||||
headers.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
|
|
||||||
let mut r = Response::with((status::Ok, json));
|
|
||||||
r.headers = headers;
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized, C, K> Handler for OwnerAPIPostHandler<T, C, K>
|
impl<T: ?Sized, C, K> Handler for OwnerAPIHandler<T, C, K>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K> + Send + Sync + 'static,
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||||
C: WalletClient + 'static,
|
C: WalletClient + 'static,
|
||||||
K: Keychain + 'static,
|
K: Keychain + 'static,
|
||||||
{
|
{
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn get(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
let mut api = APIOwner::new(self.wallet.clone());
|
match self.handle_get_request(&req) {
|
||||||
let resp = match self.handle_request(req, &mut api) {
|
Ok(r) => Box::new(ok(r)),
|
||||||
Ok(r) => self.create_ok_response(&r),
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(LOGGER, "Request Error: {:?}", e);
|
error!(LOGGER, "Request Error: {:?}", e);
|
||||||
self.create_error_response(e)
|
Box::new(ok(create_error_response(e)))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
resp
|
}
|
||||||
|
|
||||||
|
fn post(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
|
Box::new(
|
||||||
|
self.handle_post_request(req)
|
||||||
|
.and_then(|r| ok(r))
|
||||||
|
.or_else(|e| {
|
||||||
|
error!(LOGGER, "Request Error: {:?}", e);
|
||||||
|
ok(create_error_response(e))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn options(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
|
Box::new(ok(create_ok_response("{}")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Options handler
|
|
||||||
pub struct OwnerAPIOptionsHandler {}
|
|
||||||
|
|
||||||
impl Handler for OwnerAPIOptionsHandler where {
|
|
||||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
|
||||||
let mut resp_json = Ok(Response::with((status::Ok, "{}")));
|
|
||||||
let mut headers = Headers::new();
|
|
||||||
headers.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
|
|
||||||
headers.set_raw(
|
|
||||||
"access-control-allow-headers",
|
|
||||||
vec![b"Content-Type".to_vec()],
|
|
||||||
);
|
|
||||||
resp_json.as_mut().unwrap().headers = headers;
|
|
||||||
resp_json
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// API Handler/Wrapper for foreign functions
|
/// API Handler/Wrapper for foreign functions
|
||||||
|
|
||||||
pub struct ForeignAPIHandler<T: ?Sized, C, K>
|
pub struct ForeignAPIHandler<T: ?Sized, C, K>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||||
C: WalletClient,
|
C: WalletClient + 'static,
|
||||||
K: Keychain,
|
K: Keychain + 'static,
|
||||||
{
|
{
|
||||||
/// Wallet instance
|
/// Wallet instance
|
||||||
pub wallet: Arc<Mutex<Box<T>>>,
|
pub wallet: Arc<Mutex<Box<T>>>,
|
||||||
|
@ -413,9 +362,9 @@ where
|
||||||
|
|
||||||
impl<T: ?Sized, C, K> ForeignAPIHandler<T, C, K>
|
impl<T: ?Sized, C, K> ForeignAPIHandler<T, C, K>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||||
C: WalletClient,
|
C: WalletClient + 'static,
|
||||||
K: Keychain,
|
K: Keychain + 'static,
|
||||||
{
|
{
|
||||||
/// create a new api handler
|
/// create a new api handler
|
||||||
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> ForeignAPIHandler<T, C, K> {
|
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> ForeignAPIHandler<T, C, K> {
|
||||||
|
@ -428,55 +377,43 @@ where
|
||||||
|
|
||||||
fn build_coinbase(
|
fn build_coinbase(
|
||||||
&self,
|
&self,
|
||||||
req: &mut Request,
|
req: Request<Body>,
|
||||||
api: &mut APIForeign<T, C, K>,
|
mut api: APIForeign<T, C, K>,
|
||||||
) -> Result<CbData, Error> {
|
) -> Box<Future<Item = CbData, Error = Error> + Send> {
|
||||||
let struct_body = req.get::<bodyparser::Struct<BlockFees>>();
|
Box::new(parse_body(req).and_then(move |block_fees| api.build_coinbase(&block_fees)))
|
||||||
match struct_body {
|
|
||||||
Ok(Some(block_fees)) => api.build_coinbase(&block_fees),
|
|
||||||
Ok(None) => {
|
|
||||||
error!(LOGGER, "Missing request body: build_coinbase");
|
|
||||||
Err(ErrorKind::GenericError(
|
|
||||||
"Invalid request body: build_coinbase".to_owned(),
|
|
||||||
))?
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(LOGGER, "Invalid request body: build_coinbase: {:?}", e);
|
|
||||||
Err(ErrorKind::GenericError(
|
|
||||||
"Invalid request body: build_coinbase".to_owned(),
|
|
||||||
))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive_tx(&self, req: &mut Request, api: &mut APIForeign<T, C, K>) -> Result<Slate, Error> {
|
fn receive_tx(
|
||||||
let struct_body = req.get::<bodyparser::Struct<Slate>>();
|
|
||||||
if let Ok(Some(mut slate)) = struct_body {
|
|
||||||
api.receive_tx(&mut slate)?;
|
|
||||||
Ok(slate.clone())
|
|
||||||
} else {
|
|
||||||
Err(ErrorKind::GenericError(
|
|
||||||
"Invalid request body: receive_tx".to_owned(),
|
|
||||||
))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_request(
|
|
||||||
&self,
|
&self,
|
||||||
req: &mut Request,
|
req: Request<Body>,
|
||||||
api: &mut APIForeign<T, C, K>,
|
mut api: APIForeign<T, C, K>,
|
||||||
) -> IronResult<Response> {
|
) -> Box<Future<Item = Slate, Error = Error> + Send> {
|
||||||
let url = req.url.clone();
|
Box::new(
|
||||||
let path_elems = url.path();
|
parse_body(req).and_then(move |mut slate| match api.receive_tx(&mut slate) {
|
||||||
match *path_elems.last().unwrap() {
|
Ok(_) => ok(slate.clone()),
|
||||||
"build_coinbase" => json_response(&self.build_coinbase(req, api)
|
Err(e) => err(e),
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
|
}),
|
||||||
"receive_tx" => json_response(&self.receive_tx(req, api)
|
)
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
|
}
|
||||||
_ => Err(IronError::new(
|
|
||||||
Fail::compat(ErrorKind::Hyper),
|
fn handle_request(&self, req: Request<Body>) -> WalletResponseFuture {
|
||||||
status::BadRequest,
|
let api = *APIForeign::new(self.wallet.clone());
|
||||||
)),
|
match req.uri()
|
||||||
|
.path()
|
||||||
|
.trim_right_matches("/")
|
||||||
|
.rsplit("/")
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
"build_coinbase" => Box::new(
|
||||||
|
self.build_coinbase(req, api)
|
||||||
|
.and_then(|res| ok(json_response(&res))),
|
||||||
|
),
|
||||||
|
"receive_tx" => Box::new(
|
||||||
|
self.receive_tx(req, api)
|
||||||
|
.and_then(|res| ok(json_response(&res))),
|
||||||
|
),
|
||||||
|
_ => Box::new(ok(response(StatusCode::BAD_REQUEST, "unknown action"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -486,32 +423,92 @@ where
|
||||||
C: WalletClient + Send + Sync + 'static,
|
C: WalletClient + Send + Sync + 'static,
|
||||||
K: Keychain + 'static,
|
K: Keychain + 'static,
|
||||||
{
|
{
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn post(&self, req: Request<Body>) -> ResponseFuture {
|
||||||
let mut api = APIForeign::new(self.wallet.clone());
|
Box::new(self.handle_request(req).and_then(|r| ok(r)).or_else(|e| {
|
||||||
let resp_json = self.handle_request(req, &mut *api);
|
error!(LOGGER, "Request Error: {:?}", e);
|
||||||
resp_json
|
ok(create_error_response(e))
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility to serialize a struct into JSON and produce a sensible IronResult
|
// Utility to serialize a struct into JSON and produce a sensible Response
|
||||||
// out of it.
|
// out of it.
|
||||||
fn json_response<T>(s: &T) -> IronResult<Response>
|
fn json_response<T>(s: &T) -> Response<Body>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
match serde_json::to_string(s) {
|
match serde_json::to_string(s) {
|
||||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
Ok(json) => response(StatusCode::OK, json),
|
||||||
Err(_) => Ok(Response::with((status::InternalServerError, ""))),
|
Err(_) => response(StatusCode::INTERNAL_SERVER_ERROR, ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pretty-printed version of above
|
// pretty-printed version of above
|
||||||
fn json_response_pretty<T>(s: &T) -> Result<String, Error>
|
fn json_response_pretty<T>(s: &T) -> Response<Body>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
match serde_json::to_string_pretty(s) {
|
match serde_json::to_string_pretty(s) {
|
||||||
Ok(json) => Ok(json),
|
Ok(json) => response(StatusCode::OK, json),
|
||||||
Err(_) => Err(ErrorKind::Format)?,
|
Err(_) => response(StatusCode::INTERNAL_SERVER_ERROR, ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_error_response(e: Error) -> Response<Body> {
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
.header("access-control-allow-origin", "*")
|
||||||
|
.header("access-control-allow-headers", "Content-Type")
|
||||||
|
.body(format!("{}", e.kind()).into())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_ok_response(json: &str) -> Response<Body> {
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.header("access-control-allow-origin", "*")
|
||||||
|
.body(json.to_string().into())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response<T: Into<Body>>(status: StatusCode, text: T) -> Response<Body> {
|
||||||
|
Response::builder()
|
||||||
|
.status(status)
|
||||||
|
.header("access-control-allow-origin", "*")
|
||||||
|
.body(text.into())
|
||||||
|
.unwrap()
|
||||||
|
//let mut resp = Response::new(text.into());
|
||||||
|
//*resp.status_mut() = status;
|
||||||
|
//resp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_params(req: &Request<Body>) -> HashMap<String, Vec<String>> {
|
||||||
|
match req.uri().query() {
|
||||||
|
Some(query_string) => form_urlencoded::parse(query_string.as_bytes())
|
||||||
|
.into_owned()
|
||||||
|
.fold(HashMap::new(), |mut hm, (k, v)| {
|
||||||
|
hm.entry(k).or_insert(vec![]).push(v);
|
||||||
|
hm
|
||||||
|
}),
|
||||||
|
None => HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_exists(req: &Request<Body>, param: &str) -> bool {
|
||||||
|
parse_params(req).get(param).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_body<T>(req: Request<Body>) -> Box<Future<Item = T, Error = Error> + Send>
|
||||||
|
where
|
||||||
|
for<'de> T: Deserialize<'de> + Send + 'static,
|
||||||
|
{
|
||||||
|
Box::new(
|
||||||
|
req.into_body()
|
||||||
|
.concat2()
|
||||||
|
.map_err(|_| ErrorKind::GenericError("Failed to read request".to_owned()).into())
|
||||||
|
.and_then(|body| match serde_json::from_reader(&body.to_vec()[..]) {
|
||||||
|
Ok(obj) => ok(obj),
|
||||||
|
Err(_) => err(ErrorKind::GenericError("Invalid request body".to_owned()).into()),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue