Repository: denoland/deno
Pull request: fix: support vsock in DENO_SERVE_ADDRESS

<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->


Changed files:
filename: cli/args/flags.rs
patch: @@ -1195,6 +1195,7 @@ static ENV_VARIABLES_HELP: &str = cstr!(
   <g>DENO_NO_PACKAGE_JSON</>   Disables auto-resolution of package.json
   <g>DENO_NO_UPDATE_CHECK</>   Set to disable checking if a newer Deno version is available
   <g>DENO_SERVE_ADDRESS</>     Override address for Deno.serve
+                         Example: "tcp:0.0.0.0:8080", "unix:/tmp/deno.sock", or "vsock:1234:5678"
   <g>DENO_TLS_CA_STORE</>      Comma-separated list of order dependent certificate stores.
                          Possible values: "system", "mozilla" <p(245)>(defaults to "mozilla")</>
   <g>DENO_TRACE_PERMISSIONS</> Environmental variable to enable stack traces in permission prompts.

filename: ext/http/00_serve.ts
patch: @@ -776,20 +776,40 @@ function serve(arg1, arg2) {
     options = { __proto__: null };
   }
 
-  const { 0: overrideUnixPath, 1: overrideHost, 2: overridePort } =
+  const { 0: overrideKind, 1: overrideHost, 2: overridePort } =
     op_http_serve_address_override();
-  if (overrideUnixPath) {
-    options.path = overrideUnixPath;
-    delete options.port;
-    delete options.host;
-  } else {
-    if (overrideHost) {
-      options.hostname = overrideHost;
+  switch (overrideKind) {
+    case 1: {
+      // TCP
+      options = {
+        ...options,
+        hostname: overrideHost,
+        port: overridePort,
+      };
       delete options.path;
+      delete options.cid;
+      break;
     }
-    if (overridePort) {
-      options.port = overridePort;
+    case 2: {
+      // Unix
+      options = {
+        ...options,
+        path: overrideHost,
+      };
+      delete options.hostname;
+      delete options.port;
+      break;
+    }
+    case 3: {
+      // Vsock
+      options = {
+        ...options,
+        cid: Number(overrideHost),
+        port: overridePort,
+      };
+      delete options.hostname;
       delete options.path;
+      break;
     }
   }
 

filename: ext/http/lib.rs
patch: @@ -11,6 +11,7 @@ use std::io;
 use std::io::Write;
 use std::mem::replace;
 use std::mem::take;
+use std::net::SocketAddr;
 use std::pin::pin;
 use std::pin::Pin;
 use std::rc::Rc;
@@ -1692,35 +1693,69 @@ fn extract_network_stream<U: CanDowncastUpgrade>(
 
 #[op2]
 #[serde]
-pub fn op_http_serve_address_override(
-) -> (Option<String>, Option<String>, Option<u16>) {
+pub fn op_http_serve_address_override() -> (u8, String, u32) {
   match std::env::var("DENO_SERVE_ADDRESS") {
     Ok(val) => parse_serve_address(&val),
-    Err(_) => (None, None, None),
+    Err(_) => (0, String::new(), 0),
   }
 }
 
-fn parse_serve_address(
-  input: &str,
-) -> (Option<String>, Option<String>, Option<u16>) {
-  use std::net::SocketAddr;
-  if input.contains('/') {
-    // Likely a Unix socket path
-    (Some(input.to_string()), None, None)
-  } else {
-    // Try parsing as a TCP address
-    match input.parse::<SocketAddr>() {
-      Ok(addr) => {
-        let hostname = match addr {
-          SocketAddr::V4(v4) => v4.ip().to_string(),
-          SocketAddr::V6(v6) => format!("[{}]", v6.ip()),
-        };
-        (None, Some(hostname), Some(addr.port()))
+fn parse_serve_address(input: &str) -> (u8, String, u32) {
+  match input.split_once(':') {
+    Some(("tcp", addr)) => {
+      // TCP address
+      match addr.parse::<SocketAddr>() {
+        Ok(addr) => {
+          let hostname = match addr {
+            SocketAddr::V4(v4) => v4.ip().to_string(),
+            SocketAddr::V6(v6) => format!("[{}]", v6.ip()),
+          };
+          (1, hostname, addr.port() as u32)
+        }
+        Err(_) => {
+          log::error!("DENO_SERVE_ADDRESS: invalid TCP address: {}", addr);
+          (0, String::new(), 0)
+        }
       }
-      Err(_) => {
-        log::error!("DENO_SERVE_ADDRESS: Invalid TCP address: {}", input);
-        (None, None, None)
+    }
+    Some(("unix", addr)) => {
+      // Unix socket path
+      if addr.is_empty() {
+        log::error!("DENO_SERVE_ADDRESS: empty unix socket path");
+        return (0, String::new(), 0);
       }
+      (2, addr.to_string(), 0)
+    }
+    Some(("vsock", addr)) => {
+      // Vsock address
+      match addr.split_once(':') {
+        Some((cid, port)) => {
+          let cid = if cid == "-1" {
+            "-1".to_string()
+          } else {
+            match cid.parse::<u32>() {
+              Ok(cid) => cid.to_string(),
+              Err(_) => {
+                log::error!("DENO_SERVE_ADDRESS: invalid vsock CID: {}", cid);
+                return (0, String::new(), 0);
+              }
+            }
+          };
+          let port = match port.parse::<u32>() {
+            Ok(port) => port,
+            Err(_) => {
+              log::error!("DENO_SERVE_ADDRESS: invalid vsock port: {}", port);
+              return (0, String::new(), 0);
+            }
+          };
+          (3, cid, port)
+        }
+        None => (0, String::new(), 0),
+      }
+    }
+    Some((_, _)) | None => {
+      log::error!("DENO_SERVE_ADDRESS: invalid address format: {}", input);
+      (0, String::new(), 0)
     }
   }
 }
@@ -1731,21 +1766,33 @@ mod tests {
 
   #[test]
   fn test_parse_serve_address() {
-    let input = "/var/run/socket.sock";
     assert_eq!(
-      parse_serve_address(input),
-      (Some(input.to_string()), None, None)
+      parse_serve_address("tcp:127.0.0.1:8080"),
+      (1, "127.0.0.1".to_string(), 8080)
+    );
+    assert_eq!(
+      parse_serve_address("tcp:[::1]:9000"),
+      (1, "[::1]".to_string(), 9000)
+    );
+
+    assert_eq!(
+      parse_serve_address("unix:/var/run/socket.sock"),
+      (2, "/var/run/socket.sock".to_string(), 0)
     );
 
     assert_eq!(
-      parse_serve_address("127.0.0.1:8080"),
-      (None, Some("127.0.0.1".to_string()), Some(8080))
+      parse_serve_address("vsock:1234:5678"),
+      (3, "1234".to_string(), 5678)
     );
     assert_eq!(
-      parse_serve_address("[::1]:9000"),
-      (None, Some("[::1]".to_string()), Some(9000))
+      parse_serve_address("vsock:-1:5678"),
+      (3, "-1".to_string(), 5678)
     );
 
-    assert_eq!(parse_serve_address("no_an_address"), (None, None, None));
+    assert_eq!(parse_serve_address("tcp:"), (0, String::new(), 0));
+    assert_eq!(parse_serve_address("unix:"), (0, String::new(), 0));
+    assert_eq!(parse_serve_address("vsock:"), (0, String::new(), 0));
+    assert_eq!(parse_serve_address("foo:"), (0, String::new(), 0));
+    assert_eq!(parse_serve_address("bar"), (0, String::new(), 0));
   }
 }

filename: tests/integration/serve_tests.rs
patch: @@ -277,7 +277,7 @@ async fn deno_run_serve_with_tcp_from_env() {
     .arg("run")
     .arg("--allow-net")
     .arg("./serve/run_serve.ts")
-    .env("DENO_SERVE_ADDRESS", "127.0.0.1:0")
+    .env("DENO_SERVE_ADDRESS", "tcp:127.0.0.1:0")
     .stderr_piped()
     .spawn()
     .unwrap();
@@ -320,7 +320,7 @@ async fn deno_run_serve_with_unix_socket_from_env() {
     .arg(format!("--allow-read={}", sock.display()))
     .arg(format!("--allow-write={}", sock.display()))
     .arg("./serve/run_serve.ts")
-    .env("DENO_SERVE_ADDRESS", sock)
+    .env("DENO_SERVE_ADDRESS", format!("unix:{}", sock.display()))
     .stderr_piped()
     .spawn()
     .unwrap();

filename: tests/specs/cli/otel_basic/main.ts
patch: @@ -51,8 +51,14 @@ const server = Deno.serve(
                 });
               }
             }
+            if ("sum" in metric) {
+              for (const dataPoint of metric.sum.dataPoints) {
+                dataPoint.attributes.sort((a, b) => {
+                  return a.key.localeCompare(b.key);
+                });
+              }
+            }
           }
-
           console.log(JSON.stringify(data, null, 2));
         });
     },