@@ -3,7 +3,7 @@ use std::{
33 future:: Future ,
44 io:: { ErrorKind , IsTerminal } ,
55 net:: SocketAddr ,
6- sync:: Arc ,
6+ sync:: { Arc , OnceLock } ,
77 time:: Duration ,
88} ;
99
@@ -60,8 +60,14 @@ pub const MAX_RETRIES: u16 = 10;
6060
6161/// An HTTP server which runs Spin apps.
6262pub struct HttpServer < F : RuntimeFactors > {
63- /// The address the server is listening on .
63+ /// The address the server was configured to listen on (the `--listen` value) .
6464 listen_addr : SocketAddr ,
65+ /// The address the server is actually bound to, captured once after binding.
66+ ///
67+ /// This can differ from `listen_addr` when the OS assigns the port — e.g.
68+ /// `--listen 127.0.0.1:0` or `--find-free-port`. Self-request origins must use
69+ /// this real address rather than the configured one.
70+ local_addr : OnceLock < SocketAddr > ,
6571 /// The TLS configuration for the server.
6672 tls_config : Option < TlsConfig > ,
6773 /// The maximum buffer size for an HTTP1 connection.
@@ -151,6 +157,7 @@ impl<F: RuntimeFactors> HttpServer<F> {
151157 . collect :: < anyhow:: Result < _ > > ( ) ?;
152158 Ok ( Self {
153159 listen_addr,
160+ local_addr : OnceLock :: new ( ) ,
154161 tls_config,
155162 find_free_port,
156163 router,
@@ -207,6 +214,8 @@ impl<F: RuntimeFactors> HttpServer<F> {
207214 } ) ?
208215 } ;
209216
217+ let _ = self . local_addr . set ( listener. local_addr ( ) ?) ;
218+
210219 if let Some ( tls_config) = self . tls_config . clone ( ) {
211220 self . serve_https ( listener, tls_config) . await ?;
212221 } else {
@@ -247,7 +256,7 @@ impl<F: RuntimeFactors> HttpServer<F> {
247256 }
248257
249258 async fn serve_http ( self : Arc < Self > , listener : TcpListener ) -> anyhow:: Result < ( ) > {
250- self . print_startup_msgs ( "http" , & listener ) ?;
259+ self . print_startup_msgs ( "http" ) ?;
251260 loop {
252261 let ( stream, client_addr) = listener. accept ( ) . await ?;
253262 self . clone ( )
@@ -260,7 +269,7 @@ impl<F: RuntimeFactors> HttpServer<F> {
260269 listener : TcpListener ,
261270 tls_config : TlsConfig ,
262271 ) -> anyhow:: Result < ( ) > {
263- self . print_startup_msgs ( "https" , & listener ) ?;
272+ self . print_startup_msgs ( "https" ) ?;
264273 let acceptor = tls_config. server_config ( ) ?;
265274 loop {
266275 let ( stream, client_addr) = listener. accept ( ) . await ?;
@@ -386,7 +395,11 @@ impl<F: RuntimeFactors> HttpServer<F> {
386395 . context (
387396 "The wasi HTTP trigger was configured without the required wasi outbound http support" ,
388397 ) ?;
389- let origin = SelfRequestOrigin :: create ( server_scheme, & self . listen_addr . to_string ( ) ) ?;
398+ // Fall back to the configured `listen_addr` when the bound address hasn't been
399+ // recorded: the in-process runtime (`InProcessSpin`) drives `handle` directly via
400+ // `into_server` without ever calling `serve()`, so `local_addr` is never set there.
401+ let self_addr = self . local_addr . get ( ) . copied ( ) . unwrap_or ( self . listen_addr ) ;
402+ let origin = SelfRequestOrigin :: create ( server_scheme, & self_addr. to_string ( ) ) ?;
390403 outbound_http. set_self_request_origin ( origin) ;
391404 outbound_http. set_request_interceptor ( OutboundHttpInterceptor :: new ( self . clone ( ) ) ) ?;
392405
@@ -582,8 +595,11 @@ impl<F: RuntimeFactors> HttpServer<F> {
582595 }
583596 }
584597
585- fn print_startup_msgs ( & self , scheme : & str , listener : & TcpListener ) -> anyhow:: Result < ( ) > {
586- let local_addr = listener. local_addr ( ) ?;
598+ fn print_startup_msgs ( & self , scheme : & str ) -> anyhow:: Result < ( ) > {
599+ let local_addr = self
600+ . local_addr
601+ . get ( )
602+ . context ( "listen address not recorded before printing startup messages" ) ?;
587603 let base_url = format ! ( "{scheme}://{local_addr:?}" ) ;
588604 tracing:: info!( "Serving {base_url}" ) ;
589605
0 commit comments