@@ -7,6 +7,7 @@ use crate::error::Result;
77use crate :: middleware:: BodyLimitLayer ;
88use crate :: response:: IntoResponse ;
99use crate :: server:: Server ;
10+
1011impl RustApi {
1112 async fn prepare_for_serve ( & mut self , addr : & str ) {
1213 self . maybe_dump_openapi ( ) ;
@@ -23,8 +24,6 @@ impl RustApi {
2324 }
2425 }
2526
26- /// Returns `None` when hot-reload is disabled; otherwise whether a watcher was
27- /// already active before this call updated `RUSTAPI_HOT_RELOAD`.
2827 pub ( super ) fn print_hot_reload_banner ( & self , addr : & str ) -> Option < bool > {
2928 if !self . hot_reload {
3029 return None ;
@@ -34,7 +33,6 @@ impl RustApi {
3433 . map ( |v| v == "1" )
3534 . unwrap_or ( false ) ;
3635
37- // Set the env var so the CLI watcher can detect it
3836 std:: env:: set_var ( "RUSTAPI_HOT_RELOAD" , "1" ) ;
3937
4038 tracing:: info!( "Hot-reload mode enabled" ) ;
@@ -54,21 +52,20 @@ impl RustApi {
5452 hook ( ) . await ;
5553 }
5654 }
55+
5756 pub ( super ) fn apply_status_page ( & mut self ) {
5857 if let Some ( config) = & self . status_config {
5958 let monitor = std:: sync:: Arc :: new ( crate :: status:: StatusMonitor :: new ( ) ) ;
6059
61- // 1. Add middleware layer
6260 self . layers
6361 . push ( Box :: new ( crate :: status:: StatusLayer :: new ( monitor. clone ( ) ) ) ) ;
6462
65- // 2. Add status route
6663 use crate :: router:: MethodRouter ;
6764 use std:: collections:: HashMap ;
6865
6966 let monitor = monitor. clone ( ) ;
7067 let config = config. clone ( ) ;
71- let path = config. path . clone ( ) ; // Clone path before moving config
68+ let path = config. path . clone ( ) ;
7269
7370 let handler: crate :: handler:: BoxedHandler = std:: sync:: Arc :: new ( move |_| {
7471 let monitor = monitor. clone ( ) ;
@@ -84,7 +81,6 @@ impl RustApi {
8481 handlers. insert ( http:: Method :: GET , handler) ;
8582 let method_router = MethodRouter :: from_boxed ( handlers) ;
8683
87- // We need to take the router out to call route() which consumes it
8884 let router = std:: mem:: take ( & mut self . router ) ;
8985 self . router = router. route ( & path, method_router) ;
9086 }
@@ -104,9 +100,6 @@ impl RustApi {
104100 } ;
105101 config. normalize_paths ( ) ;
106102
107- // Build route inventory from currently registered routes. This snapshot
108- // intentionally happens before dashboard routes are mounted so the UI
109- // represents application endpoints rather than the dashboard itself.
110103 let mut inventory: Vec < RouteInventoryItem > = self
111104 . router
112105 . registered_routes ( )
@@ -141,11 +134,9 @@ impl RustApi {
141134 config. replay_api_path . clone ( ) ,
142135 ) ) ;
143136
144- // Insert metrics into router state using the public .state() API
145137 let router = std:: mem:: take ( & mut self . router ) ;
146138 self . router = router. state ( std:: sync:: Arc :: clone ( & metrics) ) ;
147139
148- // Register dashboard routes
149140 let prefix = config. path . trim_end_matches ( '/' ) . to_owned ( ) ;
150141
151142 fn not_found ( ) -> crate :: response:: Response {
@@ -157,7 +148,6 @@ impl RustApi {
157148 . unwrap ( )
158149 }
159150
160- // Route 1: GET /__rustapi/dashboard (the SPA page)
161151 {
162152 let metrics_c = std:: sync:: Arc :: clone ( & metrics) ;
163153 let config_c = config. clone ( ) ;
@@ -179,7 +169,6 @@ impl RustApi {
179169 self . router = router. route ( & prefix, MethodRouter :: from_boxed ( h) ) ;
180170 }
181171
182- // Route 2: GET /__rustapi/dashboard/*path (API sub-paths)
183172 {
184173 let metrics_c = std:: sync:: Arc :: clone ( & metrics) ;
185174 let config_c = config. clone ( ) ;
@@ -203,35 +192,20 @@ impl RustApi {
203192 }
204193 }
205194
206- /// Enable the embedded isometric system dashboard.
207- ///
208- /// Registers a self-contained admin surface at the configured path
209- /// (default: `/__rustapi/dashboard`).
210- ///
211- /// # Example
212- ///
213- /// ```rust,ignore
214- /// use rustapi_core::dashboard::DashboardConfig;
215- ///
216- /// RustApi::new()
217- /// .route("/api/users", get(list_users))
218- /// .dashboard(
219- /// DashboardConfig::new()
220- /// .admin_token("my-secret")
221- /// )
222- /// .run("127.0.0.1:8080")
223- /// .await
224- /// ```
225195 #[ cfg( feature = "dashboard" ) ]
226196 pub fn dashboard ( mut self , config : crate :: dashboard:: DashboardConfig ) -> Self {
227197 self . dashboard_config = Some ( config) ;
228198 self
229199 }
200+
230201 pub async fn run ( mut self , addr : & str ) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
231202 self . prepare_for_serve ( addr) . await ;
232203
204+ let shutdown_hooks = std:: mem:: take ( & mut self . lifecycle_hooks . on_shutdown ) ;
233205 let server = Server :: new ( self . router , self . layers , self . interceptors ) ;
234- server. run ( addr) . await
206+ let result = server. run ( addr) . await ;
207+ Self :: run_shutdown_hooks ( shutdown_hooks) . await ;
208+ result
235209 }
236210
237211 /// Run the server with graceful shutdown signal
@@ -252,19 +226,6 @@ impl RustApi {
252226 Ok ( ( ) )
253227 }
254228
255- /// Enable HTTP/3 support with TLS certificates
256- ///
257- /// HTTP/3 requires TLS certificates. For development, you can use
258- /// self-signed certificates with `run_http3_dev`.
259- ///
260- /// # Example
261- ///
262- /// ```rust,ignore
263- /// RustApi::new()
264- /// .route("/", get(hello))
265- /// .run_http3("0.0.0.0:443", "cert.pem", "key.pem")
266- /// .await
267- /// ```
268229 /// Run HTTP/3 with TLS certificates and a graceful shutdown signal.
269230 #[ cfg( feature = "http3" ) ]
270231 pub async fn run_http3_with_shutdown < F > (
@@ -296,26 +257,28 @@ impl RustApi {
296257
297258 #[ cfg( feature = "http3" ) ]
298259 pub async fn run_http3 (
299- self ,
260+ mut self ,
300261 config : crate :: http3:: Http3Config ,
301262 ) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
302- self . run_http3_with_shutdown ( config, std:: future:: pending ( ) )
303- . await
263+ use std:: sync:: Arc ;
264+
265+ let addr = config. socket_addr ( ) ;
266+ self . prepare_for_serve ( & addr) . await ;
267+
268+ let shutdown_hooks = std:: mem:: take ( & mut self . lifecycle_hooks . on_shutdown ) ;
269+ let server = crate :: http3:: Http3Server :: new (
270+ & config,
271+ Arc :: new ( self . router . clone ( ) ) ,
272+ Arc :: new ( self . layers . clone ( ) ) ,
273+ Arc :: new ( self . interceptors . clone ( ) ) ,
274+ )
275+ . await ?;
276+
277+ let result = server. run ( ) . await ;
278+ Self :: run_shutdown_hooks ( shutdown_hooks) . await ;
279+ result
304280 }
305281
306- /// Run HTTP/3 server with self-signed certificate (development only)
307- ///
308- /// This is useful for local development and testing.
309- /// **Do not use in production!**
310- ///
311- /// # Example
312- ///
313- /// ```rust,ignore
314- /// RustApi::new()
315- /// .route("/", get(hello))
316- /// .run_http3_dev("0.0.0.0:8443")
317- /// .await
318- /// ```
319282 /// Run HTTP/3 (self-signed) with a graceful shutdown signal.
320283 #[ cfg( feature = "http3-dev" ) ]
321284 pub async fn run_http3_dev_with_shutdown < F > (
@@ -346,43 +309,34 @@ impl RustApi {
346309
347310 #[ cfg( feature = "http3-dev" ) ]
348311 pub async fn run_http3_dev (
349- self ,
312+ mut self ,
350313 addr : & str ,
351314 ) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
352- self . run_http3_dev_with_shutdown ( addr, std:: future:: pending ( ) )
353- . await
315+ use std:: sync:: Arc ;
316+
317+ self . prepare_for_serve ( addr) . await ;
318+
319+ let shutdown_hooks = std:: mem:: take ( & mut self . lifecycle_hooks . on_shutdown ) ;
320+ let server = crate :: http3:: Http3Server :: new_with_self_signed (
321+ addr,
322+ Arc :: new ( self . router . clone ( ) ) ,
323+ Arc :: new ( self . layers . clone ( ) ) ,
324+ Arc :: new ( self . interceptors . clone ( ) ) ,
325+ )
326+ . await ?;
327+
328+ let result = server. run ( ) . await ;
329+ Self :: run_shutdown_hooks ( shutdown_hooks) . await ;
330+ result
354331 }
355332
356333 /// Configure HTTP/3 support for `run_http3` and `run_dual_stack`.
357- ///
358- /// # Example
359- ///
360- /// ```rust,ignore
361- /// RustApi::new()
362- /// .with_http3("cert.pem", "key.pem")
363- /// .run_dual_stack("127.0.0.1:8080")
364- /// .await
365- /// ```
366334 #[ cfg( feature = "http3" ) ]
367335 pub fn with_http3 ( mut self , cert_path : impl Into < String > , key_path : impl Into < String > ) -> Self {
368336 self . http3_config = Some ( crate :: http3:: Http3Config :: new ( cert_path, key_path) ) ;
369337 self
370338 }
371339
372- /// Run both HTTP/1.1 (TCP) and HTTP/3 (QUIC/UDP) simultaneously.
373- ///
374- /// The HTTP/3 listener is bound to the same host and port as `http_addr`
375- /// so clients can upgrade to either protocol on one endpoint.
376- ///
377- /// # Example
378- ///
379- /// ```rust,ignore
380- /// RustApi::new()
381- /// .route("/", get(hello))
382- /// .with_http3("cert.pem", "key.pem")
383- /// .run_dual_stack("0.0.0.0:8080")
384- /// .await
385- /// ```
386340 /// Run HTTP/1.1 and HTTP/3 together with a graceful shutdown signal.
387341 #[ cfg( feature = "http3" ) ]
388342 pub async fn run_dual_stack_with_shutdown < F > (
@@ -453,10 +407,45 @@ impl RustApi {
453407
454408 #[ cfg( feature = "http3" ) ]
455409 pub async fn run_dual_stack (
456- self ,
410+ mut self ,
457411 http_addr : & str ,
458412 ) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
459- self . run_dual_stack_with_shutdown ( http_addr, std:: future:: pending ( ) )
460- . await
413+ use std:: sync:: Arc ;
414+
415+ let mut config = self
416+ . http3_config
417+ . take ( )
418+ . ok_or ( "HTTP/3 config not set. Use .with_http3(...)" ) ?;
419+
420+ let http_socket: std:: net:: SocketAddr = http_addr. parse ( ) ?;
421+ config. bind_addr = if http_socket. ip ( ) . is_ipv6 ( ) {
422+ format ! ( "[{}]" , http_socket. ip( ) )
423+ } else {
424+ http_socket. ip ( ) . to_string ( )
425+ } ;
426+ config. port = http_socket. port ( ) ;
427+ let http_addr = http_socket. to_string ( ) ;
428+
429+ self . prepare_for_serve ( & http_addr) . await ;
430+
431+ let shutdown_hooks = std:: mem:: take ( & mut self . lifecycle_hooks . on_shutdown ) ;
432+ let router = Arc :: new ( self . router ) ;
433+ let layers = Arc :: new ( self . layers ) ;
434+ let interceptors = Arc :: new ( self . interceptors ) ;
435+
436+ let http1_server =
437+ Server :: from_shared ( router. clone ( ) , layers. clone ( ) , interceptors. clone ( ) ) ;
438+ let http3_server =
439+ crate :: http3:: Http3Server :: new ( & config, router, layers, interceptors) . await ?;
440+
441+ tracing:: info!(
442+ http1_addr = %http_addr,
443+ http3_addr = %config. socket_addr( ) ,
444+ "Starting dual-stack HTTP/1.1 + HTTP/3 servers"
445+ ) ;
446+
447+ tokio:: try_join!( http1_server. run( & http_addr) , http3_server. run( ) , ) ?;
448+ Self :: run_shutdown_hooks ( shutdown_hooks) . await ;
449+ Ok ( ( ) )
461450 }
462451}
0 commit comments