Skip to content

Commit

Permalink
Add support for ipv6 host
Browse files Browse the repository at this point in the history
  • Loading branch information
jspspike committed Aug 14, 2023
1 parent cf080a8 commit 3330ce1
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 20 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/miniflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"zod": "^3.20.6"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20230419.0",
"@cloudflare/workers-types": "^4.20230807.0",
"@types/better-sqlite3": "^7.6.2",
"@types/debug": "^4.1.7",
"@types/estree": "^1.0.0",
Expand Down
18 changes: 18 additions & 0 deletions packages/miniflare/src/http/helpers.ts
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions packages/miniflare/src/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
37 changes: 33 additions & 4 deletions packages/miniflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
configureEntrySocket,
coupleWebSocket,
fetch,
getAccessibleHosts,
} from "./http";
import {
DispatchFetch,
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -745,6 +753,10 @@ export class Miniflare {
port: number,
hostname: string
): Promise<StoppableServer> {
if (hostname === "*") {
hostname = "::";
}

return new Promise((resolve) => {
const server = stoppable(
http.createServer(this.#handleLoopback),
Expand Down Expand Up @@ -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();
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/miniflare/src/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
25 changes: 23 additions & 2 deletions packages/miniflare/src/workers/core/entry.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,29 @@ async function handleQueue(
const flattened = await request.json<number | unknown[]>();
const messages = unflatten(flattened, structuredSerializableRevivers);
const queueResponse = await service.queue(queueName, messages);
(queueResponse as QueueResponse & { time: number }).time =
(queueResponse as FetcherQueueResult & { time: number }).time =
Date.now() - startTime;
return Response.json(queueResponse);
}

async function handleScheduled(
params: URLSearchParams,
service: Fetcher
): Promise<Response> {
const time = params.get("time");
const scheduledTime = time ? new Date(parseInt(time)) : undefined;
const cron = params.get("cron") ?? undefined;

const result = await service.scheduled({
scheduledTime,
cron,
});

return new Response(result.outcome, {
status: result.outcome === "ok" ? 200 : 500,
});
}

export default <ExportedHandler<Env>>{
async fetch(request, env, ctx) {
const startTime = Date.now();
Expand All @@ -183,11 +201,14 @@ export default <ExportedHandler<Env>>{

try {
const customEvent = request.headers.get(CoreHeaders.CUSTOM_EVENT);
// TODO(soon): support scheduled events, requires support from workerd
if (customEvent === "queue") {
return await handleQueue(request, url, service, startTime);
}

if (url.pathname === "/cdn-cgi/mf/scheduled") {
return await handleScheduled(url.searchParams, service);
}

let response = await service.fetch(request);
if (!isDispatchFetch) {
response = await maybePrettifyError(request, response, env);
Expand Down
54 changes: 54 additions & 0 deletions packages/miniflare/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,3 +498,57 @@ test("Miniflare: Accepts https requests", async (t) => {

t.assert(log.logs[0][1].startsWith("Ready on https://"));
});

test("Miniflare: Manually triggered scheduled events", async (t) => {
const log = new TestLog(t);

const mf = new Miniflare({
log,
modules: true,
script: `
let scheduledRun = false;
export default {
fetch() {
return new Response(scheduledRun);
},
scheduled() {
scheduledRun = true;
}
}`,
});

let res = await mf.dispatchFetch("http://localhost");
t.is(await res.text(), "false");

res = await mf.dispatchFetch("http://localhost/cdn-cgi/mf/scheduled");
t.is(await res.text(), "ok");

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);
});
3 changes: 0 additions & 3 deletions packages/miniflare/test/plugins/d1/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ export class TestD1Database implements D1Database {
return this[kSend]("/batch", statements);
}

// @ts-expect-error this function should return a `Promise<D1ExecResult>`,
// not a `Promise<D1Result<T>>`, `@cloudflare/workers-types` is wrong here
// TODO(now): fix in `@cloudflare/workers-types`
async exec(query: string): Promise<D1ExecResult> {
return this[kSend]("/exec", query);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/miniflare/test/plugins/d1/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export default (binding: string, WORKER_PATH: string) => {
const select = await db.prepare(`SELECT * FROM ${tableColours}`);
let result: ColourRow | null = await select.first<ColourRow>();
t.deepEqual(result, { id: 1, name: "red", rgb: 0xff0000 });
let id = await select.first<number>("id");
let id: number | null = await select.first<number>("id");
t.is(id, 1);

// Check with multiple statements (should only match on first statement)
Expand Down

0 comments on commit 3330ce1

Please sign in to comment.