Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 0b7a470

Browse files
authored
Merge pull request #650 from cloudflare/jspspike/localhost
Add support for ipv6 host
2 parents 3f5ab99 + d61b3f7 commit 0b7a470

File tree

5 files changed

+80
-5
lines changed

5 files changed

+80
-5
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { networkInterfaces } from "os";
2+
3+
export function getAccessibleHosts(ipv4Only = false): string[] {
4+
const hosts: string[] = [];
5+
Object.values(networkInterfaces()).forEach((net) => {
6+
net?.forEach(({ family, address }) => {
7+
// The `family` property being numeric was reverted in Node 18.2
8+
// https://github.com/nodejs/node/issues/43014
9+
// @ts-expect-error the `family` property is numeric as of Node.js 18.0.0
10+
if (family === "IPv4" || family === 4) {
11+
hosts.push(address);
12+
} else if (!ipv4Only && (family === "IPv6" || family === 6)) {
13+
hosts.push(`[${address}]`);
14+
}
15+
});
16+
});
17+
return hosts;
18+
}

packages/miniflare/src/http/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./request";
33
export * from "./response";
44
export * from "./websocket";
55
export * from "./server";
6+
export * from "./helpers";
67

78
export { File, FormData, Headers } from "undici";
89
export type {

packages/miniflare/src/index.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
configureEntrySocket,
3232
coupleWebSocket,
3333
fetch,
34+
getAccessibleHosts,
3435
} from "./http";
3536
import {
3637
D1_PLUGIN_NAME,
@@ -518,7 +519,14 @@ export class Miniflare {
518519
this.#timers = this.#sharedOpts.core.timers ?? defaultTimers;
519520
this.#host = this.#sharedOpts.core.host ?? "127.0.0.1";
520521
this.#accessibleHost =
521-
this.#host === "*" || this.#host === "0.0.0.0" ? "127.0.0.1" : this.#host;
522+
this.#host === "*" || this.#host === "0.0.0.0" || this.#host === "::"
523+
? "127.0.0.1"
524+
: this.#host;
525+
526+
if (net.isIPv6(this.#accessibleHost)) {
527+
this.#accessibleHost = `[${this.#accessibleHost}]`;
528+
}
529+
522530
this.#initPlugins();
523531

524532
this.#liveReloadServer = new WebSocketServer({ noServer: true });
@@ -605,7 +613,7 @@ export class Miniflare {
605613
// Start runtime
606614
const port = this.#sharedOpts.core.port ?? 0;
607615
const opts: RuntimeOptions = {
608-
entryHost: this.#host,
616+
entryHost: net.isIPv6(this.#host) ? `[${this.#host}]` : this.#host,
609617
entryPort: port,
610618
loopbackPort: this.#loopbackPort,
611619
inspectorPort: this.#sharedOpts.core.inspectorPort,
@@ -702,7 +710,7 @@ export class Miniflare {
702710
// Extract original URL passed to `fetch`
703711
const url = new URL(
704712
headers.get(CoreHeaders.ORIGINAL_URL) ?? req.url ?? "",
705-
"http://127.0.0.1"
713+
"http://localhost"
706714
);
707715
headers.delete(CoreHeaders.ORIGINAL_URL);
708716

@@ -825,6 +833,10 @@ export class Miniflare {
825833
port: number,
826834
hostname: string
827835
): Promise<StoppableServer> {
836+
if (hostname === "*") {
837+
hostname = "::";
838+
}
839+
828840
return new Promise((resolve) => {
829841
const server = stoppable(
830842
http.createServer(this.#handleLoopback),
@@ -1018,7 +1030,24 @@ export class Miniflare {
10181030
if (!this.#runtimeMutex.hasWaiting) {
10191031
// Only log and trigger reload if there aren't pending updates
10201032
const ready = initial ? "Ready" : "Updated and ready";
1021-
this.#log.info(`${ready} on ${this.#runtimeEntryURL}`);
1033+
const host = net.isIPv6(this.#host) ? `[${this.#host}]` : this.#host;
1034+
this.#log.info(
1035+
`${ready} on ${secure ? "https" : "http"}://${host}:${maybePort} `
1036+
);
1037+
1038+
let hosts: string[];
1039+
if (this.#host === "::" || this.#host === "*") {
1040+
hosts = getAccessibleHosts(false);
1041+
} else if (this.#host === "0.0.0.0") {
1042+
hosts = getAccessibleHosts(true);
1043+
} else {
1044+
hosts = [];
1045+
}
1046+
1047+
for (const h of hosts) {
1048+
this.#log.info(`- ${secure ? "https" : "http"}://${h}:${maybePort}`);
1049+
}
1050+
10221051
this.#handleReload();
10231052
}
10241053
}

packages/miniflare/src/runtime/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class Runtime {
9595
];
9696
if (this.opts.inspectorPort !== undefined) {
9797
// Required to enable the V8 inspector
98-
args.push(`--inspector-addr=127.0.0.1:${this.opts.inspectorPort}`);
98+
args.push(`--inspector-addr=localhost:${this.opts.inspectorPort}`);
9999
}
100100
if (this.opts.verbose) {
101101
args.push("--verbose");

packages/miniflare/test/index.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,33 @@ test("Miniflare: manually triggered scheduled events", async (t) => {
557557
t.is(await res.text(), "true");
558558
});
559559

560+
test("Miniflare: Listens on ipv6", async (t) => {
561+
const log = new TestLog(t);
562+
563+
const mf = new Miniflare({
564+
log,
565+
modules: true,
566+
host: "*",
567+
script: `export default {
568+
fetch() {
569+
return new Response("Hello world");
570+
}
571+
}`,
572+
});
573+
t.teardown(() => mf.dispose());
574+
575+
const url = await mf.ready;
576+
577+
let response = await fetch(`http://localhost:${url.port}`);
578+
t.true(response.ok);
579+
580+
response = await fetch(`http://[::1]:${url.port}`);
581+
t.true(response.ok);
582+
583+
response = await fetch(`http://127.0.0.1:${url.port}`);
584+
t.true(response.ok);
585+
});
586+
560587
queuesTest("Miniflare: getBindings() returns all bindings", async (t) => {
561588
const tmp = await useTmp(t);
562589
const blobPath = path.join(tmp, "blob.txt");

0 commit comments

Comments
 (0)