Skip to content

Commit 6072eb8

Browse files
WC-3583 Route requests based on static routing when present
- When a request matches a static routing "exclude" rule, it's directly forwarded to the asset worker. - When a request matches a static routing "include" rule, it's directly forwarded to the user worker. - Otherwise, previous behavior takes over This also adds a new analytics field (double6) for what routing decision was made
1 parent ebf6096 commit 6072eb8

File tree

3 files changed

+233
-1
lines changed

3 files changed

+233
-1
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import type { ReadyAnalytics } from "./types";
33
// This will allow us to make breaking changes to the analytic schema
44
const VERSION = 1;
55

6+
export enum STATIC_ROUTING_DECISION {
7+
NOT_PROVIDED = 0,
8+
NOT_ROUTED = 1,
9+
EXCLUDE = 2,
10+
INCLUDE = 3,
11+
}
12+
613
export enum DISPATCH_TYPE {
714
ASSETS = "asset",
815
WORKER = "worker",
@@ -25,6 +32,8 @@ type Data = {
2532
coloTier?: number;
2633
// double5 - Run user worker ahead of assets
2734
userWorkerAhead?: boolean;
35+
// double6 - Routing performed based on the _routes.json (if provided)
36+
staticRoutingDecision?: STATIC_ROUTING_DECISION;
2837

2938
// -- Blobs --
3039
// blob1 - Hostname of the request
@@ -79,6 +88,7 @@ export class Analytics {
7988
this.data.userWorkerAhead === undefined // double5
8089
? -1
8190
: Number(this.data.userWorkerAhead),
91+
this.data.staticRoutingDecision ?? STATIC_ROUTING_DECISION.NOT_PROVIDED, // double6
8292
],
8393
blobs: [
8494
this.data.hostname?.substring(0, 256), // blob1 - trim to 256 bytes

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

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { generateStaticRoutingRuleMatcher } from "../../asset-worker/src/utils/rules-engine";
12
import { PerformanceTimer } from "../../utils/performance";
23
import { setupSentry } from "../../utils/sentry";
34
import { mockJaegerBinding } from "../../utils/tracing";
4-
import { Analytics, DISPATCH_TYPE } from "./analytics";
5+
import { Analytics, DISPATCH_TYPE, STATIC_ROUTING_DECISION } from "./analytics";
56
import { applyConfigurationDefaults } from "./configuration";
67
import type AssetWorker from "../../asset-worker";
78
import type {
@@ -53,6 +54,7 @@ export default {
5354
env.CONFIG?.script_id
5455
);
5556

57+
const hasStaticRouting = env.CONFIG.static_routing !== undefined;
5658
const config = applyConfigurationDefaults(env.CONFIG);
5759

5860
const url = new URL(request.url);
@@ -75,6 +77,69 @@ export default {
7577

7678
const maybeSecondRequest = request.clone();
7779

80+
if (config.static_routing) {
81+
// evaluate "exclude" rules
82+
const excludeRulesMatcher = generateStaticRoutingRuleMatcher(
83+
config.static_routing.exclude ?? []
84+
);
85+
if (
86+
excludeRulesMatcher({
87+
request,
88+
})
89+
) {
90+
// direct to asset worker
91+
analytics.setData({
92+
dispatchtype: DISPATCH_TYPE.ASSETS,
93+
staticRoutingDecision: STATIC_ROUTING_DECISION.EXCLUDE,
94+
});
95+
return await env.JAEGER.enterSpan("dispatch_assets", async (span) => {
96+
span.setTags({
97+
hasUserWorker: config.has_user_worker,
98+
asset: "static_routing",
99+
dispatchType: DISPATCH_TYPE.ASSETS,
100+
});
101+
102+
return env.ASSET_WORKER.fetch(maybeSecondRequest);
103+
});
104+
}
105+
// evaluate "include" rules
106+
const includeRulesMatcher = generateStaticRoutingRuleMatcher(
107+
config.static_routing.include
108+
);
109+
if (
110+
includeRulesMatcher({
111+
request,
112+
})
113+
) {
114+
if (!config.has_user_worker) {
115+
throw new Error(
116+
"Fetch for user worker without having a user worker binding"
117+
);
118+
}
119+
// direct to user worker
120+
analytics.setData({
121+
dispatchtype: DISPATCH_TYPE.WORKER,
122+
staticRoutingDecision: STATIC_ROUTING_DECISION.INCLUDE,
123+
});
124+
return await env.JAEGER.enterSpan("dispatch_worker", async (span) => {
125+
span.setTags({
126+
hasUserWorker: true,
127+
asset: "static_routing",
128+
dispatchType: DISPATCH_TYPE.WORKER,
129+
});
130+
131+
userWorkerInvocation = true;
132+
return env.USER_WORKER.fetch(maybeSecondRequest);
133+
});
134+
}
135+
136+
analytics.setData({
137+
staticRoutingDecision: hasStaticRouting
138+
? STATIC_ROUTING_DECISION.NOT_ROUTED
139+
: STATIC_ROUTING_DECISION.NOT_PROVIDED,
140+
});
141+
}
142+
78143
// User's configuration indicates they want user-Worker to run ahead of any
79144
// assets. Do not provide any fallback logic.
80145
if (config.invoke_user_worker_ahead_of_assets) {

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

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,161 @@ describe("unit tests", async () => {
9494
const response = await worker.fetch(request, env, ctx);
9595
expect(await response.text()).toEqual("hello from asset worker");
9696
});
97+
98+
it("it returns fetch from user worker when static_routing include rule matches", async () => {
99+
const request = new Request("https://example.com/api/includeme");
100+
const ctx = createExecutionContext();
101+
102+
const env = {
103+
CONFIG: {
104+
has_user_worker: true,
105+
static_routing: {
106+
version: 1,
107+
include: ["/api/*"],
108+
},
109+
},
110+
USER_WORKER: {
111+
async fetch(_: Request): Promise<Response> {
112+
return new Response("hello from user worker");
113+
},
114+
},
115+
ASSET_WORKER: {
116+
async fetch(_: Request): Promise<Response> {
117+
return new Response("hello from asset worker");
118+
},
119+
async unstable_canFetch(_: Request): Promise<boolean> {
120+
return true;
121+
},
122+
},
123+
} as Env;
124+
125+
const response = await worker.fetch(request, env, ctx);
126+
expect(await response.text()).toEqual("hello from user worker");
127+
});
128+
129+
it("it returns fetch from asset worker when static_routing exclude rule matches", async () => {
130+
const request = new Request("https://example.com/api/excludeme");
131+
const ctx = createExecutionContext();
132+
133+
const env = {
134+
CONFIG: {
135+
has_user_worker: true,
136+
static_routing: {
137+
version: 1,
138+
include: ["/api/includeme"],
139+
exclude: ["/api/excludeme"],
140+
},
141+
},
142+
USER_WORKER: {
143+
async fetch(_: Request): Promise<Response> {
144+
return new Response("hello from user worker");
145+
},
146+
},
147+
ASSET_WORKER: {
148+
async fetch(_: Request): Promise<Response> {
149+
return new Response("hello from asset worker");
150+
},
151+
async unstable_canFetch(_: Request): Promise<boolean> {
152+
return true;
153+
},
154+
},
155+
} as Env;
156+
157+
const response = await worker.fetch(request, env, ctx);
158+
expect(await response.text()).toEqual("hello from asset worker");
159+
});
160+
161+
it("it returns fetch from asset worker when static_routing exclude and include rule matches", async () => {
162+
const request = new Request("https://example.com/api/excludeme");
163+
const ctx = createExecutionContext();
164+
165+
const env = {
166+
CONFIG: {
167+
has_user_worker: true,
168+
static_routing: {
169+
version: 1,
170+
include: ["/api/*"],
171+
exclude: ["/api/excludeme"],
172+
},
173+
},
174+
USER_WORKER: {
175+
async fetch(_: Request): Promise<Response> {
176+
return new Response("hello from user worker");
177+
},
178+
},
179+
ASSET_WORKER: {
180+
async fetch(_: Request): Promise<Response> {
181+
return new Response("hello from asset worker");
182+
},
183+
async unstable_canFetch(_: Request): Promise<boolean> {
184+
return true;
185+
},
186+
},
187+
} as Env;
188+
189+
const response = await worker.fetch(request, env, ctx);
190+
expect(await response.text()).toEqual("hello from asset worker");
191+
});
192+
193+
it("it returns fetch from asset worker when no static_routing rule matches but asset exists", async () => {
194+
const request = new Request("https://example.com/someasset");
195+
const ctx = createExecutionContext();
196+
197+
const env = {
198+
CONFIG: {
199+
has_user_worker: true,
200+
static_routing: {
201+
version: 1,
202+
include: ["/api/*"],
203+
},
204+
},
205+
USER_WORKER: {
206+
async fetch(_: Request): Promise<Response> {
207+
return new Response("hello from user worker");
208+
},
209+
},
210+
ASSET_WORKER: {
211+
async fetch(_: Request): Promise<Response> {
212+
return new Response("hello from asset worker");
213+
},
214+
async unstable_canFetch(_: Request): Promise<boolean> {
215+
return true;
216+
},
217+
},
218+
} as Env;
219+
220+
const response = await worker.fetch(request, env, ctx);
221+
expect(await response.text()).toEqual("hello from asset worker");
222+
});
223+
224+
it("it returns fetch from user worker when no static_routing rule matches and no asset exists", async () => {
225+
const request = new Request("https://example.com/somemissingasset");
226+
const ctx = createExecutionContext();
227+
228+
const env = {
229+
CONFIG: {
230+
has_user_worker: true,
231+
static_routing: {
232+
version: 1,
233+
include: ["/api/*"],
234+
},
235+
},
236+
USER_WORKER: {
237+
async fetch(_: Request): Promise<Response> {
238+
return new Response("hello from user worker");
239+
},
240+
},
241+
ASSET_WORKER: {
242+
async fetch(_: Request): Promise<Response> {
243+
return new Response("hello from asset worker");
244+
},
245+
async unstable_canFetch(_: Request): Promise<boolean> {
246+
return false;
247+
},
248+
},
249+
} as Env;
250+
251+
const response = await worker.fetch(request, env, ctx);
252+
expect(await response.text()).toEqual("hello from user worker");
253+
});
97254
});

0 commit comments

Comments
 (0)