forked from sigp/lighthouse
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Included rest api - it is a mirror of what is on "interop" branch ups…
…tream.
- Loading branch information
1 parent
0c9a785
commit 488e0c4
Showing
10 changed files
with
493 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}; | ||
} |
Oops, something went wrong.