systemprompt_api/services/server/
builder.rs1use anyhow::Result;
14use axum::Router;
15use axum::extract::DefaultBodyLimit;
16use systemprompt_runtime::AppContext;
17use systemprompt_traits::{StartupEventExt, StartupEventSender};
18
19use super::routes::configure_routes;
20use crate::services::middleware::{
21 AnalyticsMiddleware, CorsMiddleware, LoadShed, PublicContextMiddleware, SessionMiddleware,
22 inject_security_headers, inject_served_by, inject_trace_header, remove_trailing_slash,
23};
24
25pub use super::discovery::*;
26pub use super::health::handle_health;
27
28pub fn setup_api_server(ctx: &AppContext, events: Option<&StartupEventSender>) -> Result<Router> {
29 let profile = systemprompt_config::ProfileBootstrap::get()?;
30 let services_cache = systemprompt_loader::bundle::cache_root(profile);
31 systemprompt_config::ensure_state_dirs_writable(
32 &ctx.app_paths()
33 .writable_roots(ctx.config().role, &services_cache),
34 )?;
35
36 let rate_config = &ctx.config().rate_limits;
37
38 if rate_config.disabled
39 && let Some(tx) = events
40 {
41 tx.warning("Rate limiting disabled - development mode only");
42 }
43
44 register_artifact_scanner(ctx);
45 let router = configure_routes(ctx, events)?;
46 apply_global_middleware(router, ctx)
47}
48
49fn register_artifact_scanner(ctx: &AppContext) {
53 let resolver = systemprompt_gateway::PolicyResolver::from_repository(
54 ctx.ai_repositories().gateway_policies.clone(),
55 ctx.cache_epochs().clone(),
56 );
57 ctx.artifact_ingest().register_scanner(std::sync::Arc::new(
58 systemprompt_gateway::GatewayArtifactScanner::new(resolver),
59 ));
60}
61
62fn apply_global_middleware(router: Router, ctx: &AppContext) -> Result<Router> {
63 let mut router = router;
64
65 if let Some(max) = ctx.config().max_in_flight {
66 let shed = std::sync::Arc::new(LoadShed::new(max));
67 router = router.layer(axum::middleware::from_fn_with_state(
68 shed,
69 crate::services::middleware::load_shed::shed,
70 ));
71 }
72
73 router = router.layer(DefaultBodyLimit::max(2 * 1024 * 1024));
74
75 let analytics_middleware = AnalyticsMiddleware::new(ctx);
76 router = router.layer(axum::middleware::from_fn({
77 let middleware = analytics_middleware;
78 move |req, next| {
79 let middleware = middleware.clone();
80 async move { middleware.track_request(req, next).await }
81 }
82 }));
83
84 let global_context_middleware = PublicContextMiddleware::new();
85 router = router.layer(axum::middleware::from_fn({
86 let middleware = global_context_middleware;
87 move |req, next| async move { middleware.seed(req, next).await }
88 }));
89
90 let session_middleware = SessionMiddleware::new(ctx);
91 router = router.layer(axum::middleware::from_fn({
92 let middleware = session_middleware;
93 move |req, next| {
94 let middleware = middleware.clone();
95 async move { middleware.handle(req, next).await }
96 }
97 }));
98
99 let cors = CorsMiddleware::build_layer(ctx.config())?;
100 router = router.layer(cors);
101
102 router = router.layer(axum::middleware::from_fn(remove_trailing_slash));
103
104 router = router.layer(axum::middleware::from_fn(inject_trace_header));
105
106 router = router.layer(axum::middleware::from_fn(inject_served_by));
107
108 router = router.layer(axum::middleware::from_fn(
109 crate::services::middleware::strip_caller_purpose,
110 ));
111
112 if ctx.config().content_negotiation.enabled {
113 router = router.layer(axum::middleware::from_fn(
114 crate::services::middleware::content_negotiation_middleware,
115 ));
116 }
117
118 if ctx.config().security_headers.enabled {
119 let security_config = ctx.config().security_headers.clone();
120 router = router.layer(axum::middleware::from_fn(move |req, next| {
121 let config = security_config.clone();
122 inject_security_headers(config, req, next)
123 }));
124 }
125
126 Ok(router)
127}