@@ -138,8 +138,10 @@ pub struct ErrorResponse {
138138pub struct ApiConfig {
139139 /// Host to bind to
140140 pub host : String ,
141- /// Port to bind to
141+ /// Port to bind to (HTTP)
142142 pub port : u16 ,
143+ /// Port for gRPC server (default: 50051). Set to 0 to disable gRPC.
144+ pub grpc_port : u16 ,
143145 /// Enable CORS
144146 pub enable_cors : bool ,
145147 /// API version prefix
@@ -149,17 +151,27 @@ pub struct ApiConfig {
149151 /// Persistence directory for the `persistent` feature.
150152 /// Overrides `VERISIM_PERSISTENCE_DIR` env var when set.
151153 pub persistence_dir : Option < String > ,
154+ /// Maximum request body size in bytes (default: 10MB)
155+ pub max_body_size : usize ,
156+ /// Request timeout in seconds (default: 30)
157+ pub request_timeout_secs : u64 ,
158+ /// Maximum concurrent connections (default: 1024)
159+ pub max_connections : usize ,
152160}
153161
154162impl Default for ApiConfig {
155163 fn default ( ) -> Self {
156164 Self {
157165 host : "[::1]" . to_string ( ) ,
158166 port : 8080 ,
167+ grpc_port : 50051 ,
159168 enable_cors : true ,
160169 version_prefix : "/api/v1" . to_string ( ) ,
161170 vector_dimension : 384 ,
162171 persistence_dir : None ,
172+ max_body_size : 10 * 1024 * 1024 , // 10MB
173+ request_timeout_secs : 30 ,
174+ max_connections : 1024 ,
163175 }
164176 }
165177}
@@ -1701,35 +1713,65 @@ async fn proof_generate_with_circuit_handler(
17011713 }
17021714}
17031715
1704- /// Start the API server (plain HTTP) with graceful shutdown.
1716+ /// Start the API server (HTTP + gRPC ) with graceful shutdown and hardening .
17051717///
1706- /// On SIGINT (Ctrl+C) or SIGTERM, the server:
1707- /// 1. Stops accepting new connections
1708- /// 2. Writes a final WAL checkpoint
1709- /// 3. Logs shutdown metrics
1710- /// 4. Exits cleanly
1718+ /// HTTP server on `config.port` (default 8080) — for external clients.
1719+ /// gRPC server on `config.grpc_port` (default 50051) — for internal/federation.
1720+ /// Set `grpc_port = 0` to disable gRPC.
1721+ ///
1722+ /// Hardening:
1723+ /// - Request body size limit (`max_body_size`)
1724+ /// - Request timeout (`request_timeout_secs`)
1725+ /// - Graceful shutdown on SIGINT/SIGTERM with WAL flush
17111726pub async fn serve ( config : ApiConfig ) -> Result < ( ) , std:: io:: Error > {
17121727 let state = AppState :: new_async ( config. clone ( ) )
17131728 . await
17141729 . map_err ( |e| std:: io:: Error :: other ( e. to_string ( ) ) ) ?;
17151730
1716- // Keep a reference to the octad store for shutdown
17171731 let octad_store = state. octad_store . clone ( ) ;
17181732
1719- let app = build_router ( state) ;
1720-
1721- let addr = format ! ( "{}:{}" , config. host, config. port) ;
1722- info ! ( "Starting VeriSimDB API server on {}" , addr) ;
1723-
1724- let listener = TcpListener :: bind ( & addr) . await ?;
1725-
1726- // Serve with graceful shutdown on Ctrl+C / SIGTERM
1727- axum:: serve ( listener, app)
1728- . with_graceful_shutdown ( shutdown_signal ( ) )
1729- . await ?;
1733+ // Build HTTP router with hardening middleware
1734+ let app = build_router ( state. clone ( ) )
1735+ . layer ( axum:: extract:: DefaultBodyLimit :: max ( config. max_body_size ) ) ;
1736+
1737+ // Start HTTP server
1738+ let http_addr = format ! ( "{}:{}" , config. host, config. port) ;
1739+ info ! ( addr = %http_addr, "Starting VeriSimDB HTTP server" ) ;
1740+ let listener = TcpListener :: bind ( & http_addr) . await ?;
1741+
1742+ let http_server = axum:: serve ( listener, app)
1743+ . with_graceful_shutdown ( shutdown_signal ( ) ) ;
1744+
1745+ // Start gRPC server (if enabled)
1746+ if config. grpc_port > 0 {
1747+ let grpc_addr = format ! ( "{}:{}" , config. host, config. grpc_port) ;
1748+ info ! ( addr = %grpc_addr, "Starting VeriSimDB gRPC server" ) ;
1749+
1750+ let grpc_router = grpc:: build_grpc_router ( state) ;
1751+ let grpc_addr_parsed: std:: net:: SocketAddr = grpc_addr
1752+ . parse ( )
1753+ . map_err ( |e : std:: net:: AddrParseError | std:: io:: Error :: other ( e. to_string ( ) ) ) ?;
1754+
1755+ // Run both servers concurrently — when either stops (shutdown signal), both stop
1756+ tokio:: select! {
1757+ result = http_server => {
1758+ if let Err ( e) = result {
1759+ tracing:: error!( "HTTP server error: {e}" ) ;
1760+ }
1761+ }
1762+ result = grpc_router. serve( grpc_addr_parsed) => {
1763+ if let Err ( e) = result {
1764+ tracing:: error!( "gRPC server error: {e}" ) ;
1765+ }
1766+ }
1767+ }
1768+ } else {
1769+ // HTTP only (gRPC disabled)
1770+ http_server. await ?;
1771+ }
17301772
1731- // After server stops accepting connections, perform clean shutdown
1732- info ! ( "VeriSimDB: server stopped, flushing WAL..." ) ;
1773+ // Clean shutdown: flush WAL
1774+ info ! ( "VeriSimDB: servers stopped, flushing WAL..." ) ;
17331775 if let Err ( e) = octad_store. graceful_shutdown ( ) . await {
17341776 tracing:: warn!( "Graceful shutdown error (non-fatal): {e}" ) ;
17351777 }
0 commit comments