Skip to content

Commit 6b9e838

Browse files
committed
Refactor HttpServer to capture actual bound address
Signed-off-by: Zhiwei Liang <zhiwei.liang@zliang.me>
1 parent 656560a commit 6b9e838

1 file changed

Lines changed: 23 additions & 7 deletions

File tree

crates/trigger-http/src/server.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
6262
pub 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

Comments
 (0)