Skip to content

Commit 20cb0e8

Browse files
authored
feat(serve): support --port 0 to use an open port (#23846)
Closes #23845
1 parent 2b560be commit 20cb0e8

File tree

9 files changed

+96
-16
lines changed

9 files changed

+96
-16
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/args/flags.rs

+31-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ use serde::Serialize;
2626
use std::env;
2727
use std::ffi::OsString;
2828
use std::net::SocketAddr;
29-
use std::num::NonZeroU16;
3029
use std::num::NonZeroU32;
3130
use std::num::NonZeroU8;
3231
use std::num::NonZeroUsize;
@@ -283,7 +282,7 @@ impl RunFlags {
283282
pub struct ServeFlags {
284283
pub script: String,
285284
pub watch: Option<WatchFlagsWithPaths>,
286-
pub port: NonZeroU16,
285+
pub port: u16,
287286
pub host: String,
288287
}
289288

@@ -293,7 +292,7 @@ impl ServeFlags {
293292
Self {
294293
script,
295294
watch: None,
296-
port: NonZeroU16::new(port).unwrap(),
295+
port,
297296
host: host.to_owned(),
298297
}
299298
}
@@ -2464,8 +2463,8 @@ fn serve_subcommand() -> Command {
24642463
.arg(
24652464
Arg::new("port")
24662465
.long("port")
2467-
.help("The TCP port to serve on, defaulting to 8000.")
2468-
.value_parser(value_parser!(NonZeroU16)),
2466+
.help("The TCP port to serve on, defaulting to 8000. Pass 0 to pick a random free port.")
2467+
.value_parser(value_parser!(u16)),
24692468
)
24702469
.arg(
24712470
Arg::new("host")
@@ -4127,9 +4126,7 @@ fn serve_parse(
41274126
app: Command,
41284127
) -> clap::error::Result<()> {
41294128
// deno serve implies --allow-net=host:port
4130-
let port = matches
4131-
.remove_one::<NonZeroU16>("port")
4132-
.unwrap_or(NonZeroU16::new(8000).unwrap());
4129+
let port = matches.remove_one::<u16>("port").unwrap_or(8000);
41334130
let host = matches
41344131
.remove_one::<String>("host")
41354132
.unwrap_or_else(|| "0.0.0.0".to_owned());
@@ -5322,6 +5319,32 @@ mod tests {
53225319
..Flags::default()
53235320
}
53245321
);
5322+
5323+
let r = flags_from_vec(svec![
5324+
"deno",
5325+
"serve",
5326+
"--port",
5327+
"0",
5328+
"--host",
5329+
"example.com",
5330+
"main.ts"
5331+
]);
5332+
assert_eq!(
5333+
r.unwrap(),
5334+
Flags {
5335+
subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
5336+
"main.ts".to_string(),
5337+
0,
5338+
"example.com"
5339+
)),
5340+
permissions: PermissionFlags {
5341+
allow_net: Some(vec!["example.com:0".to_owned()]),
5342+
..Default::default()
5343+
},
5344+
code_cache_enabled: true,
5345+
..Flags::default()
5346+
}
5347+
);
53255348
}
53265349

53275350
#[test]

cli/args/mod.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ use std::env;
6262
use std::io::BufReader;
6363
use std::io::Cursor;
6464
use std::net::SocketAddr;
65-
use std::num::NonZeroU16;
6665
use std::num::NonZeroUsize;
6766
use std::path::Path;
6867
use std::path::PathBuf;
@@ -1036,7 +1035,7 @@ impl CliOptions {
10361035
}
10371036
}
10381037

1039-
pub fn serve_port(&self) -> Option<NonZeroU16> {
1038+
pub fn serve_port(&self) -> Option<u16> {
10401039
if let DenoSubcommand::Serve(flags) = self.sub_command() {
10411040
Some(flags.port)
10421041
} else {

cli/worker.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
22

3-
use std::num::NonZeroU16;
43
use std::path::Path;
54
use std::path::PathBuf;
65
use std::rc::Rc;
@@ -148,7 +147,7 @@ struct SharedWorkerState {
148147
disable_deprecated_api_warning: bool,
149148
verbose_deprecated_api_warning: bool,
150149
code_cache: Option<Arc<dyn code_cache::CodeCache>>,
151-
serve_port: Option<NonZeroU16>,
150+
serve_port: Option<u16>,
152151
serve_host: Option<String>,
153152
}
154153

@@ -418,7 +417,7 @@ impl CliMainWorkerFactory {
418417
feature_checker: Arc<FeatureChecker>,
419418
options: CliMainWorkerOptions,
420419
node_ipc: Option<i64>,
421-
serve_port: Option<NonZeroU16>,
420+
serve_port: Option<u16>,
422421
serve_host: Option<String>,
423422
enable_future_features: bool,
424423
disable_deprecated_api_warning: bool,

runtime/worker_bootstrap.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use deno_core::v8;
44
use deno_core::ModuleSpecifier;
55
use serde::Serialize;
66
use std::cell::RefCell;
7-
use std::num::NonZeroU16;
87
use std::thread;
98

109
use deno_terminal::colors;
@@ -93,7 +92,7 @@ pub struct BootstrapOptions {
9392
pub future: bool,
9493
pub mode: WorkerExecutionMode,
9594
// Used by `deno serve`
96-
pub serve_port: Option<NonZeroU16>,
95+
pub serve_port: Option<u16>,
9796
pub serve_host: Option<String>,
9897
}
9998

@@ -198,7 +197,7 @@ impl BootstrapOptions {
198197
self.verbose_deprecated_api_warning,
199198
self.future,
200199
self.mode as u8 as _,
201-
self.serve_port.map(|x| x.into()).unwrap_or_default(),
200+
self.serve_port.unwrap_or_default(),
202201
self.serve_host.as_deref(),
203202
);
204203

tests/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ hyper-util.workspace = true
5252
once_cell.workspace = true
5353
os_pipe.workspace = true
5454
pretty_assertions.workspace = true
55+
regex.workspace = true
5556
serde.workspace = true
5657
test_util.workspace = true
5758
tokio.workspace = true

tests/integration/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ mod publish;
6060
mod repl;
6161
#[path = "run_tests.rs"]
6262
mod run;
63+
#[path = "serve_tests.rs"]
64+
mod serve;
6365
#[path = "shared_library_tests.rs"]
6466
mod shared_library_tests;
6567
#[path = "task_tests.rs"]

tests/integration/serve_tests.rs

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
3+
use std::io::Read;
4+
5+
use deno_fetch::reqwest;
6+
use pretty_assertions::assert_eq;
7+
use regex::Regex;
8+
use test_util as util;
9+
10+
#[tokio::test]
11+
async fn deno_serve_port_0() {
12+
let mut child = util::deno_cmd()
13+
.current_dir(util::testdata_path())
14+
.arg("serve")
15+
.arg("--port")
16+
.arg("0")
17+
.arg("./serve.ts")
18+
.stdout_piped()
19+
.spawn()
20+
.unwrap();
21+
let stdout = child.stdout.as_mut().unwrap();
22+
let mut buffer = [0; 50];
23+
let _read = stdout.read(&mut buffer).unwrap();
24+
let msg = std::str::from_utf8(&buffer).unwrap();
25+
let port_regex = Regex::new(r"(\d+)").unwrap();
26+
let port = port_regex.find(msg).unwrap().as_str();
27+
28+
let cert = reqwest::Certificate::from_pem(include_bytes!(
29+
"../testdata/tls/RootCA.crt"
30+
))
31+
.unwrap();
32+
33+
let client = reqwest::Client::builder()
34+
.add_root_certificate(cert)
35+
.http2_prior_knowledge()
36+
.build()
37+
.unwrap();
38+
39+
let res = client
40+
.get(&format!("http://127.0.0.1:{port}"))
41+
.send()
42+
.await
43+
.unwrap();
44+
assert_eq!(200, res.status());
45+
46+
let body = res.text().await.unwrap();
47+
assert_eq!(body, "deno serve --port 0 works!");
48+
49+
child.kill().unwrap();
50+
child.wait().unwrap();
51+
}

tests/testdata/serve.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
fetch(_req: Request) {
3+
return new Response("deno serve --port 0 works!");
4+
},
5+
};

0 commit comments

Comments
 (0)