pub mod embedded_assets;
pub mod error;
pub mod handlers;
pub mod intercept_handlers;
#[cfg(feature = "enterprise")]
pub mod middleware;
#[cfg(any(feature = "grpc", feature = "scripting", feature = "plugins"))]
pub mod phase3_handlers;
#[cfg(feature = "enterprise")]
pub mod phase4_handlers;
pub mod routes;
pub mod validation;
pub mod ws;
use axum::Router;
#[cfg(feature = "enterprise")]
use madhyamas_core::enterprise::AuthManager;
#[cfg(feature = "grpc")]
use madhyamas_core::GrpcManager;
#[cfg(feature = "plugins")]
use madhyamas_core::PluginManager;
#[cfg(feature = "scripting")]
use madhyamas_core::ScriptRuntime;
use madhyamas_core::{
BreakpointManager, CertificateManager, InterceptStore, MockManager, ProxyConfig, ReplayManager,
RewriteManager, SessionManager, ThrottleManager, TrafficStore, WsManager,
};
use parking_lot::RwLock;
use std::sync::Arc;
use tower_http::cors::{AllowOrigin, Any, CorsLayer};
use tower_http::limit::RequestBodyLimitLayer;
use tower_http::set_header::SetResponseHeaderLayer;
use tower_http::trace::TraceLayer;
fn is_safe_origin(value: &axum::http::HeaderValue) -> bool {
let s = match value.to_str() {
Ok(s) => s,
Err(_) => return false,
};
if let Ok(url) = url::Url::parse(s) {
match url.host() {
Some(url::Host::Ipv4(ip)) => {
ip.is_loopback() || ip.is_private() || ip.is_link_local() || ip.is_unspecified()
}
Some(url::Host::Ipv6(ip)) => {
ip.is_loopback() || ip.is_unspecified() || ip.is_unicast_link_local()
}
Some(url::Host::Domain(d)) => d == "localhost" || d.ends_with(".localhost"),
None => false,
}
} else {
false
}
}
#[derive(Clone)]
pub struct AppState {
pub traffic_store: Arc<TrafficStore>,
pub cert_manager: Option<Arc<CertificateManager>>,
pub breakpoint_manager: Arc<BreakpointManager>,
pub mock_manager: Arc<MockManager>,
pub rewrite_manager: Arc<RewriteManager>,
pub throttle_manager: Arc<ThrottleManager>,
pub replay_manager: Arc<ReplayManager>,
#[cfg(feature = "grpc")]
pub grpc_manager: Arc<GrpcManager>,
#[cfg(feature = "scripting")]
pub script_runtime: Arc<ScriptRuntime>,
#[cfg(feature = "plugins")]
pub plugin_manager: Arc<PluginManager>,
pub ws_manager: Arc<WsManager>,
pub session_manager: Arc<SessionManager>,
pub intercept_store: Option<Arc<InterceptStore>>,
pub proxy_config: Option<Arc<RwLock<ProxyConfig>>>,
#[cfg(feature = "enterprise")]
pub auth_service: Option<Arc<AuthManager>>,
}
impl AppState {
pub fn new(traffic_store: Arc<TrafficStore>) -> Self {
let session_manager = Arc::new(SessionManager::new(traffic_store.clone()));
Self {
traffic_store,
cert_manager: None,
breakpoint_manager: Arc::new(BreakpointManager::default()),
mock_manager: Arc::new(MockManager::default()),
rewrite_manager: Arc::new(RewriteManager::default()),
throttle_manager: Arc::new(ThrottleManager::default()),
replay_manager: Arc::new(ReplayManager::default()),
#[cfg(feature = "grpc")]
grpc_manager: Arc::new(GrpcManager::default()),
#[cfg(feature = "scripting")]
script_runtime: Arc::new(ScriptRuntime::default()),
#[cfg(feature = "plugins")]
plugin_manager: Arc::new(PluginManager::default()),
ws_manager: Arc::new(WsManager::new()),
session_manager,
intercept_store: None,
proxy_config: None,
#[cfg(feature = "enterprise")]
auth_service: None,
}
}
pub fn with_cert_manager(mut self, cert_manager: Arc<CertificateManager>) -> Self {
self.cert_manager = Some(cert_manager);
self
}
pub fn with_breakpoint_manager(mut self, manager: Arc<BreakpointManager>) -> Self {
self.breakpoint_manager = manager;
self
}
pub fn with_mock_manager(mut self, manager: Arc<MockManager>) -> Self {
self.mock_manager = manager;
self
}
pub fn with_rewrite_manager(mut self, manager: Arc<RewriteManager>) -> Self {
self.rewrite_manager = manager;
self
}
pub fn with_throttle_manager(mut self, manager: Arc<ThrottleManager>) -> Self {
self.throttle_manager = manager;
self
}
pub fn with_replay_manager(mut self, manager: Arc<ReplayManager>) -> Self {
self.replay_manager = manager;
self
}
#[cfg(feature = "grpc")]
pub fn with_grpc_manager(mut self, manager: Arc<GrpcManager>) -> Self {
self.grpc_manager = manager;
self
}
#[cfg(feature = "scripting")]
pub fn with_script_runtime(mut self, runtime: Arc<ScriptRuntime>) -> Self {
self.script_runtime = runtime;
self
}
#[cfg(feature = "plugins")]
pub fn with_plugin_manager(mut self, manager: Arc<PluginManager>) -> Self {
self.plugin_manager = manager;
self
}
pub fn with_ws_manager(mut self, manager: Arc<WsManager>) -> Self {
self.ws_manager = manager;
self
}
pub fn with_session_manager(mut self, manager: Arc<SessionManager>) -> Self {
self.session_manager = manager;
self
}
pub fn with_intercept_store(mut self, store: Arc<InterceptStore>) -> Self {
self.intercept_store = Some(store);
self
}
pub fn with_proxy_config(mut self, config: Arc<RwLock<ProxyConfig>>) -> Self {
self.proxy_config = Some(config);
self
}
#[cfg(feature = "enterprise")]
pub fn with_auth_service(mut self, auth_service: Arc<AuthManager>) -> Self {
self.auth_service = Some(auth_service);
self
}
}
#[derive(Clone, Debug)]
pub struct RateLimitConfig {
pub enabled: bool,
pub requests_per_second: u32,
pub burst_size: u32,
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
enabled: false,
requests_per_second: 600,
burst_size: 1000,
}
}
}
impl RateLimitConfig {
pub fn disabled() -> Self {
Self {
enabled: false,
..Default::default()
}
}
pub fn enabled(requests_per_second: u32, burst_size: u32) -> Self {
Self {
enabled: true,
requests_per_second,
burst_size,
}
}
}
pub fn create_router(state: AppState, rate_limit: RateLimitConfig) -> Router<()> {
let state = Arc::new(state);
let mut router = Router::new()
.route("/health", axum::routing::get(|| async { "OK" }))
.nest("/api", routes::create_routes())
.fallback(embedded_assets::embedded_fallback)
.layer(
CorsLayer::new()
.allow_origin(AllowOrigin::predicate(|origin, _| is_safe_origin(origin)))
.allow_methods(Any)
.allow_headers(Any),
)
.layer(SetResponseHeaderLayer::if_not_present(
axum::http::header::X_FRAME_OPTIONS,
axum::http::HeaderValue::from_static("DENY"),
))
.layer(SetResponseHeaderLayer::if_not_present(
axum::http::header::X_CONTENT_TYPE_OPTIONS,
axum::http::HeaderValue::from_static("nosniff"),
))
.layer(SetResponseHeaderLayer::if_not_present(
axum::http::header::REFERRER_POLICY,
axum::http::HeaderValue::from_static("strict-origin-when-cross-origin"),
))
.layer(RequestBodyLimitLayer::new(10 * 1024 * 1024));
if rate_limit.enabled {
tracing::info!(
"API rate limiting enabled: {} req/s, burst {}",
rate_limit.requests_per_second,
rate_limit.burst_size
);
router = router.layer(tower_governor::GovernorLayer::new(
tower_governor::governor::GovernorConfigBuilder::default()
.const_per_second(rate_limit.requests_per_second as u64)
.burst_size(rate_limit.burst_size)
.finish()
.unwrap(),
));
} else {
tracing::debug!("API rate limiting disabled (default)");
}
router = router.layer(TraceLayer::new_for_http().make_span_with(
|request: &axum::http::Request<_>| {
tracing::info_span!(
"api",
method = %request.method(),
uri = %request.uri(),
)
},
));
router.with_state(state)
}