Skip to content

Commit

Permalink
Included rest api - it is a mirror of what is on "interop" branch ups…
Browse files Browse the repository at this point in the history
…tream.
  • Loading branch information
villanuevawill committed Sep 26, 2019
1 parent 0c9a785 commit 488e0c4
Show file tree
Hide file tree
Showing 10 changed files with 493 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ members = [
"shard_node",
"shard_node/shard_store",
"shard_node/shard_chain",
"shard_node/rest_api",
"tests/ef_tests",
"protos",
"validator_client",
Expand Down
27 changes: 27 additions & 0 deletions shard_node/rest_api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

[package]
name = "rest_api"
version = "0.1.0"
authors = ["Luke Anderson <[email protected]>", "Will Villanueva"]
edition = "2018"

[dependencies]
beacon_chain = { path = "../../beacon_node/beacon_chain" }
shard_chain = { path = "../shard_chain" }
shard_store = { path = "../shard_store" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "^1.0"
serde_yaml = "0.8"
slog = "^2.2.3"
slog-term = "^2.4.0"
slog-async = "^2.3.0"
eth2_ssz = { path = "../../eth2/utils/ssz" }
eth2_ssz_derive = { path = "../../eth2/utils/ssz_derive" }
types = { path = "../../eth2/types" }
http = "^0.1.17"
hyper = "0.12.34"
exit-future = "0.1.3"
tokio = "0.1.17"
url = "2.0"
lazy_static = "1.3.0"
futures = "0.1.25"
23 changes: 23 additions & 0 deletions shard_node/rest_api/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use serde::{Deserialize, Serialize};
use std::net::Ipv4Addr;

/// HTTP REST API Configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
/// Enable the REST API server.
pub enabled: bool,
/// The IPv4 address the REST API HTTP server will listen on.
pub listen_address: Ipv4Addr,
/// The port the REST API HTTP server will listen on.
pub port: u16,
}

impl Default for Config {
fn default() -> Self {
Config {
enabled: true,
listen_address: Ipv4Addr::new(127, 0, 0, 1),
port: 5052,
}
}
}
78 changes: 78 additions & 0 deletions shard_node/rest_api/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use crate::BoxFut;
use hyper::{Body, Response, StatusCode};
use std::error::Error as StdError;

#[derive(PartialEq, Debug, Clone)]
pub enum ApiError {
MethodNotAllowed(String),
ServerError(String),
NotImplemented(String),
BadRequest(String),
NotFound(String),
UnsupportedType(String),
ImATeapot(String),
}

pub type ApiResult = Result<Response<Body>, ApiError>;

impl ApiError {
pub fn status_code(self) -> (StatusCode, String) {
match self {
ApiError::MethodNotAllowed(desc) => (StatusCode::METHOD_NOT_ALLOWED, desc),
ApiError::ServerError(desc) => (StatusCode::INTERNAL_SERVER_ERROR, desc),
ApiError::NotImplemented(desc) => (StatusCode::NOT_IMPLEMENTED, desc),
ApiError::BadRequest(desc) => (StatusCode::BAD_REQUEST, desc),
ApiError::NotFound(desc) => (StatusCode::NOT_FOUND, desc),
ApiError::UnsupportedType(desc) => (StatusCode::UNSUPPORTED_MEDIA_TYPE, desc),
ApiError::ImATeapot(desc) => (StatusCode::IM_A_TEAPOT, desc),
}
}
}

impl Into<Response<Body>> for ApiError {
fn into(self) -> Response<Body> {
let status_code = self.status_code();
Response::builder()
.status(status_code.0)
.header("content-type", "text/plain; charset=utf-8")
.body(Body::from(status_code.1))
.expect("Response should always be created.")
}
}

impl Into<BoxFut> for ApiError {
fn into(self) -> BoxFut {
Box::new(futures::future::err(self))
}
}

impl From<shard_store::Error> for ApiError {
fn from(e: shard_store::Error) -> ApiError {
ApiError::ServerError(format!("Database error: {:?}", e))
}
}

impl From<types::ShardStateError> for ApiError {
fn from(e: types::ShardStateError) -> ApiError {
ApiError::ServerError(format!("BeaconState error: {:?}", e))
}
}

impl From<hyper::error::Error> for ApiError {
fn from(e: hyper::error::Error) -> ApiError {
ApiError::ServerError(format!("Networking error: {:?}", e))
}
}

impl StdError for ApiError {
fn cause(&self) -> Option<&dyn StdError> {
None
}
}

impl std::fmt::Display for ApiError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let status = self.clone().status_code();
write!(f, "{:?}: {:?}", status.0, status.1)
}
}
41 changes: 41 additions & 0 deletions shard_node/rest_api/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::{ApiError, ApiResult};
use beacon_chain::BeaconChainTypes;
use shard_chain::{ShardChain, ShardChainTypes};
use http::header;
use hyper::{Body, Request};
use std::sync::Arc;

/// Checks the provided request to ensure that the `content-type` header.
///
/// The content-type header should either be omitted, in which case JSON is assumed, or it should
/// explicity specify `application/json`. If anything else is provided, an error is returned.
pub fn check_content_type_for_json(req: &Request<Body>) -> Result<(), ApiError> {
match req.headers().get(header::CONTENT_TYPE) {
Some(h) if h == "application/json" => Ok(()),
Some(h) => Err(ApiError::BadRequest(format!(
"The provided content-type {:?} is not available, this endpoint only supports json.",
h
))),
_ => Ok(()),
}
}

pub fn get_shard_chain_from_request<T: ShardChainTypes + 'static, L: BeaconChainTypes + 'static> (
req: &Request<Body>,
) -> Result<(Arc<ShardChain<T, L>>), ApiError> {
// Get shard chain
let shard_chain = req
.extensions()
.get::<Arc<ShardChain<T, L>>>()
.ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".into()))?;

Ok(shard_chain.clone())
}

pub fn get_logger_from_request(req: &Request<Body>) -> slog::Logger {
let log = req
.extensions()
.get::<slog::Logger>()
.expect("Should always get the logger from the request, since we put it in there.");
log.to_owned()
}
122 changes: 122 additions & 0 deletions shard_node/rest_api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#[macro_use]
mod macros;
#[macro_use]
extern crate lazy_static;

mod config;
mod error;
mod helpers;
mod response_builder;
mod url_query;
mod shard;

use beacon_chain::BeaconChainTypes;
use shard_chain::{ShardChain, ShardChainTypes};
use error::{ApiError, ApiResult};
use futures::future::IntoFuture;
use hyper::rt::Future;
use hyper::service::Service;
use hyper::{Body, Method, Request, Response, Server};
use slog::{info, o, warn};
use std::sync::Arc;
use url_query::UrlQuery;
use tokio::runtime::TaskExecutor;

pub use config::Config as ApiConfig;

type BoxFut = Box<dyn Future<Item = Response<Body>, Error = ApiError> + Send>;

pub struct ApiService<T: ShardChainTypes + 'static, L: BeaconChainTypes + 'static> {
log: slog::Logger,
shard_chain: Arc<ShardChain<T, L>>,
}

fn into_boxfut<F: IntoFuture + 'static>(item: F) -> BoxFut
where
F: IntoFuture<Item = Response<Body>, Error = ApiError>,
F::Future: Send,
{
Box::new(item.into_future())
}

impl<T: ShardChainTypes, L: BeaconChainTypes> Service for ApiService<T, L> {
type ReqBody = Body;
type ResBody = Body;
type Error = ApiError;
type Future = BoxFut;

fn call(&mut self, mut req: Request<Body>) -> Self::Future {
req.extensions_mut()
.insert::<slog::Logger>(self.log.clone());
req.extensions_mut()
.insert::<Arc<ShardChain<T, L>>>(self.shard_chain.clone());

let path = req.uri().path().to_string();

let result = match (req.method(), path.as_ref()) {
(&Method::GET, "/hello") => into_boxfut(shard::hello(req)),
_ => Box::new(futures::future::err(ApiError::NotFound(
"Request path and/or method not found.".to_owned(),
))),
};

let response = match result.wait() {
// Return the `hyper::Response`.
Ok(response) => {
slog::debug!(self.log, "Request successful: {:?}", path);
response
}
// Map the `ApiError` into `hyper::Response`.
Err(e) => {
slog::debug!(self.log, "Request failure: {:?}", path);
e.into()
}
};

Box::new(futures::future::ok(response))
}
}

pub fn start_server<T: ShardChainTypes + 'static, L: BeaconChainTypes + 'static>(
config: &ApiConfig,
executor: &TaskExecutor,
shard_chain: Arc<ShardChain<T, L>>,
log: &slog::Logger,
) -> Result<(), hyper::Error> {
let log = log.new(o!("Service" => "Api"));

// Get the address to bind to
let bind_addr = (config.listen_address, config.port).into();

// Clone our stateful objects, for use in service closure.
let server_log = log.clone();
let server_bc = shard_chain.clone();

let service = move || -> futures::future::FutureResult<ApiService<T, L>, String> {
futures::future::ok(ApiService {
log: server_log.clone(),
shard_chain: server_bc.clone(),
})
};

let log_clone = log.clone();
let server = Server::bind(&bind_addr)
.serve(service)
.map_err(move |e| {
warn!(
log_clone,
"API failed to start, Unable to bind"; "address" => format!("{:?}", e)
)
});

info!(
log,
"REST API started";
"address" => format!("{}", config.listen_address),
"port" => config.port,
);

executor.spawn(server);

Ok(())
}
13 changes: 13 additions & 0 deletions shard_node/rest_api/src/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
macro_rules! try_future {
($expr:expr) => {
match $expr {
core::result::Result::Ok(val) => val,
core::result::Result::Err(err) => {
return Box::new(futures::future::err(std::convert::From::from(err)))
}
}
};
($expr:expr,) => {
$crate::try_future!($expr)
};
}
Loading

0 comments on commit 488e0c4

Please sign in to comment.