feat(serve): env var DENO_SERVE_ADDRESS for configuring default listen address

This commit is contained in:
losfair 2024-07-02 00:06:24 +08:00
parent f4b9d85862
commit 82c97d3fd4
6 changed files with 124 additions and 0 deletions

1
Cargo.lock generated
View File

@ -744,6 +744,7 @@ dependencies = [
"pretty_assertions",
"regex",
"serde",
"tempfile",
"test_server",
"tokio",
"tower-lsp",

View File

@ -1100,6 +1100,8 @@ static ENV_VARIABLES_HELP: &str = color_print::cstr!(
<g>DENO_NO_UPDATE_CHECK</> Set to disable checking if a newer Deno version is
available
<g>DENO_SERVE_ADDRESS</> Default listening address for `Deno.serve()`
<g>DENO_TLS_CA_STORE</> Comma-separated list of order dependent certificate
stores. Possible values: "system", "mozilla".
Defaults to "mozilla".

View File

@ -595,6 +595,48 @@ function serve(arg1, arg2) {
options = { __proto__: null };
}
const canOverrideOptions = !ObjectHasOwn(options, "path")
&& !ObjectHasOwn(options, "hostname")
&& !ObjectHasOwn(options, "port");
const env = Deno.permissions.querySync({ name: "env", variable: "DENO_SERVE_ADDRESS" }).state === "granted"
&& Deno.env.get("DENO_SERVE_ADDRESS");
if (canOverrideOptions && env) {
const delim = env.indexOf("/");
if (delim >= 0) {
const network = env.slice(0, delim);
const address = env.slice(delim + 1);
switch (network) {
case "tcp": {
const ipv6Delim = address.indexOf("]");
let hostname: string;
let port: number;
if (ipv6Delim >= 0) {
hostname = address.slice(0, ipv6Delim + 1);
port = parseInt(address.slice(ipv6Delim + 2));
} else {
const [hostname_, port_] = address.split(":");
hostname = hostname_;
port = parseInt(port_);
}
if (!Number.isSafeInteger(port) || port < 0 || port > 65535) {
throw new TypeError(`DENO_SERVE_ADDRESS: Invalid port: ${port}`);
}
options.hostname = hostname;
options.port = port;
break;
}
case "unix": {
options.path = address;
break;
}
default:
console.error(`DENO_SERVE_ADDRESS: Invalid network type: ${network}`);
}
}
}
const wantsHttps = hasTlsKeyPairOptions(options);
const wantsUnix = ObjectHasOwn(options, "path");
const signal = options.signal;

View File

@ -54,6 +54,7 @@ os_pipe.workspace = true
pretty_assertions.workspace = true
regex.workspace = true
serde.workspace = true
tempfile.workspace = true
test_util.workspace = true
tokio.workspace = true
tower-lsp.workspace = true

View File

@ -1,5 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read;
use deno_fetch::reqwest;
@ -92,3 +94,76 @@ async fn deno_serve_no_args() {
child.kill().unwrap();
child.wait().unwrap();
}
#[tokio::test]
async fn deno_run_serve_with_tcp_from_env() {
let mut child = util::deno_cmd()
.current_dir(util::testdata_path())
.arg("run")
.arg("--allow-env=DENO_SERVE_ADDRESS")
.arg("--allow-net")
.arg("./serve/run_serve.ts")
.env("DENO_SERVE_ADDRESS", format!("tcp/127.0.0.1:0"))
.stdout_piped()
.spawn()
.unwrap();
let stdout = BufReader::new(child.stdout.as_mut().unwrap());
let msg = stdout.lines().next().unwrap().unwrap();
// Deno.serve() listens on 0.0.0.0 by default. This checks DENO_SERVE_ADDRESS
// is not ignored by ensuring it's listening on 127.0.0.1.
let port_regex = Regex::new(r"http:\/\/127\.0\.0\.1:(\d+)").unwrap();
let port = port_regex.captures(&msg).unwrap().get(1).unwrap().as_str();
let client = reqwest::Client::builder().build().unwrap();
let res = client
.get(&format!("http://127.0.0.1:{port}"))
.send()
.await
.unwrap();
assert_eq!(200, res.status());
let body = res.text().await.unwrap();
assert_eq!(body, "Deno.serve() works!");
child.kill().unwrap();
child.wait().unwrap();
}
#[tokio::test]
#[cfg(unix)]
async fn deno_run_serve_with_unix_socket_from_env() {
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use tokio::net::UnixStream;
let dir = tempfile::TempDir::new().unwrap();
let sock = dir.path().join("listen.sock");
let mut child = util::deno_cmd()
.current_dir(util::testdata_path())
.arg("run")
.arg("--allow-env=DENO_SERVE_ADDRESS")
.arg(format!("--allow-read={}", sock.display()))
.arg(format!("--allow-write={}", sock.display()))
.arg("./serve/run_serve.ts")
.env("DENO_SERVE_ADDRESS", format!("unix/{}", sock.display()))
.stdout_piped()
.spawn()
.unwrap();
let stdout = BufReader::new(child.stdout.as_mut().unwrap());
stdout.lines().next().unwrap().unwrap();
// reqwest does not support connecting to unix sockets yet, so here we send the http
// payload directly
let mut conn = UnixStream::connect(dir.path().join("listen.sock"))
.await
.unwrap();
conn.write_all(b"GET / HTTP/1.0\r\n\r\n").await.unwrap();
let mut response = String::new();
conn.read_to_string(&mut response).await.unwrap();
assert!(response.ends_with("\r\nDeno.serve() works!"));
child.kill().unwrap();
child.wait().unwrap();
}

3
tests/testdata/serve/run_serve.ts vendored Normal file
View File

@ -0,0 +1,3 @@
Deno.serve((_req: Request) => {
return new Response("Deno.serve() works!");
})