-
Notifications
You must be signed in to change notification settings - Fork 680
feat(net): add isPortAvailable
#7204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| // Copyright 2018-2026 the Deno authors. MIT license. | ||
|
|
||
| /** | ||
| * Options for {@linkcode isPortAvailable}. | ||
| * | ||
| * @experimental **UNSTABLE**: New API, yet to be vetted. | ||
| */ | ||
| export interface IsPortAvailableOptions { | ||
| /** | ||
| * The hostname to check the port on. | ||
| * | ||
| * @default {"0.0.0.0"} | ||
| */ | ||
| hostname?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Returns whether the given TCP port is available to listen on. | ||
| * | ||
| * @experimental **UNSTABLE**: New API, yet to be vetted. | ||
| * | ||
| * > [!IMPORTANT] | ||
| * > This check is inherently racy: the port may be taken by another process | ||
| * > between the time this function returns and the time you attempt to listen | ||
| * > on it. When you control the listener, prefer passing `port: 0` to | ||
| * > {@linkcode Deno.serve} or {@linkcode Deno.listen} to let the operating | ||
| * > system assign an available port, or use | ||
| * > {@linkcode https://jsr.io/@std/net/doc/get-available-port/~/getAvailablePort | getAvailablePort}. | ||
| * | ||
| * This function requires the `--allow-net` permission. Any error other than | ||
| * {@linkcode Deno.errors.AddrInUse} is rethrown, including | ||
| * {@linkcode Deno.errors.PermissionDenied} for privileged ports (below 1024) | ||
| * or when the `net` permission has not been granted. | ||
| * | ||
| * @param port The port to check. Use `0` to check whether the operating system | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: documenting |
||
| * can assign an ephemeral port. | ||
| * @param options Options for checking the port. | ||
| * @returns `true` if the port is available to listen on, `false` if it is | ||
| * already in use. | ||
| * | ||
| * @example Usage | ||
| * ```ts | ||
| * import { isPortAvailable } from "@std/net/unstable-is-port-available"; | ||
| * import { assert } from "@std/assert"; | ||
| * | ||
| * using listener = Deno.listen({ port: 0 }); | ||
| * const { port } = listener.addr; | ||
| * | ||
| * assert(!isPortAvailable(port)); | ||
| * ``` | ||
| * | ||
| * @example Check before serving | ||
| * ```ts no-assert ignore | ||
| * import { isPortAvailable } from "@std/net/unstable-is-port-available"; | ||
| * | ||
| * if (isPortAvailable(8080)) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example demonstrates exactly the pattern the A warning followed by a worked example of the thing being warned about mostly teaches the example. People copy code blocks; they skim prose. I'd drop this example entirely, or replace it with the safe shape ( |
||
| * Deno.serve({ port: 8080 }, () => new Response("Hello, world!")); | ||
| * } | ||
| * ``` | ||
| */ | ||
| export function isPortAvailable( | ||
| port: number, | ||
| options?: IsPortAvailableOptions, | ||
| ): boolean { | ||
| try { | ||
| using _listener = Deno.listen( | ||
| options?.hostname !== undefined | ||
| ? { port, hostname: options.hostname } | ||
| : { port }, | ||
| ); | ||
| return true; | ||
| } catch (e) { | ||
| if (e instanceof Deno.errors.AddrInUse) { | ||
| return false; | ||
| } | ||
| throw e; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Copyright 2018-2026 the Deno authors. MIT license. | ||
|
|
||
| import { isPortAvailable } from "./unstable_is_port_available.ts"; | ||
| import { assert, assertFalse, assertThrows } from "@std/assert"; | ||
| import { stub } from "@std/testing/mock"; | ||
|
|
||
| Deno.test("isPortAvailable() returns true for an available port", () => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The A test with real signal: listen on port |
||
| assert(isPortAvailable(0)); | ||
| }); | ||
|
|
||
| Deno.test("isPortAvailable() returns false for a port already in use", () => { | ||
| using listener = Deno.listen({ port: 0 }); | ||
| const { port } = listener.addr; | ||
| assertFalse(isPortAvailable(port)); | ||
| }); | ||
|
|
||
| Deno.test("isPortAvailable() respects the hostname option", () => { | ||
| using listener = Deno.listen({ port: 0, hostname: "127.0.0.1" }); | ||
| const { port } = listener.addr; | ||
| assertFalse(isPortAvailable(port, { hostname: "127.0.0.1" })); | ||
| }); | ||
|
|
||
| Deno.test("isPortAvailable() rethrows errors other than AddrInUse", () => { | ||
| using _listen = stub(Deno, "listen", () => { | ||
| throw new Error("boom"); | ||
| }); | ||
| assertThrows(() => isPortAvailable(0), Error, "boom"); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth documenting here that availability is per-interface, not global — the current wording reads as though a port is simply free or not.
With the default
0.0.0.0, a process already listening on127.0.0.1:8080makes this returnfalseon Linux and macOS, but the same situation can returntrueon Windows, which has different address-conflict rules. So the same code gives different answers per platform, and neither answer is wrong.Related:
localhostresolving to::1versus127.0.0.1will also change the result, which matters as soon as someone passeshostname: "localhost".Also worth saying plainly that this is TCP-only. There's no
transportoption and UDP occupies a separate port namespace, soisPortAvailable(53)says nothing about a DNS server on UDP 53 — the name doesn't hint at that restriction, only the prose does.