Skip to content

Commit

Permalink
Add API config (#117)
Browse files Browse the repository at this point in the history
* Add API config
  • Loading branch information
DaughterOfMars committed May 4, 2022
1 parent 1390e9b commit a42d4ea
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 28 deletions.
4 changes: 4 additions & 0 deletions bin/inx-chronicle/config.template.toml
Expand Up @@ -6,3 +6,7 @@ password = "root"
[inx]
connect_url = "http://localhost:9029"
connection_retry_interval = "5s"

[api]
port = 9092
allow_origins = ["0.0.0.0"]
54 changes: 54 additions & 0 deletions bin/inx-chronicle/src/api/config.rs
@@ -0,0 +1,54 @@
// Copyright 2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use derive_more::From;
use serde::{Deserialize, Serialize};
use tower_http::cors::AllowOrigin;

use super::error::ConfigError;

/// API configuration
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ApiConfig {
pub port: u16,
pub allow_origins: Option<SingleOrMultiple<String>>,
}

impl Default for ApiConfig {
fn default() -> Self {
Self {
port: 9092,
allow_origins: Some(String::from("*").into()),
}
}
}

/// Convenience type that allows specifying either a single value or a list of values
/// in the configuration file.
///
/// ## Examples
/// ```toml
/// [api]
/// allow_origins = "origin"
/// allow_origins = ["origin1", "origin2"]
/// ```
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, From)]
#[serde(untagged)]
pub enum SingleOrMultiple<T> {
Single(T),
Multiple(Vec<T>),
}

impl TryFrom<SingleOrMultiple<String>> for AllowOrigin {
type Error = ConfigError;

fn try_from(value: SingleOrMultiple<String>) -> Result<Self, Self::Error> {
Ok(match value {
SingleOrMultiple::Single(value) => AllowOrigin::exact(value.parse()?),
SingleOrMultiple::Multiple(value) => {
AllowOrigin::list(value.into_iter().map(|v| v.parse()).collect::<Result<Vec<_>, _>>()?)
}
})
}
}
9 changes: 8 additions & 1 deletion bin/inx-chronicle/src/api/error.rs
Expand Up @@ -5,7 +5,7 @@ use std::str::ParseBoolError;

use axum::{extract::rejection::QueryRejection, response::IntoResponse};
use chronicle::db::{bson::DocError, model::inclusion_state::UnexpectedLedgerInclusionState};
use hyper::StatusCode;
use hyper::{header::InvalidHeaderValue, StatusCode};
use mongodb::bson::document::ValueAccessError;
use serde::Serialize;
use thiserror::Error;
Expand All @@ -18,6 +18,8 @@ pub enum InternalApiError {
#[error(transparent)]
Doc(#[from] DocError),
#[error(transparent)]
Config(#[from] ConfigError),
#[error(transparent)]
Hyper(#[from] hyper::Error),
#[error(transparent)]
MongoDb(#[from] mongodb::error::Error),
Expand Down Expand Up @@ -104,6 +106,11 @@ pub enum ParseError {
TimeRange(#[from] time::error::ComponentRange),
}

#[derive(Error, Debug)]
pub enum ConfigError {
#[error(transparent)]
InvalidHeader(#[from] InvalidHeaderValue),
}
#[derive(Clone, Debug, Serialize)]
pub struct ErrorBody {
#[serde(skip_serializing)]
Expand Down
38 changes: 33 additions & 5 deletions bin/inx-chronicle/src/api/mod.rs
Expand Up @@ -12,19 +12,27 @@ pub(crate) mod stardust;
mod error;
#[macro_use]
mod responses;
mod config;
mod routes;

use async_trait::async_trait;
use axum::Server;
use axum::{Extension, Server};
use chronicle::{
db::MongoDb,
runtime::actor::{context::ActorContext, Actor},
};
pub use config::ApiConfig;
pub use error::ApiError;
use hyper::Method;
pub(crate) use responses::impl_success_response;
pub use responses::SuccessBody;
use routes::routes;
use tokio::{sync::oneshot, task::JoinHandle};
use tower_http::{
catch_panic::CatchPanicLayer,
cors::{AllowOrigin, Any, CorsLayer},
trace::TraceLayer,
};

/// The result of a request to the api
pub type ApiResult<T> = Result<T, ApiError>;
Expand All @@ -33,14 +41,16 @@ pub type ApiResult<T> = Result<T, ApiError>;
#[derive(Debug)]
pub struct ApiWorker {
db: MongoDb,
config: ApiConfig,
server_handle: Option<(JoinHandle<hyper::Result<()>>, oneshot::Sender<()>)>,
}

impl ApiWorker {
/// Create a new Chronicle API actor from a mongo connection.
pub fn new(db: MongoDb) -> Self {
pub fn new(db: MongoDb, config: ApiConfig) -> Self {
Self {
db,
config,
server_handle: None,
}
}
Expand All @@ -55,11 +65,29 @@ impl Actor for ApiWorker {
async fn init(&mut self, cx: &mut ActorContext<Self>) -> Result<Self::State, Self::Error> {
let (sender, receiver) = oneshot::channel();
log::info!("Starting API server");
let db = self.db.clone();
let api_handle = cx.handle().clone();
let port = self.config.port;
let routes = routes()
.layer(Extension(self.db.clone()))
.layer(CatchPanicLayer::new())
.layer(TraceLayer::new_for_http())
.layer(
CorsLayer::new()
.allow_origin(
self.config
.allow_origins
.clone()
.map(AllowOrigin::try_from)
.transpose()?
.unwrap_or_else(AllowOrigin::any),
)
.allow_methods(vec![Method::GET, Method::OPTIONS])
.allow_headers(Any)
.allow_credentials(false),
);
let join_handle = tokio::spawn(async move {
let res = Server::bind(&([0, 0, 0, 0], 9092).into())
.serve(routes(db).into_make_service())
let res = Server::bind(&([0, 0, 0, 0], port).into())
.serve(routes.into_make_service())
.with_graceful_shutdown(shutdown_signal(receiver))
.await;
// If the Axum server shuts down, we should also shutdown the API actor
Expand Down
22 changes: 2 additions & 20 deletions bin/inx-chronicle/src/api/routes.rs
Expand Up @@ -7,16 +7,10 @@ use chronicle::db::{
MongoDb,
};
use futures::TryStreamExt;
use hyper::Method;
use tower_http::{
catch_panic::CatchPanicLayer,
cors::{Any, CorsLayer},
trace::TraceLayer,
};

use super::{error::ApiError, responses::*, ApiResult};

pub fn routes(db: MongoDb) -> Router {
pub fn routes() -> Router {
#[allow(unused_mut)]
let mut router = Router::new().route("/info", get(info)).route("/sync", get(sync));

Expand All @@ -25,19 +19,7 @@ pub fn routes(db: MongoDb) -> Router {
router = router.merge(super::stardust::routes())
}

Router::new()
.nest("/api", router)
.fallback(not_found.into_service())
.layer(Extension(db))
.layer(CatchPanicLayer::new())
.layer(TraceLayer::new_for_http())
.layer(
CorsLayer::new()
.allow_origin(Any)
.allow_methods(vec![Method::GET, Method::OPTIONS])
.allow_headers(Any)
.allow_credentials(false),
)
Router::new().nest("/api", router).fallback(not_found.into_service())
}

async fn info() -> InfoResponse {
Expand Down
4 changes: 4 additions & 0 deletions bin/inx-chronicle/src/config.rs
Expand Up @@ -7,6 +7,8 @@ use chronicle::db::MongoDbConfig;
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[cfg(feature = "api")]
use crate::api::ApiConfig;
#[cfg(feature = "inx")]
use crate::inx::InxConfig;

Expand All @@ -24,6 +26,8 @@ pub struct ChronicleConfig {
pub mongodb: MongoDbConfig,
#[cfg(feature = "inx")]
pub inx: InxConfig,
#[cfg(feature = "api")]
pub api: ApiConfig,
}

impl ChronicleConfig {
Expand Down
4 changes: 2 additions & 2 deletions bin/inx-chronicle/src/main.rs
Expand Up @@ -88,7 +88,7 @@ impl Actor for Launcher {
cx.spawn_child(InxWorker::new(config.inx.clone())).await;

#[cfg(feature = "api")]
cx.spawn_child(ApiWorker::new(db)).await;
cx.spawn_child(ApiWorker::new(db, config.api.clone())).await;
Ok(config)
}
}
Expand Down Expand Up @@ -235,7 +235,7 @@ impl HandleEvent<Report<ApiWorker>> for Launcher {
Report::Error(e) => match e.error {
ActorError::Result(_) => {
let db = MongoDb::connect(&config.mongodb).await?;
cx.spawn_child(ApiWorker::new(db)).await;
cx.spawn_child(ApiWorker::new(db, config.api.clone())).await;
}
ActorError::Panic | ActorError::Aborted => {
cx.shutdown();
Expand Down

0 comments on commit a42d4ea

Please sign in to comment.