diff --git a/packages/miniflare/src/http/helpers.ts b/packages/miniflare/src/http/helpers.ts new file mode 100644 index 000000000..4bf15c849 --- /dev/null +++ b/packages/miniflare/src/http/helpers.ts @@ -0,0 +1,18 @@ +import { networkInterfaces } from "os"; + +export function getAccessibleHosts(ipv4Only = false): string[] { + const hosts: string[] = []; + Object.values(networkInterfaces()).forEach((net) => { + net?.forEach(({ family, address }) => { + // The `family` property being numeric was reverted in Node 18.2 + // https://github.com/nodejs/node/issues/43014 + // @ts-expect-error the `family` property is numeric as of Node.js 18.0.0 + if (family === "IPv4" || family === 4) { + hosts.push(address); + } else if (!ipv4Only && (family === "IPv6" || family === 6)) { + hosts.push(`[${address}]`); + } + }); + }); + return hosts; +} diff --git a/packages/miniflare/src/http/index.ts b/packages/miniflare/src/http/index.ts index fb860b150..1d9b0796c 100644 --- a/packages/miniflare/src/http/index.ts +++ b/packages/miniflare/src/http/index.ts @@ -3,6 +3,7 @@ export * from "./request"; export * from "./response"; export * from "./websocket"; export * from "./server"; +export * from "./helpers"; export { File, FormData, Headers } from "undici"; export type { diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index 2b8027ec6..97cfda3ca 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -24,6 +24,7 @@ import { configureEntrySocket, coupleWebSocket, fetch, + getAccessibleHosts, } from "./http"; import { DispatchFetch, @@ -440,7 +441,14 @@ export class Miniflare { this.#timers = this.#sharedOpts.core.timers ?? defaultTimers; this.#host = this.#sharedOpts.core.host ?? "127.0.0.1"; this.#accessibleHost = - this.#host === "*" || this.#host === "0.0.0.0" ? "127.0.0.1" : this.#host; + this.#host === "*" || this.#host === "0.0.0.0" || this.#host === "::" + ? "127.0.0.1" + : this.#host; + + if (net.isIPv6(this.#accessibleHost)) { + this.#accessibleHost = `[${this.#accessibleHost}]`; + } + this.#initPlugins(); this.#liveReloadServer = new WebSocketServer({ noServer: true }); @@ -527,7 +535,7 @@ export class Miniflare { // Start runtime const port = this.#sharedOpts.core.port ?? 0; const opts: RuntimeOptions = { - entryHost: this.#host, + entryHost: net.isIPv6(this.#host) ? `[${this.#host}]` : this.#host, entryPort: port, loopbackPort: this.#loopbackPort, inspectorPort: this.#sharedOpts.core.inspectorPort, @@ -624,7 +632,7 @@ export class Miniflare { // Extract original URL passed to `fetch` const url = new URL( headers.get(CoreHeaders.ORIGINAL_URL) ?? req.url ?? "", - "http://127.0.0.1" + "http://localhost" ); headers.delete(CoreHeaders.ORIGINAL_URL); @@ -745,6 +753,10 @@ export class Miniflare { port: number, hostname: string ): Promise { + if (hostname === "*") { + hostname = "::"; + } + return new Promise((resolve) => { const server = stoppable( http.createServer(this.#handleLoopback), @@ -877,7 +889,24 @@ export class Miniflare { if (!this.#runtimeMutex.hasWaiting) { // Only log and trigger reload if there aren't pending updates const ready = initial ? "Ready" : "Updated and ready"; - this.#log.info(`${ready} on ${this.#runtimeEntryURL}`); + const host = net.isIPv6(this.#host) ? `[${this.#host}]` : this.#host; + this.#log.info( + `${ready} on ${secure ? "https" : "http"}://${host}:${maybePort} ` + ); + + let hosts: string[]; + if (this.#host === "::" || this.#host === "*") { + hosts = getAccessibleHosts(false); + } else if (this.#host === "0.0.0.0") { + hosts = getAccessibleHosts(true); + } else { + hosts = []; + } + + for (const h of hosts) { + this.#log.info(`- ${secure ? "https" : "http"}://${h}:${maybePort}`); + } + this.#handleReload(); } } diff --git a/packages/miniflare/src/runtime/index.ts b/packages/miniflare/src/runtime/index.ts index 8467c22ca..76e1cbe13 100644 --- a/packages/miniflare/src/runtime/index.ts +++ b/packages/miniflare/src/runtime/index.ts @@ -95,7 +95,7 @@ export class Runtime { ]; if (this.opts.inspectorPort !== undefined) { // Required to enable the V8 inspector - args.push(`--inspector-addr=127.0.0.1:${this.opts.inspectorPort}`); + args.push(`--inspector-addr=localhost:${this.opts.inspectorPort}`); } if (this.opts.verbose) { args.push("--verbose"); diff --git a/packages/miniflare/test/index.spec.ts b/packages/miniflare/test/index.spec.ts index 9d89fc967..f355a35bd 100644 --- a/packages/miniflare/test/index.spec.ts +++ b/packages/miniflare/test/index.spec.ts @@ -526,3 +526,29 @@ test("Miniflare: Manually triggered scheduled events", async (t) => { res = await mf.dispatchFetch("http://localhost"); t.is(await res.text(), "true"); }); + +test("Miniflare: Listens on ipv6", async (t) => { + const log = new TestLog(t); + + const mf = new Miniflare({ + log, + modules: true, + host: "*", + script: `export default { + fetch() { + return new Response("Hello world"); + } + }`, + }); + + const url = await mf.ready; + + let response = await fetch(`http://localhost:${url.port}`); + t.true(response.ok); + + response = await fetch(`http://[::1]:${url.port}`); + t.true(response.ok); + + response = await fetch(`http://127.0.0.1:${url.port}`); + t.true(response.ok); +});