@@ -1072,20 +1072,7 @@ impl RustApi {
10721072 server. run ( ) . await
10731073 }
10741074
1075- /// Run both HTTP/1.1 and HTTP/3 servers simultaneously
1076- ///
1077- /// This allows clients to use either protocol. The HTTP/1.1 server
1078- /// will advertise HTTP/3 availability via Alt-Svc header.
1079- ///
1080- /// # Example
1081- ///
1082- /// ```rust,ignore
1083- /// RustApi::new()
1084- /// .route("/", get(hello))
1085- /// .run_dual_stack("0.0.0.0:8080", Http3Config::new("cert.pem", "key.pem"))
1086- /// .await
1087- /// ```
1088- /// Configure HTTP/3 support
1075+ /// Configure HTTP/3 support for `run_http3` and `run_dual_stack`.
10891076 ///
10901077 /// # Example
10911078 ///
@@ -1101,10 +1088,10 @@ impl RustApi {
11011088 self
11021089 }
11031090
1104- /// Run both HTTP/1.1 and HTTP/3 servers simultaneously
1091+ /// Run both HTTP/1.1 (TCP) and HTTP/3 (QUIC/UDP) simultaneously.
11051092 ///
1106- /// This allows clients to use either protocol. The HTTP/1.1 server
1107- /// will advertise HTTP/3 availability via Alt-Svc header .
1093+ /// The HTTP/3 listener is bound to the same host and port as `http_addr`
1094+ /// so clients can upgrade to either protocol on one endpoint .
11081095 ///
11091096 /// # Example
11101097 ///
@@ -1118,22 +1105,53 @@ impl RustApi {
11181105 #[ cfg( feature = "http3" ) ]
11191106 pub async fn run_dual_stack (
11201107 mut self ,
1121- _http_addr : & str ,
1108+ http_addr : & str ,
11221109 ) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
1123- // TODO: Dual-stack requires Router, LayerStack, InterceptorChain to implement Clone.
1124- // For now, we only run HTTP/3.
1125- // In the future, we can either:
1126- // 1. Make Router/LayerStack/InterceptorChain Clone
1127- // 2. Use Arc<RwLock<...>> pattern
1128- // 3. Create shared state mechanism
1129-
1130- let config = self
1110+ use std:: sync:: Arc ;
1111+
1112+ let mut config = self
11311113 . http3_config
11321114 . take ( )
11331115 . ok_or ( "HTTP/3 config not set. Use .with_http3(...)" ) ?;
11341116
1135- tracing:: warn!( "run_dual_stack currently only runs HTTP/3. HTTP/1.1 support coming soon." ) ;
1136- self . run_http3 ( config) . await
1117+ let http_socket: std:: net:: SocketAddr = http_addr. parse ( ) ?;
1118+ config. bind_addr = if http_socket. ip ( ) . is_ipv6 ( ) {
1119+ format ! ( "[{}]" , http_socket. ip( ) )
1120+ } else {
1121+ http_socket. ip ( ) . to_string ( )
1122+ } ;
1123+ config. port = http_socket. port ( ) ;
1124+ let http_addr = http_socket. to_string ( ) ;
1125+
1126+ // Apply status page if configured
1127+ self . apply_status_page ( ) ;
1128+
1129+ // Apply body limit layer if configured
1130+ if let Some ( limit) = self . body_limit {
1131+ self . layers . prepend ( Box :: new ( BodyLimitLayer :: new ( limit) ) ) ;
1132+ }
1133+
1134+ let router = Arc :: new ( self . router ) ;
1135+ let layers = Arc :: new ( self . layers ) ;
1136+ let interceptors = Arc :: new ( self . interceptors ) ;
1137+
1138+ let http1_server =
1139+ Server :: from_shared ( router. clone ( ) , layers. clone ( ) , interceptors. clone ( ) ) ;
1140+ let http3_server =
1141+ crate :: http3:: Http3Server :: new ( & config, router, layers, interceptors) . await ?;
1142+
1143+ tracing:: info!(
1144+ http1_addr = %http_addr,
1145+ http3_addr = %config. socket_addr( ) ,
1146+ "Starting dual-stack HTTP/1.1 + HTTP/3 servers"
1147+ ) ;
1148+
1149+ tokio:: try_join!(
1150+ http1_server. run_with_shutdown( & http_addr, std:: future:: pending:: <( ) >( ) ) ,
1151+ http3_server. run_with_shutdown( std:: future:: pending:: <( ) >( ) ) ,
1152+ ) ?;
1153+
1154+ Ok ( ( ) )
11371155 }
11381156}
11391157
0 commit comments