pub mod app;
pub mod config;
pub mod dto;
pub mod error;
pub mod pool;
pub mod port;
pub mod state;
pub mod wire_proxy;
pub use config::EdgeConfig;
pub use error::ApiError;
pub use pool::{BrainPool, BrainPoolConfig};
pub use port::{
BearerResolver, CredentialResolver, MeterEvent, MeteringSink, NoopMeter, Outcome,
ResolvedCredential, bearer_token,
};
pub use state::EdgeState;
pub use wire_proxy::{RateLimitConfig, WireProxyConfig};
pub use brain_db_sdk::wire::types::ActAs;
use std::time::Duration;
use axum::Router;
use axum::extract::State;
use axum::http::{HeaderName, StatusCode, header};
use axum::response::IntoResponse;
use axum::routing::get;
use tower_http::catch_panic::CatchPanicLayer;
use tower_http::limit::RequestBodyLimitLayer;
use tower_http::request_id::{MakeRequestUuid, PropagateRequestIdLayer, SetRequestIdLayer};
use tower_http::sensitive_headers::SetSensitiveRequestHeadersLayer;
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;
use tracing::{error, info};
const REQUEST_ID_HEADER: HeaderName = HeaderName::from_static("x-request-id");
fn panic_to_response(err: &Box<dyn std::any::Any + Send + 'static>) -> axum::response::Response {
let detail: &str = err.downcast_ref::<&str>().copied().unwrap_or_else(|| {
err.downcast_ref::<String>()
.map_or("non-string panic payload", String::as_str)
});
ApiError::internal(format!("handler panicked: {detail}")).into_response()
}
pub fn harden(router: Router, request_timeout: Duration, max_body_bytes: usize) -> Router {
router
.layer(CatchPanicLayer::custom(|e| panic_to_response(&e)))
.layer(TraceLayer::new_for_http())
.layer(SetSensitiveRequestHeadersLayer::new([
header::AUTHORIZATION,
HeaderName::from_static("x-api-key"),
]))
.layer(TimeoutLayer::with_status_code(
StatusCode::REQUEST_TIMEOUT,
request_timeout,
))
.layer(RequestBodyLimitLayer::new(max_body_bytes))
.layer(PropagateRequestIdLayer::new(REQUEST_ID_HEADER))
.layer(SetRequestIdLayer::new(REQUEST_ID_HEADER, MakeRequestUuid))
}
pub async fn run(config: EdgeConfig) -> Result<(), Box<dyn std::error::Error>> {
let listen_addr = config.listen_addr;
let request_timeout = Duration::from_secs(config.request_timeout_secs);
let max_body_bytes = config.max_body_bytes;
let wire_listen_addr = config.wire_listen_addr;
let brain_addr = config.brain_addr;
let wire_rate = RateLimitConfig {
capacity: config.wire_rate_capacity,
refill_per_sec: config.wire_rate_refill_per_sec,
};
let state = EdgeState::new(config);
if let Some(wire_addr) = wire_listen_addr {
let meter = state.meter();
let wcfg = WireProxyConfig {
wire_listen_addr: wire_addr,
brain_addr,
rate: wire_rate,
};
tokio::spawn(async move {
if let Err(e) = wire_proxy::serve(wcfg, meter).await {
error!(error = %e, "wire proxy listener exited");
}
});
}
{
let sweeper_state = state.clone();
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(60));
ticker.tick().await; loop {
ticker.tick().await;
sweeper_state.pool().sweep_idle();
}
});
}
let health = axum::Router::new()
.route("/health/live", get(|| async { StatusCode::OK }))
.route("/health/ready", get(readiness))
.with_state(state.clone());
let app = harden(
app::router(state).merge(health),
request_timeout,
max_body_bytes,
);
let listener = tokio::net::TcpListener::bind(listen_addr).await?;
info!(addr = %listen_addr, "listening");
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?;
info!("shutdown complete");
Ok(())
}
async fn readiness(State(state): State<EdgeState>) -> StatusCode {
if state.pool().probe(Duration::from_secs(2)).await {
StatusCode::OK
} else {
StatusCode::SERVICE_UNAVAILABLE
}
}
async fn shutdown_signal() {
let ctrl_c = async {
let _ = tokio::signal::ctrl_c().await;
};
#[cfg(unix)]
let terminate = async {
match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
Ok(mut sig) => {
sig.recv().await;
}
Err(_) => std::future::pending::<()>().await,
}
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => info!("SIGINT received, shutting down"),
() = terminate => info!("SIGTERM received, shutting down"),
}
}