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 committed by Ryan Dahl
parent d07b7ea6f6
commit dad975b530
5 changed files with 137 additions and 0 deletions

1
Cargo.lock generated
View file

@ -952,6 +952,7 @@ dependencies = [
"serde",
"serde_json",
"sys_traits",
"tempfile",
"test_server",
"tokio",
"url",

View file

@ -46,6 +46,11 @@ const {
TypedArrayPrototypeGetSymbolToStringTag,
Uint8Array,
Promise,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeIndexOf,
NumberIsSafeInteger,
NumberParseInt,
} = primordials;
import { InnerBody } from "ext:deno_fetch/22_body.js";
@ -767,6 +772,58 @@ 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 (env) {
const delim = StringPrototypeIndexOf(env, "/");
if (delim >= 0) {
const network = StringPrototypeSlice(env, 0, delim);
const address = StringPrototypeSlice(env, delim + 1);
switch (network) {
case "tcp": {
const ipv6Delim = StringPrototypeIndexOf(address, "]");
let hostname: string;
let port: number;
if (ipv6Delim >= 0) {
hostname = StringPrototypeSlice(address, 0, ipv6Delim + 1);
port = NumberParseInt(StringPrototypeSlice(address, ipv6Delim + 2));
} else {
const { 0: hostname_, 1: port_ } = StringPrototypeSplit(
address,
":",
);
hostname = hostname_;
port = NumberParseInt(port_);
}
if (!NumberIsSafeInteger(port) || port < 0 || port > 65535) {
throw new TypeError(`DENO_SERVE_ADDRESS: Invalid port: ${port}`);
}
options.hostname = hostname;
options.port = port;
delete options.path;
break;
}
case "unix": {
delete options.hostname;
delete options.port;
options.path = address;
break;
}
default:
// deno-lint-ignore no-console
console.error(`DENO_SERVE_ADDRESS: Invalid network type: ${network}`);
break;
}
}
}
const wantsHttps = hasTlsKeyPairOptions(options);
const wantsUnix = ObjectHasOwn(options, "path");
const signal = options.signal;

View file

@ -60,6 +60,7 @@ rustls-tokio-stream.workspace = true
serde.workspace = true
serde_json.workspace = true
sys_traits = { workspace = true, features = ["real", "getrandom", "libc", "winapi"] }
tempfile.workspace = true
test_util.workspace = true
tokio.workspace = true
tower-lsp.workspace = true

View file

@ -2,6 +2,8 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read;
use std::time::Duration;
@ -267,3 +269,76 @@ async fn deno_serve_parallel() {
"bad {serve_counts:?}"
);
}
#[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!");
});