lighthouse/beacon_node/rest_api/src/lib.rs
Luke Anderson 0052ea711e First RESTful HTTP API (#399)
* Added generated code for REST API.
 - Created a new crate rest_api, which will adapt the openapi generated code to Lighthouse
 - Committed automatically generated code from openapi-generator-cli (via docker). Should hopfully not have to modify this at all, and do all changes in the rest_api crate.

* Removed openapi generated code, because it was the rust client, not the rust server.

* Added the correct rust-server code, automatically generated from openapi.

* Added generated code for REST API.
 - Created a new crate rest_api, which will adapt the openapi generated code to Lighthouse
 - Committed automatically generated code from openapi-generator-cli (via docker). Should hopfully not have to modify this at all, and do all changes in the rest_api crate.

* Removed openapi generated code, because it was the rust client, not the rust server.

* Added the correct rust-server code, automatically generated from openapi.

* Included REST API in configuratuion.
 - Started adding the rest_api into the beacon node's dependencies.
 - Set up configuration file for rest_api and integrated into main client config
 - Added CLI flags for REST API.

* Futher work on REST API.
 - Adding the dependencies to rest_api crate
 - Created a skeleton BeaconNodeService, which will handle /node requests.
 - Started the rest_api server definition, with the high level request handling logic.

* Added generated code for REST API.
 - Created a new crate rest_api, which will adapt the openapi generated code to Lighthouse
 - Committed automatically generated code from openapi-generator-cli (via docker). Should hopfully not have to modify this at all, and do all changes in the rest_api crate.

* Removed openapi generated code, because it was the rust client, not the rust server.

* Added the correct rust-server code, automatically generated from openapi.

* Included REST API in configuratuion.
 - Started adding the rest_api into the beacon node's dependencies.
 - Set up configuration file for rest_api and integrated into main client config
 - Added CLI flags for REST API.

* Futher work on REST API.
 - Adding the dependencies to rest_api crate
 - Created a skeleton BeaconNodeService, which will handle /node requests.
 - Started the rest_api server definition, with the high level request handling logic.

* WIP: Restructured REST API to use hyper_router and separate services.

* WIP: Fixing rust for REST API

* WIP: Fixed up many bugs in trying to get router to compile.

* WIP: Got the beacon_node to compile with the REST changes

* Basic API works!
 - Changed CLI flags from rest-api* to api*
 - Fixed port cli flag
 - Tested, works over HTTP

* WIP: Moved things around so that we can get state inside the handlers.

* WIP: Significant API updates.
 - Started writing a macro for getting the handler functions.
 - Added the BeaconChain into the type map, gives stateful access to the beacon state.
 - Created new generic error types (haven't figured out yet), to reduce code duplication.
 - Moved common stuff into lib.rs

* WIP: Factored macros, defined API result and error.
 - did more logging when creating HTTP responses
 - Tried moving stuff into macros, but can't get macros in macros to compile.
 - Pulled out a lot of placeholder code.

* Fixed macros so that things compile.

* Cleaned up code.
 - Removed unused imports
 - Removed comments
 - Addressed all compiler warnings.
 - Ran cargo fmt.

* Removed auto-generated OpenAPI code.

* Addressed Paul's suggestions.
 - Fixed spelling mistake
 - Moved the simple macros into functions, since it doesn't make sense for them to be macros.
 - Removed redundant code & inclusions.

* Removed redundant validate_request function.

* Included graceful shutdown in Hyper server.

* Fixing the dropped exit_signal, which prevented the API from starting.

* Wrapped the exit signal, to get an API shutdown log line.
2019-07-31 18:29:41 +10:00

133 lines
4.0 KiB
Rust

extern crate futures;
extern crate hyper;
#[macro_use]
mod macros;
mod beacon_node;
pub mod config;
use beacon_chain::{BeaconChain, BeaconChainTypes};
pub use config::Config as APIConfig;
use slog::{info, o, warn};
use std::sync::Arc;
use tokio::runtime::TaskExecutor;
use crate::beacon_node::BeaconNodeServiceInstance;
use hyper::rt::Future;
use hyper::service::{service_fn, Service};
use hyper::{Body, Request, Response, Server, StatusCode};
use hyper_router::{RouterBuilder, RouterService};
pub enum APIError {
MethodNotAllowed { desc: String },
ServerError { desc: String },
NotImplemented { desc: String },
}
pub type APIResult = Result<Response<Body>, APIError>;
impl Into<Response<Body>> for APIError {
fn into(self) -> Response<Body> {
let status_code: (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),
};
Response::builder()
.status(status_code.0)
.body(Body::from(status_code.1))
.expect("Response should always be created.")
}
}
pub trait APIService {
fn add_routes(&mut self, router_builder: RouterBuilder) -> Result<RouterBuilder, hyper::Error>;
}
pub fn start_server<T: BeaconChainTypes + Clone + 'static>(
config: &APIConfig,
executor: &TaskExecutor,
beacon_chain: Arc<BeaconChain<T>>,
log: &slog::Logger,
) -> Result<exit_future::Signal, hyper::Error> {
let log = log.new(o!("Service" => "API"));
// build a channel to kill the HTTP server
let (exit_signal, exit) = exit_future::signal();
let exit_log = log.clone();
let server_exit = exit.and_then(move |_| {
info!(exit_log, "API service shutdown");
Ok(())
});
// 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 = beacon_chain.clone();
// Create the service closure
let service = move || {
//TODO: This router must be moved out of this closure, so it isn't rebuilt for every connection.
let mut router = build_router_service::<T>();
// Clone our stateful objects, for use in handler closure
let service_log = server_log.clone();
let service_bc = server_bc.clone();
// Create a simple handler for the router, inject our stateful objects into the request.
service_fn(move |mut req| {
req.extensions_mut()
.insert::<slog::Logger>(service_log.clone());
req.extensions_mut()
.insert::<Arc<BeaconChain<T>>>(service_bc.clone());
router.call(req)
})
};
let server = Server::bind(&bind_addr)
.serve(service)
.with_graceful_shutdown(server_exit)
.map_err(move |e| {
warn!(
log,
"API failed to start, Unable to bind"; "address" => format!("{:?}", e)
)
});
executor.spawn(server);
Ok(exit_signal)
}
fn build_router_service<T: BeaconChainTypes + 'static>() -> RouterService {
let mut router_builder = RouterBuilder::new();
let mut bn_service: BeaconNodeServiceInstance<T> = BeaconNodeServiceInstance {
marker: std::marker::PhantomData,
};
router_builder = bn_service
.add_routes(router_builder)
.expect("The routes should always be made.");
RouterService::new(router_builder.build())
}
fn path_from_request(req: &Request<Body>) -> String {
req.uri()
.path_and_query()
.as_ref()
.map(|pq| String::from(pq.as_str()))
.unwrap_or(String::new())
}
fn success_response(body: Body) -> Response<Body> {
Response::builder()
.status(StatusCode::OK)
.body(body)
.expect("We should always be able to make response from the success body.")
}