Skip to content

Commit a45c310

Browse files
committed
feat(router-worker): record canonical routing differences
1 parent f885467 commit a45c310

3 files changed

Lines changed: 64 additions & 0 deletions

File tree

packages/workers-shared/router-worker/src/analytics.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ type Data = {
3939
userWorkerFreeTierLimiting?: boolean;
4040
// double9 - The time it takes for the request to be handed off the Asset Worker or user Worker in milliseconds
4141
timeToDispatch?: number;
42+
// double10 - Canonicalization performed while in shadow mode
43+
pathNormalization?: number;
44+
// double11 - Canonical routing decision differs from current behavior
45+
pathNormalizationDifference?: number;
4246

4347
// -- Blobs --
4448
// blob1 - Hostname of the request
@@ -103,6 +107,8 @@ export class Analytics {
103107
this.data.abuseMitigationBlocked ? 1 : 0, // double7
104108
this.data.userWorkerFreeTierLimiting ? 1 : 0, // double8
105109
this.data.timeToDispatch ?? -1, // double9
110+
this.data.pathNormalization ?? 0, // double10
111+
this.data.pathNormalizationDifference ?? 0, // double11
106112
],
107113
blobs: [
108114
this.data.hostname?.substring(0, 256), // blob1 - trim to 256 bytes

packages/workers-shared/router-worker/src/worker.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { WorkerEntrypoint } from "cloudflare:workers";
2+
import { canonicalizePath } from "../../asset-worker/src/utils/canonical-path";
23
import { generateStaticRoutingRuleMatcher } from "../../asset-worker/src/utils/rules-engine";
34
import { PerformanceTimer } from "../../utils/performance";
45
import { TemporaryRedirectResponse } from "../../utils/responses";
@@ -281,6 +282,26 @@ export class RouterInnerEntrypoint extends WorkerEntrypoint<Env> {
281282
};
282283

283284
if (config.static_routing) {
285+
// Shadow-only: raw-path dispatch below remains authoritative.
286+
const canonicalPath = canonicalizePath(url.pathname);
287+
if (canonicalPath.routingPath !== url.pathname) {
288+
const currentTarget = getStaticRoutingTarget(
289+
request,
290+
config.static_routing
291+
);
292+
const canonicalTarget = getStaticRoutingTarget(
293+
request,
294+
config.static_routing,
295+
canonicalPath.routingPath
296+
);
297+
if (currentTarget !== canonicalTarget) {
298+
analytics.setData({
299+
pathNormalization: canonicalPath.normalization,
300+
pathNormalizationDifference: 1,
301+
});
302+
}
303+
}
304+
284305
switch (getStaticRoutingTarget(request, config.static_routing)) {
285306
case "asset":
286307
// direct to asset worker

packages/workers-shared/router-worker/tests/index.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import DefaultRouterEntrypoint, {
55
RouterInnerEntrypoint,
66
RouterOuterEntrypoint,
77
} from "../src/worker";
8+
import type { ReadyAnalyticsEvent } from "../src/types";
89
import type { Env } from "../src/worker";
910

1011
async function fetchFromInnerEntrypoint(
@@ -200,6 +201,42 @@ describe("inner entrypoint unit tests", () => {
200201
expect(await response.text()).toEqual("hello from user worker");
201202
});
202203

204+
it("records a canonical routing difference without changing dispatch", async ({
205+
expect,
206+
}) => {
207+
const request = new Request("https://example.com/docs+draft");
208+
const ctx = createExecutionContext();
209+
const logEvent = vi.fn((_event: ReadyAnalyticsEvent) => undefined);
210+
const env = {
211+
CONFIG: {
212+
has_user_worker: true,
213+
static_routing: { user_worker: ["/docs%2Bdraft"] },
214+
},
215+
ANALYTICS: { logEvent },
216+
USER_WORKER: {
217+
async fetch(_request: Request): Promise<Response> {
218+
return new Response("user worker");
219+
},
220+
},
221+
ASSET_WORKER: {
222+
async fetch(_request: Request): Promise<Response> {
223+
return new Response("asset worker");
224+
},
225+
async unstable_canFetch(_request: Request): Promise<boolean> {
226+
return true;
227+
},
228+
},
229+
} as unknown as Env;
230+
231+
const response = await fetchFromInnerEntrypoint(request, env, ctx);
232+
233+
expect(await response.text()).toBe("asset worker");
234+
expect(logEvent).toHaveBeenCalledOnce();
235+
const event = logEvent.mock.calls[0]?.[0];
236+
expect(event?.doubles?.[9]).toBe(8);
237+
expect(event?.doubles?.[10]).toBe(1);
238+
});
239+
203240
it("returns fetch from asset worker when static_routing asset_worker rule matches", async ({
204241
expect,
205242
}) => {

0 commit comments

Comments
 (0)