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

Commit 20d4dce

Browse files
committed
Route /cdn-cgi/mf/scheduled to mounts based on host, closes #163
1 parent 389b13f commit 20d4dce

File tree

4 files changed

+85
-12
lines changed

4 files changed

+85
-12
lines changed

packages/core/src/index.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,18 @@ export class MiniflareCore<
991991
return this.#mounts!.get(name)!;
992992
}
993993

994+
#matchMount(url: URL): MiniflareCore<Plugins> | undefined {
995+
if (this.#mounts?.size) {
996+
const mountMatch = this.#router!.match(url);
997+
const name = this.#instances!.CorePlugin.name;
998+
// If there was a match, and it isn't the current (parent) worker,
999+
// forward the request to the matching mount instead
1000+
if (mountMatch !== null && mountMatch !== name) {
1001+
return this.#mounts.get(mountMatch);
1002+
}
1003+
}
1004+
}
1005+
9941006
async dispatchFetch<WaitUntil extends any[] = unknown[]>(
9951007
input: RequestInfo,
9961008
init?: RequestInit
@@ -1003,16 +1015,8 @@ export class MiniflareCore<
10031015
const url = new URL(request.url);
10041016

10051017
// Forward to matching mount if any
1006-
if (this.#mounts?.size) {
1007-
const mountMatch = this.#router!.match(url);
1008-
const name = this.#instances!.CorePlugin.name;
1009-
// If there was a match, and it isn't the current (parent) worker,
1010-
// forward the request to the matching mount instead
1011-
if (mountMatch !== null && mountMatch !== name) {
1012-
const mount = this.#mounts.get(mountMatch);
1013-
if (mount) return mount.dispatchFetch(request);
1014-
}
1015-
}
1018+
const mount = this.#matchMount(url);
1019+
if (mount) return mount.dispatchFetch(request);
10161020

10171021
// If upstream set, and the request URL doesn't begin with it, rewrite it
10181022
// so fetching the incoming request gets a response from the upstream
@@ -1065,9 +1069,19 @@ export class MiniflareCore<
10651069

10661070
async dispatchScheduled<WaitUntil extends any[] = unknown[]>(
10671071
scheduledTime?: number,
1068-
cron?: string
1072+
cron?: string,
1073+
url?: string | URL
10691074
): Promise<WaitUntil> {
10701075
await this.#initPromise;
1076+
1077+
// Forward to matching mount if any (this is primarily intended for the
1078+
// "/cdn-cgi/mf/scheduled" route)
1079+
if (url !== undefined) {
1080+
if (typeof url === "string") url = new URL(url);
1081+
const mount = this.#matchMount(url);
1082+
if (mount) return mount.dispatchScheduled(scheduledTime, cron);
1083+
}
1084+
10711085
const globalScope = this.#globalScope;
10721086
// Each fetch gets its own context (e.g. 50 subrequests).
10731087
// Start a new pipeline too.

packages/core/test/index.mounts.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,40 @@ test("MiniflareCore: uses original protocol and host when matching mount routes"
480480
t.is(body, "https://example.com/a:custom.mf");
481481
});
482482

483+
test("MiniflareCore: dispatches scheduled event to mount", async (t) => {
484+
const mf = useMiniflare(
485+
{},
486+
{
487+
modules: true,
488+
script: `export default {
489+
scheduled(controller, env, ctx) {
490+
ctx.waitUntil("parent");
491+
ctx.waitUntil(controller.scheduledTime);
492+
ctx.waitUntil(controller.cron);
493+
}
494+
}`,
495+
mounts: {
496+
a: {
497+
routes: ["https://test.mf/*"],
498+
script: `addEventListener("scheduled", (event) => {
499+
event.waitUntil("mount");
500+
event.waitUntil(event.scheduledTime);
501+
event.waitUntil(event.cron);
502+
})`,
503+
},
504+
},
505+
}
506+
);
507+
let waitUntil = await mf.dispatchScheduled(1000, "* * * * *");
508+
t.deepEqual(waitUntil, ["parent", 1000, "* * * * *"]);
509+
waitUntil = await mf.dispatchScheduled(
510+
1000,
511+
"* * * * *",
512+
"https://test.mf/cdn-cgi/mf/scheduled"
513+
);
514+
t.deepEqual(waitUntil, ["mount", 1000, "* * * * *"]);
515+
});
516+
483517
// Shared storage persistence tests
484518
type PersistOptions = Pick<
485519
MiniflareOptions,

packages/http-server/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ export function createRequestListener<Plugins extends HTTPPluginSignatures>(
154154
const cron = url.searchParams.get("cron");
155155
waitUntil = mf.dispatchScheduled(
156156
time ? parseInt(time) : undefined,
157-
cron ?? undefined
157+
cron ?? undefined,
158+
url
158159
);
159160
status = 200;
160161
} else {

packages/http-server/test/index.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,30 @@ test("createRequestListener: handles scheduled event trigger over http", async (
348348
t.is(events[2].scheduledTime, 1000);
349349
t.is(events[2].cron, "* * * * *");
350350
});
351+
test("createRequestListener: handles scheduled event triggers over http for mounts", async (t) => {
352+
const events: string[] = [];
353+
const mf = useMiniflare(
354+
{ HTTPPlugin, BindingsPlugin },
355+
{
356+
globals: { events },
357+
script: `addEventListener("scheduled", () => events.push("parent"))`,
358+
mounts: {
359+
a: {
360+
routes: ["http://mount.mf/*"],
361+
globals: { events },
362+
script: `addEventListener("scheduled", () => events.push("child"))`,
363+
},
364+
},
365+
}
366+
);
367+
const port = await listen(t, http.createServer(createRequestListener(mf)));
368+
369+
await request(port, "/cdn-cgi/mf/scheduled");
370+
t.deepEqual(events, ["parent"]);
371+
372+
await request(port, "/cdn-cgi/mf/scheduled", { host: "mount.mf" });
373+
t.deepEqual(events, ["parent", "child"]);
374+
});
351375
test("createRequestListener: displays appropriately-formatted error page", async (t) => {
352376
const log = new TestLog();
353377
log.error = (message) =>

0 commit comments

Comments
 (0)