Skip to content

Commit fbed697

Browse files
committed
approach revamp using --find-free-port flag
Signed-off-by: Aminu 'Seun Joshua <[email protected]>
1 parent f9d5295 commit fbed697

File tree

3 files changed

+57
-32
lines changed

3 files changed

+57
-32
lines changed

crates/trigger-http/src/lib.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,8 @@ pub struct CliArgs {
5151
#[clap(long, env = "SPIN_TLS_KEY", requires = "tls-cert")]
5252
pub tls_key: Option<PathBuf>,
5353

54-
/// The port to listen on
55-
#[clap(long = "port", env = "SPIN_HTTP_LISTEN_PORT")]
56-
pub port: Option<u16>,
54+
#[clap(long = "find-free-port")]
55+
pub find_free_port: bool,
5756
}
5857

5958
impl CliArgs {
@@ -77,8 +76,7 @@ pub struct HttpTrigger {
7776
/// If the port is set to 0, the actual address will be determined by the OS.
7877
listen_addr: SocketAddr,
7978
tls_config: Option<TlsConfig>,
80-
/// Optional port to listen on
81-
port: Option<u16>,
79+
find_free_port: bool,
8280
}
8381

8482
impl<F: RuntimeFactors> Trigger<F> for HttpTrigger {
@@ -88,8 +86,14 @@ impl<F: RuntimeFactors> Trigger<F> for HttpTrigger {
8886
type InstanceState = ();
8987

9088
fn new(cli_args: Self::CliArgs, app: &spin_app::App) -> anyhow::Result<Self> {
91-
let port = cli_args.port;
92-
Self::new(app, cli_args.address, cli_args.into_tls_config(), port)
89+
let find_free_port = cli_args.find_free_port;
90+
91+
Self::new(
92+
app,
93+
cli_args.address,
94+
cli_args.into_tls_config(),
95+
find_free_port,
96+
)
9397
}
9498

9599
async fn run(self, trigger_app: TriggerApp<F>) -> anyhow::Result<()> {
@@ -111,14 +115,14 @@ impl HttpTrigger {
111115
app: &spin_app::App,
112116
listen_addr: SocketAddr,
113117
tls_config: Option<TlsConfig>,
114-
port: Option<u16>,
118+
find_free_port: bool,
115119
) -> anyhow::Result<Self> {
116120
Self::validate_app(app)?;
117121

118122
Ok(Self {
119123
listen_addr,
120124
tls_config,
121-
port,
125+
find_free_port,
122126
})
123127
}
124128

@@ -130,9 +134,14 @@ impl HttpTrigger {
130134
let Self {
131135
listen_addr,
132136
tls_config,
133-
port,
137+
find_free_port,
134138
} = self;
135-
let server = Arc::new(HttpServer::new(listen_addr, tls_config, port, trigger_app)?);
139+
let server = Arc::new(HttpServer::new(
140+
listen_addr,
141+
tls_config,
142+
find_free_port,
143+
trigger_app,
144+
)?);
136145
Ok(server)
137146
}
138147

crates/trigger-http/src/server.rs

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ pub struct HttpServer<F: RuntimeFactors> {
5353
listen_addr: SocketAddr,
5454
/// The TLS configuration for the server.
5555
tls_config: Option<TlsConfig>,
56-
/// Optional custom port the server is listening on.
57-
port: Option<u16>,
56+
/// Whether to find a free port if the specified port is already in use.
57+
find_free_port: bool,
5858
/// Request router.
5959
router: Router,
6060
/// The app being triggered.
@@ -70,7 +70,7 @@ impl<F: RuntimeFactors> HttpServer<F> {
7070
pub fn new(
7171
listen_addr: SocketAddr,
7272
tls_config: Option<TlsConfig>,
73-
port: Option<u16>,
73+
find_free_port: bool,
7474
trigger_app: TriggerApp<F>,
7575
) -> anyhow::Result<Self> {
7676
// This needs to be a vec before building the router to handle duplicate routes
@@ -138,7 +138,7 @@ impl<F: RuntimeFactors> HttpServer<F> {
138138
Ok(Self {
139139
listen_addr,
140140
tls_config,
141-
port,
141+
find_free_port,
142142
router,
143143
trigger_app,
144144
component_trigger_configs,
@@ -148,31 +148,47 @@ impl<F: RuntimeFactors> HttpServer<F> {
148148

149149
/// Serve incoming requests over the provided [`TcpListener`].
150150
pub async fn serve(self: Arc<Self>) -> anyhow::Result<()> {
151-
let listener: anyhow::Result<TcpListener> = match TcpListener::bind(self.listen_addr).await
152-
{
153-
Ok(listener) => Ok(listener),
151+
let listener: TcpListener = match TcpListener::bind(self.listen_addr).await {
152+
Ok(listener) => listener,
154153
Err(err) => {
155-
if err.kind() == ErrorKind::AddrInUse {
156-
let mut addr = self.listen_addr;
157-
addr.set_port(self.port.unwrap_or(0));
158-
let listener = TcpListener::bind(addr).await.with_context(|| {
159-
format!(
160-
"Unable to listen on {listen_addr}",
161-
listen_addr = self.listen_addr
162-
)
163-
})?;
154+
if self.find_free_port && err.kind() == ErrorKind::AddrInUse {
155+
let mut found_listener = None;
156+
for _ in 1..=9 {
157+
let mut addr = self.listen_addr;
158+
addr.set_port(addr.port() + 1);
159+
160+
match TcpListener::bind(addr).await {
161+
Ok(listener) => {
162+
found_listener = Some(listener);
163+
break;
164+
}
165+
Err(err) => {
166+
if err.kind() == ErrorKind::AddrInUse {
167+
continue;
168+
}
169+
return Err(anyhow::anyhow!("Unable to listen on {}", addr));
170+
}
171+
}
172+
}
164173

165-
Ok(listener)
174+
match found_listener {
175+
Some(listener) => listener,
176+
None => {
177+
return Err(anyhow::anyhow!(
178+
"All retries failed. Unable to bind to a free port"
179+
));
180+
}
181+
}
166182
} else {
167-
Err(anyhow::anyhow!("Unable to listen on {}", self.listen_addr))
183+
return Err(anyhow::anyhow!("Unable to listen on {}", self.listen_addr));
168184
}
169185
}
170186
};
171187

172188
if let Some(tls_config) = self.tls_config.clone() {
173-
self.serve_https(listener?, tls_config).await?;
189+
self.serve_https(listener, tls_config).await?;
174190
} else {
175-
self.serve_http(listener?).await?;
191+
self.serve_http(listener).await?;
176192
}
177193
Ok(())
178194
}

tests/testing-framework/src/runtimes/in_process_spin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ async fn initialize_trigger(
104104
.await?;
105105

106106
let app = spin_app::App::new("my-app", locked_app);
107-
let trigger = HttpTrigger::new(&app, "127.0.0.1:80".parse().unwrap(), None, None)?;
107+
let trigger = HttpTrigger::new(&app, "127.0.0.1:80".parse().unwrap(), None, true)?;
108108
let mut builder = TriggerAppBuilder::<_, FactorsBuilder>::new(trigger);
109109
let trigger_app = builder
110110
.build(

0 commit comments

Comments
 (0)