Skip to content

Commit

Permalink
Refactor API handlers (#2572)
Browse files Browse the repository at this point in the history
Also add some API tests
  • Loading branch information
hashmap authored Feb 15, 2019
1 parent aad0e94 commit ac6ed71
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 175 deletions.
9 changes: 9 additions & 0 deletions api/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ where
}
}

/// Helper function to easily issue a HTTP GET request
/// on a given URL that returns nothing. Handles request
/// building and response code checking.
pub fn get_no_ret(url: &str, api_secret: Option<String>) -> Result<(), Error> {
let req = build_request(url, "GET", api_secret, None)?;
send_request(req)?;
Ok(())
}

/// 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
/// building, JSON serialization and deserialization, and response code
Expand Down
12 changes: 2 additions & 10 deletions api/src/handlers/blocks_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::utils::{get_output, w};
use crate::chain;
use crate::core::core::hash::Hash;
Expand Down Expand Up @@ -68,10 +67,7 @@ impl HeaderHandler {

impl Handler for HeaderHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture {
let el = match req.uri().path().trim_right_matches('/').rsplit('/').next() {
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
Some(el) => el,
};
let el = right_path_element!(req);
result_to_response(self.get_header(el.to_string()))
}
}
Expand Down Expand Up @@ -130,11 +126,7 @@ fn check_block_param(input: &String) -> Result<(), Error> {

impl Handler for BlockHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture {
let el = match req.uri().path().trim_right_matches('/').rsplit('/').next() {
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
Some(el) => el,
};

let el = right_path_element!(req);
let h = match self.parse_input(el.to_string()) {
Err(e) => {
return response(
Expand Down
76 changes: 13 additions & 63 deletions api/src/handlers/chain_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ use crate::util;
use crate::util::secp::pedersen::Commitment;
use crate::web::*;
use hyper::{Body, Request, StatusCode};
use std::collections::HashMap;
use std::sync::Weak;
use url::form_urlencoded;

/// Chain handler. Get the head details.
/// GET /v1/chain
Expand Down Expand Up @@ -101,21 +99,9 @@ impl OutputHandler {
fn outputs_by_ids(&self, req: &Request<Body>) -> Result<Vec<Output>, Error> {
let mut commitments: Vec<String> = vec![];

let query = match req.uri().query() {
Some(q) => q,
None => return Err(ErrorKind::RequestError("no query string".to_owned()))?,
};
let params = form_urlencoded::parse(query.as_bytes())
.into_owned()
.collect::<Vec<(String, String)>>();

for (k, id) in params {
if k == "id" {
for id in id.split(',') {
commitments.push(id.to_owned());
}
}
}
let query = must_get_query!(req);
let params = QueryParams::from(query);
params.process_multival_param("id", |id| commitments.push(id.to_owned()));

let mut outputs: Vec<Output> = vec![];
for x in commitments {
Expand Down Expand Up @@ -159,49 +145,17 @@ impl OutputHandler {
// returns outputs for a specified range of blocks
fn outputs_block_batch(&self, req: &Request<Body>) -> Result<Vec<BlockOutputs>, Error> {
let mut commitments: Vec<Commitment> = vec![];
let mut start_height = 1;
let mut end_height = 1;
let mut include_rp = false;

let query = match req.uri().query() {
Some(q) => q,
None => return Err(ErrorKind::RequestError("no query string".to_owned()))?,
};

let params = form_urlencoded::parse(query.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") {
for id in ids {
for id in id.split(',') {
if let Ok(x) = util::from_hex(String::from(id)) {
commitments.push(Commitment::from_vec(x));
}
}
let query = must_get_query!(req);
let params = QueryParams::from(query);
params.process_multival_param("id", |id| {
if let Ok(x) = util::from_hex(String::from(id)) {
commitments.push(Commitment::from_vec(x));
}
}
if let Some(heights) = params.get("start_height") {
for height in heights {
start_height = height
.parse()
.map_err(|_| ErrorKind::RequestError("invalid start_height".to_owned()))?;
}
}
if let Some(heights) = params.get("end_height") {
for height in heights {
end_height = height
.parse()
.map_err(|_| ErrorKind::RequestError("invalid end_height".to_owned()))?;
}
}
if let Some(_) = params.get("include_rp") {
include_rp = true;
}
});
let start_height = parse_param!(params, "start_height", 1);
let end_height = parse_param!(params, "end_height", 1);
let include_rp = params.get("include_rp").is_some();

debug!(
"outputs_block_batch: {}-{}, {:?}, {:?}",
Expand All @@ -223,11 +177,7 @@ impl OutputHandler {

impl Handler for OutputHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture {
let command = match req.uri().path().trim_right_matches('/').rsplit('/').next() {
Some(c) => c,
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
};
match command {
match right_path_element!(req) {
"byids" => result_to_response(self.outputs_by_ids(&req)),
"byheight" => result_to_response(self.outputs_block_batch(&req)),
_ => response(StatusCode::BAD_REQUEST, ""),
Expand Down
15 changes: 6 additions & 9 deletions api/src/handlers/peers_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ pub struct PeersConnectedHandler {

impl Handler for PeersConnectedHandler {
fn get(&self, _req: Request<Body>) -> ResponseFuture {
let mut peers: Vec<PeerInfoDisplay> = vec![];
for p in &w(&self.peers).connected_peers() {
let peer_info = p.info.clone();
peers.push(peer_info.into());
}
let peers: Vec<PeerInfoDisplay> = w(&self.peers)
.connected_peers()
.iter()
.map(|p| p.info.clone().into())
.collect();
json_response(&peers)
}
}
Expand All @@ -56,10 +56,7 @@ pub struct PeerHandler {

impl Handler for PeerHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture {
let command = match req.uri().path().trim_right_matches('/').rsplit('/').next() {
Some(c) => c,
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
};
let command = right_path_element!(req);
if let Ok(addr) = command.parse() {
match w(&self.peers).get_peer(addr) {
Ok(peer) => json_response(&peer),
Expand Down
26 changes: 9 additions & 17 deletions api/src/handlers/pool_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ use crate::types::*;
use crate::util;
use crate::util::RwLock;
use crate::web::*;
use failure::ResultExt;
use futures::future::ok;
use futures::Future;
use hyper::{Body, Request, StatusCode};
use std::collections::HashMap;
use std::sync::Weak;
use url::form_urlencoded;

/// Get basic information about the transaction pool.
/// GET /v1/pool
Expand Down Expand Up @@ -61,15 +60,7 @@ pub struct PoolPushHandler {

impl PoolPushHandler {
fn update_pool(&self, req: Request<Body>) -> Box<dyn Future<Item = (), Error = Error> + Send> {
let params = 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(),
};
let params = QueryParams::from(req.uri().query());

let fluff = params.get("fluff").is_some();
let pool_arc = w(&self.tx_pool).clone();
Expand Down Expand Up @@ -99,13 +90,14 @@ impl PoolPushHandler {

// Push to tx pool.
let mut tx_pool = pool_arc.write();
let header = tx_pool.blockchain.chain_head().unwrap();
tx_pool
let header = tx_pool
.blockchain
.chain_head()
.context(ErrorKind::Internal("Failed to get chain head".to_owned()))?;
let res = tx_pool
.add_to_pool(source, tx, !fluff, &header)
.map_err(|e| {
error!("update_pool: failed with error: {:?}", e);
ErrorKind::Internal(format!("Failed to update pool: {:?}", e)).into()
})
.context(ErrorKind::Internal("Failed to update pool".to_owned()))?;
Ok(res)
}),
)
}
Expand Down
59 changes: 6 additions & 53 deletions api/src/handlers/transactions_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ use crate::util::secp::pedersen::Commitment;
use crate::web::*;
use failure::ResultExt;
use hyper::{Body, Request, StatusCode};
use std::collections::HashMap;
use std::sync::Weak;
use url::form_urlencoded;

// Sum tree handler. Retrieve the roots:
// GET /v1/txhashset/roots
Expand Down Expand Up @@ -114,59 +112,14 @@ impl TxHashSetHandler {

impl Handler for TxHashSetHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture {
let mut start_index = 1;
let mut max = 100;
let mut id = "".to_owned();

// TODO: probably need to set a reasonable max limit here
let mut last_n = 10;
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") {
for num in nums {
if let Ok(n) = str::parse(num) {
last_n = n;
}
}
}
if let Some(start_indexes) = params.get("start_index") {
for si in start_indexes {
if let Ok(s) = str::parse(si) {
start_index = s;
}
}
}
if let Some(maxes) = params.get("max") {
for ma in maxes {
if let Ok(m) = str::parse(ma) {
max = m;
}
}
}
if let Some(ids) = params.get("id") {
if !ids.is_empty() {
id = ids.last().unwrap().to_owned();
}
}
}
let command = match req
.uri()
.path()
.trim_right()
.trim_right_matches("/")
.rsplit("/")
.next()
{
Some(c) => c,
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
};
let params = QueryParams::from(req.uri().query());
let last_n = parse_param_no_err!(params, "n", 10);
let start_index = parse_param_no_err!(params, "start_index", 1);
let max = parse_param_no_err!(params, "max", 100);
let id = parse_param_no_err!(params, "id", "".to_owned());

match command {
match right_path_element!(req) {
"roots" => json_response_pretty(&self.get_roots()),
"lastoutputs" => json_response_pretty(&self.get_last_n_output(last_n)),
"lastrangeproofs" => json_response_pretty(&self.get_last_n_rangeproof(last_n)),
Expand Down
15 changes: 9 additions & 6 deletions api/src/handlers/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,15 @@ pub fn get_output(
OutputIdentifier::new(OutputFeatures::Coinbase, &commit),
];

for x in outputs.iter() {
if let Ok(_) = w(chain).is_unspent(&x) {
let block_height = w(chain).get_header_for_output(&x).unwrap().height;
let output_pos = w(chain).get_output_pos(&x.commit).unwrap_or(0);
return Ok((Output::new(&commit, block_height, output_pos), x.clone()));
}
for x in outputs.iter().filter(|x| w(chain).is_unspent(x).is_ok()) {
let block_height = w(chain)
.get_header_for_output(&x)
.context(ErrorKind::Internal(
"Can't get header for output".to_owned(),
))?
.height;
let output_pos = w(chain).get_output_pos(&x.commit).unwrap_or(0);
return Ok((Output::new(&commit, block_height, output_pos), x.clone()));
}
Err(ErrorKind::NotFound)?
}
3 changes: 2 additions & 1 deletion api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ extern crate serde_derive;
#[macro_use]
extern crate log;

#[macro_use]
mod web;
pub mod auth;
pub mod client;
mod handlers;
mod rest;
mod router;
mod types;
mod web;

pub use crate::auth::BasicAuthMiddleware;
pub use crate::handlers::start_rest_apis;
Expand Down
Loading

0 comments on commit ac6ed71

Please sign in to comment.