Skip to content

Commit e054717

Browse files
committed
feat(serve): Add --tls-cert and --tls-key to deno serve CLI (#25177)
1 parent f298025 commit e054717

14 files changed

Lines changed: 216 additions & 25 deletions

File tree

cli/args/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,37 @@ impl CliOptions {
698698
}
699699
}
700700

701+
pub fn serve_cert(&self) -> Result<Option<String>, AnyError> {
702+
if let DenoSubcommand::Serve(flags) = self.sub_command() {
703+
flags
704+
.tls_cert_and_key
705+
.as_ref()
706+
.map(|(cert, _key)| {
707+
std::fs::read_to_string(cert).with_context(|| {
708+
format!("Failed to read TLS certificate file: {cert:?}")
709+
})
710+
})
711+
.transpose()
712+
} else {
713+
Ok(None)
714+
}
715+
}
716+
717+
pub fn serve_key(&self) -> Result<Option<String>, AnyError> {
718+
if let DenoSubcommand::Serve(flags) = self.sub_command() {
719+
flags
720+
.tls_cert_and_key
721+
.as_ref()
722+
.map(|(_cert, key)| {
723+
std::fs::read_to_string(key)
724+
.with_context(|| format!("Failed to read TLS key file: {key:?}"))
725+
})
726+
.transpose()
727+
} else {
728+
Ok(None)
729+
}
730+
}
731+
701732
pub fn eszip(&self) -> bool {
702733
self.flags.eszip
703734
}

cli/factory.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,8 @@ impl CliFactory {
13521352
node_ipc_init: cli_options.node_ipc_init(&self.sys())?,
13531353
serve_port: cli_options.serve_port(),
13541354
serve_host: cli_options.serve_host(),
1355+
serve_cert: cli_options.serve_cert()?,
1356+
serve_key: cli_options.serve_key()?,
13551357
otel_config: cli_options.otel_config(),
13561358
no_legacy_abort: cli_options.no_legacy_abort(),
13571359
startup_snapshot: deno_snapshots::CLI_SNAPSHOT,

cli/lib/worker.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ pub struct LibMainWorkerOptions {
296296
pub residual_lazy_esm_sources: &'static [(&'static str, &'static str)],
297297
pub serve_port: Option<u16>,
298298
pub serve_host: Option<String>,
299+
pub serve_cert: Option<String>,
300+
pub serve_key: Option<String>,
299301
pub close_on_idle: bool,
300302
pub maybe_initial_cwd: Option<Url>,
301303
/// When true, the `OffscreenCanvas` global is removed at bootstrap.
@@ -501,6 +503,8 @@ impl<TSys: DenoLibSys> LibWorkerFactorySharedState<TSys> {
501503
mode: WorkerExecutionMode::Worker,
502504
serve_port: shared.options.serve_port,
503505
serve_host: shared.options.serve_host.clone(),
506+
serve_cert: shared.options.serve_cert.clone(),
507+
serve_key: shared.options.serve_key.clone(),
504508
otel_config: shared.options.otel_config.clone(),
505509
no_legacy_abort: shared.options.no_legacy_abort,
506510
close_on_idle: args.close_on_idle,
@@ -752,6 +756,8 @@ impl<TSys: DenoLibSys> LibMainWorkerFactory<TSys> {
752756
no_legacy_abort: shared.options.no_legacy_abort,
753757
serve_port: shared.options.serve_port,
754758
serve_host: shared.options.serve_host.clone(),
759+
serve_cert: shared.options.serve_cert.clone(),
760+
serve_key: shared.options.serve_key.clone(),
755761
otel_config: shared.options.otel_config.clone(),
756762
close_on_idle: shared.options.close_on_idle,
757763
disable_offscreen_canvas: shared.options.disable_offscreen_canvas,

cli/rt/run.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,8 @@ pub struct RunOptions {
13131313
pub auto_serve: bool,
13141314
pub serve_port: Option<u16>,
13151315
pub serve_host: Option<String>,
1316+
pub serve_cert: Option<String>,
1317+
pub serve_key: Option<String>,
13161318
/// Enable HMR file watching from this directory.
13171319
pub hmr_watch_dir: Option<PathBuf>,
13181320
/// Callback invoked after each successful HMR replacement.
@@ -1778,6 +1780,8 @@ pub async fn run_with_options(
17781780
node_ipc_init: deno_lib::args::node_ipc_init(&sys)?,
17791781
serve_port: options.serve_port,
17801782
serve_host: options.serve_host,
1783+
serve_cert: options.serve_cert,
1784+
serve_key: options.serve_key,
17811785
otel_config: metadata.otel_config,
17821786
no_legacy_abort: false,
17831787
startup_snapshot: deno_snapshots::CLI_SNAPSHOT,

cli/rt_desktop/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1800,6 +1800,8 @@ async fn run_desktop(
18001800
auto_serve: true,
18011801
serve_port: Some(desktop_serve_port),
18021802
serve_host: Some("127.0.0.1".to_string()),
1803+
serve_cert: None,
1804+
serve_key: None,
18031805
hmr_watch_dir: if is_framework_dev {
18041806
None
18051807
} else {

cli/tools/serve.rs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ pub async fn serve(
6969
);
7070

7171
if serve_flags.open_site {
72-
let url = resolve_serve_url(serve_flags.host, serve_flags.port);
72+
let url = resolve_serve_url(
73+
serve_flags.host,
74+
serve_flags.port,
75+
serve_flags.tls_cert_and_key.is_some(),
76+
);
7377
let _ = open::that_detached(url);
7478
}
7579

@@ -229,18 +233,20 @@ async fn serve_with_watch(
229233
Ok(0)
230234
}
231235

232-
fn resolve_serve_url(host: String, port: u16) -> String {
236+
fn resolve_serve_url(host: String, port: u16, https: bool) -> String {
233237
let host = if matches!(host.as_str(), "0.0.0.0" | "::") {
234238
"127.0.0.1".to_string()
235239
} else if std::net::Ipv6Addr::from_str(&host).is_ok() {
236240
format!("[{}]", host)
237241
} else {
238242
host
239243
};
240-
if port == 80 {
241-
format!("http://{host}/")
244+
let (scheme, default_port) =
245+
if https { ("https", 443) } else { ("http", 80) };
246+
if port == default_port {
247+
format!("{scheme}://{host}/")
242248
} else {
243-
format!("http://{host}:{port}/")
249+
format!("{scheme}://{host}:{port}/")
244250
}
245251
}
246252

@@ -251,17 +257,36 @@ mod test {
251257
#[test]
252258
fn test_resolve_serve_url() {
253259
assert_eq!(
254-
resolve_serve_url("localhost".to_string(), 80),
260+
resolve_serve_url("localhost".to_string(), 80, false),
255261
"http://localhost/"
256262
);
257263
assert_eq!(
258-
resolve_serve_url("0.0.0.0".to_string(), 80),
264+
resolve_serve_url("0.0.0.0".to_string(), 80, false),
265+
"http://127.0.0.1/"
266+
);
267+
assert_eq!(
268+
resolve_serve_url("::".to_string(), 80, false),
259269
"http://127.0.0.1/"
260270
);
261-
assert_eq!(resolve_serve_url("::".to_string(), 80), "http://127.0.0.1/");
262271
assert_eq!(
263-
resolve_serve_url("::".to_string(), 90),
272+
resolve_serve_url("::".to_string(), 90, false),
264273
"http://127.0.0.1:90/"
265274
);
275+
assert_eq!(
276+
resolve_serve_url("localhost".to_string(), 443, true),
277+
"https://localhost/"
278+
);
279+
assert_eq!(
280+
resolve_serve_url("0.0.0.0".to_string(), 443, true),
281+
"https://127.0.0.1/"
282+
);
283+
assert_eq!(
284+
resolve_serve_url("::".to_string(), 443, true),
285+
"https://127.0.0.1/"
286+
);
287+
assert_eq!(
288+
resolve_serve_url("::".to_string(), 90, true),
289+
"https://127.0.0.1:90/"
290+
);
266291
}
267292
}

ext/http/00_serve.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,11 +1649,15 @@ function registerDeclarativeServer(exports) {
16491649
return ({
16501650
servePort,
16511651
serveHost,
1652+
serveCert,
1653+
serveKey,
16521654
workerCountWhenMain,
16531655
}) => {
16541656
const server = Deno.serve({
16551657
port: servePort,
16561658
hostname: serveHost,
1659+
cert: serveCert ?? undefined,
1660+
key: serveKey ?? undefined,
16571661
[kLoadBalanced]: workerCountWhenMain == null
16581662
? true
16591663
: workerCountWhenMain > 0,
@@ -1666,11 +1670,13 @@ function registerDeclarativeServer(exports) {
16661670

16671671
let target;
16681672
switch (localAddr.transport) {
1669-
case "tcp":
1670-
target = `http://${
1673+
case "tcp": {
1674+
const protocol = serveCert === null ? "http" : "https";
1675+
target = `${protocol}://${
16711676
formatHostName(localAddr.hostname)
16721677
}:${localAddr.port}/`;
16731678
break;
1679+
}
16741680
case "unix":
16751681
target = localAddr.path;
16761682
break;

libs/cli_parser/src/convert.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,14 @@ fn serve_parse(
14801480
flags.code_cache_enabled = !result.get_bool("no-code-cache");
14811481
flags.tunnel = result.get_bool("tunnel");
14821482

1483+
let tls_cert = result.get_one("tls-cert");
1484+
let tls_key = result.get_one("tls-key");
1485+
1486+
let tls_cert_and_key = match (tls_cert, tls_key) {
1487+
(Some(cert), Some(key)) => Some((cert.to_owned(), key.to_owned())),
1488+
_ => None,
1489+
};
1490+
14831491
let script = result
14841492
.get_one("script_arg")
14851493
.map(|s| s.to_string())
@@ -1495,6 +1503,7 @@ fn serve_parse(
14951503
script,
14961504
port,
14971505
host,
1506+
tls_cert_and_key,
14981507
parallel,
14991508
open_site: result.get_bool("open"),
15001509
});

libs/cli_parser/src/defs.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,18 @@ pub static SERVE_SUBCOMMAND: CommandDef = CommandDef {
745745
.action(ArgAction::Set)
746746
.num_args(NumArgs::Exact(1))
747747
.help("The TCP address to serve on, defaulting to 0.0.0.0 (all interfaces)"),
748+
ArgDef::new("tls-cert")
749+
.long("tls-cert")
750+
.action(ArgAction::Set)
751+
.num_args(NumArgs::Exact(1))
752+
.requires(&["tls-key"])
753+
.help("Load TLS certificate from PEM encoded file"),
754+
ArgDef::new("tls-key")
755+
.long("tls-key")
756+
.action(ArgAction::Set)
757+
.num_args(NumArgs::Exact(1))
758+
.requires(&["tls-cert"])
759+
.help("Load TLS private key from PEM encoded file"),
748760
ArgDef::new("open").long("open").set_true()
749761
.help("Open the browser on the address that the server is running on."),
750762
ArgDef::new("tunnel")

libs/cli_parser/src/flags.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ pub struct ServeFlags {
593593
pub host: String,
594594
pub parallel: bool,
595595
pub open_site: bool,
596+
pub tls_cert_and_key: Option<(String, String)>,
596597
}
597598

598599
impl ServeFlags {
@@ -603,6 +604,7 @@ impl ServeFlags {
603604
host: host.to_owned(),
604605
parallel: false,
605606
open_site: false,
607+
tls_cert_and_key: None,
606608
}
607609
}
608610
}

0 commit comments

Comments
 (0)