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

Commit b2a8295

Browse files
committed
Move Workers Sites key filtering to gateway
This is a temporary fix to allow Workers Sites tests cherrypicked from #656 to pass. Listing keys in Workers Sites `__STATIC_CONTENT` namespace was previously broken, due to a type error in the inline namespace worker script. This change also means we can avoid the `Function#toString()`ing to "bundle" code in inline worker scripts.
1 parent 32fdf00 commit b2a8295

File tree

2 files changed

+42
-76
lines changed

2 files changed

+42
-76
lines changed

packages/miniflare/src/plugins/kv/router.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,14 @@ export class KVRouter extends Router<KVGateway> {
6464

6565
// Get value from storage
6666
let value: KVGatewayGetResult | undefined;
67-
if (req.headers.get(HEADER_SITES) === null) {
67+
const siteRegExps = req.headers.get(HEADER_SITES);
68+
if (siteRegExps === null) {
6869
const gateway = this.gatewayFactory.get(namespace, persist);
6970
value = await gateway.get(key, options);
7071
} else {
7172
// Workers Sites: if this is a sites request, persist should be used as
7273
// the root without any additional namespace
73-
value = await sitesGatewayGet(persist, key, options);
74+
value = await sitesGatewayGet(persist, siteRegExps, key, options);
7475
}
7576
if (value === undefined) throw new KVError(404, "Not Found");
7677

@@ -155,13 +156,14 @@ export class KVRouter extends Router<KVGateway> {
155156

156157
// List keys from storage
157158
let result: KVGatewayListResult;
158-
if (req.headers.get(HEADER_SITES) === null) {
159+
const siteRegExps = req.headers.get(HEADER_SITES);
160+
if (siteRegExps === null) {
159161
const gateway = this.gatewayFactory.get(namespace, persist);
160162
result = await gateway.list(options);
161163
} else {
162164
// Workers Sites: if this is a sites request, persist should be used as
163165
// the root without any additional namespace
164-
result = await sitesGatewayList(persist, options);
166+
result = await sitesGatewayList(persist, siteRegExps, options);
165167
}
166168
return Response.json(result);
167169
};

packages/miniflare/src/plugins/kv/sites.ts

Lines changed: 36 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,7 @@ import {
2222
WORKER_BINDING_SERVICE_LOOPBACK,
2323
kProxyNodeBinding,
2424
} from "../shared";
25-
import {
26-
HEADER_SITES,
27-
KV_PLUGIN_NAME,
28-
MAX_LIST_KEYS,
29-
PARAM_URL_ENCODED,
30-
} from "./constants";
25+
import { HEADER_SITES, KV_PLUGIN_NAME, MAX_LIST_KEYS } from "./constants";
3126
import {
3227
KVGatewayGetOptions,
3328
KVGatewayGetResult,
@@ -43,7 +38,7 @@ async function* listKeysInDirectoryInner(
4338
): AsyncGenerator<string> {
4439
const fileEntries = await fs.readdir(currentPath, { withFileTypes: true });
4540
for (const fileEntry of fileEntries) {
46-
const filePath = path.join(currentPath, fileEntry.name);
41+
const filePath = path.posix.join(currentPath, fileEntry.name);
4742
if (fileEntry.isDirectory()) {
4843
yield* listKeysInDirectoryInner(rootPath, filePath);
4944
} else {
@@ -70,6 +65,20 @@ export interface SiteMatcherRegExps {
7065
// Cache glob RegExps between `getBindings` and `getServices` calls
7166
const sitesRegExpsCache = new WeakMap<SitesOptions, SiteMatcherRegExps>();
7267

68+
function serialiseSiteRegExps(exps: SiteMatcherRegExps) {
69+
return {
70+
include: exps.include && serialiseRegExps(exps.include),
71+
exclude: exps.exclude && serialiseRegExps(exps.exclude),
72+
};
73+
}
74+
75+
function deserialiseSiteRegExps(exps: ReturnType<typeof serialiseSiteRegExps>) {
76+
return {
77+
include: exps.include && deserialiseRegExps(exps.include),
78+
exclude: exps.exclude && deserialiseRegExps(exps.exclude),
79+
};
80+
}
81+
7382
function testSiteRegExps(regExps: SiteMatcherRegExps, key: string): boolean {
7483
return (
7584
// Either include globs undefined, or name matches them
@@ -104,77 +113,25 @@ const SERVICE_NAMESPACE_SITE = `${KV_PLUGIN_NAME}:site`;
104113

105114
const BINDING_KV_NAMESPACE_SITE = "__STATIC_CONTENT";
106115
const BINDING_JSON_SITE_MANIFEST = "__STATIC_CONTENT_MANIFEST";
107-
const BINDING_JSON_SITE_FILTER = "MINIFLARE_SITE_FILTER";
116+
const BINDING_TEXT_SITE_FILTER = "MINIFLARE_SITE_FILTER";
108117

109118
const SCRIPT_SITE = `
110-
// Inject key encoding/decoding functions
111-
const SITES_NO_CACHE_PREFIX = "${SITES_NO_CACHE_PREFIX}";
112-
const encodeSitesKey = ${encodeSitesKey.toString()};
113-
const decodeSitesKey = ${decodeSitesKey.toString()};
114-
115-
// Inject glob matching RegExp functions
116-
const deserialiseRegExps = ${deserialiseRegExps.toString()};
117-
const testRegExps = ${testRegExps.toString()};
118-
const testSiteRegExps = ${testSiteRegExps.toString()};
119-
120-
// Deserialise glob matching RegExps
121-
const serialisedSiteRegExps = ${BINDING_JSON_SITE_FILTER};
122-
const siteRegExps = {
123-
include: serialisedSiteRegExps.include && deserialiseRegExps(serialisedSiteRegExps.include),
124-
exclude: serialisedSiteRegExps.exclude && deserialiseRegExps(serialisedSiteRegExps.exclude),
125-
};
126-
127-
async function handleRequest(request) {
119+
function handleRequest(request) {
128120
// Only permit reads
129121
if (request.method !== "GET") {
130122
const message = \`Cannot \${request.method.toLowerCase()}() with read-only Workers Sites namespace\`;
131123
return new Response(message, { status: 405, statusText: message });
132124
}
133-
134-
// Decode key (empty if listing)
135-
const url = new URL(request.url);
136-
let key = url.pathname.substring(1); // Strip leading "/"
137-
if (url.searchParams.get("${PARAM_URL_ENCODED}")?.toLowerCase() === "true") {
138-
key = decodeURIComponent(key);
139-
}
140-
141-
// Strip SITES_NO_CACHE_PREFIX
142-
key = decodeSitesKey(key);
143125
144-
// If not listing keys, check key is included, returning not found if not
145-
if (key !== "" && !testSiteRegExps(siteRegExps, key)) {
146-
return new Response("Not Found", { status: 404, statusText: "Not Found" })
147-
}
148-
149-
// Re-encode key
150-
key = encodeURIComponent(key);
151-
url.pathname = \`/${KV_PLUGIN_NAME}/${BINDING_KV_NAMESPACE_SITE}/\${key}\`;
152-
url.searchParams.set("${PARAM_URL_ENCODED}", "true"); // Always URL encoded now
126+
const url = new URL(request.url);
127+
url.pathname = \`/${KV_PLUGIN_NAME}/${BINDING_KV_NAMESPACE_SITE}/\${url.pathname}\`;
153128
154-
// Send request to loopback server
155129
request = new Request(url, request);
156130
request.headers.set("${HEADER_PERSIST}", ${BINDING_TEXT_PERSIST});
157131
// Add magic header to indicate namespace should be ignored, and persist
158132
// should be used as the root without any additional namespace
159-
request.headers.set("${HEADER_SITES}", "true");
160-
const response = await ${CoreBindings.SERVICE_LOOPBACK}.fetch(request);
161-
162-
// If listing keys, only return included keys, and add SITES_NO_CACHE_PREFIX
163-
// to all result keys
164-
if (key === "" && response.ok) {
165-
const { keys, list_complete, cursor } = await response.json();
166-
return Response.json({
167-
keys: keys.filter((key) => {
168-
if (!testSiteRegExps(siteRegExps, key)) return false;
169-
key.name = encodeSitesKey(key.name);
170-
return true;
171-
}),
172-
list_complete,
173-
cursor,
174-
});
175-
}
176-
177-
return response;
133+
request.headers.set("${HEADER_SITES}", ${BINDING_TEXT_SITE_FILTER});
134+
return ${CoreBindings.SERVICE_LOOPBACK}.fetch(request);
178135
}
179136
180137
addEventListener("fetch", (event) => event.respondWith(handleRequest(event.request)));
@@ -252,10 +209,7 @@ export function getSitesService(options: SitesOptions): Service {
252209
const siteRegExps = sitesRegExpsCache.get(options);
253210
assert(siteRegExps !== undefined);
254211
// Ensure `siteRegExps` is JSON-serialisable
255-
const serialisedSiteRegExps = {
256-
include: siteRegExps.include && serialiseRegExps(siteRegExps.include),
257-
exclude: siteRegExps.exclude && serialiseRegExps(siteRegExps.exclude),
258-
};
212+
const serialisedSiteRegExps = serialiseSiteRegExps(siteRegExps);
259213

260214
// Use unsanitised file storage to ensure file names containing e.g. dots
261215
// resolve correctly.
@@ -273,8 +227,8 @@ export function getSitesService(options: SitesOptions): Service {
273227
text: JSON.stringify(persist),
274228
},
275229
{
276-
name: BINDING_JSON_SITE_FILTER,
277-
json: JSON.stringify(serialisedSiteRegExps),
230+
name: BINDING_TEXT_SITE_FILTER,
231+
text: JSON.stringify(serialisedSiteRegExps),
278232
},
279233
],
280234
},
@@ -288,13 +242,19 @@ export function getSitesService(options: SitesOptions): Service {
288242

289243
export async function sitesGatewayGet(
290244
persist: Persistence,
245+
serialisedSiteRegExps: string,
291246
key: string,
292247
opts?: KVGatewayGetOptions
293248
): Promise<KVGatewayGetResult | undefined> {
294249
// `persist` is a resolved path set in `getSitesService()`
295250
assert(typeof persist === "string");
251+
const siteRegExps = deserialiseSiteRegExps(JSON.parse(serialisedSiteRegExps));
296252

297253
validateGetOptions(key, opts);
254+
255+
key = decodeSitesKey(key);
256+
if (!testSiteRegExps(siteRegExps, key)) return;
257+
298258
const filePath = path.join(persist, key);
299259
if (!filePath.startsWith(persist)) return;
300260
try {
@@ -314,17 +274,21 @@ export async function sitesGatewayGet(
314274

315275
export async function sitesGatewayList(
316276
persist: Persistence,
277+
serialisedSiteRegExps: string,
317278
opts: KVGatewayListOptions = {}
318279
): Promise<KVGatewayListResult> {
319280
// `persist` is a resolved path set in `getSitesService()`
320281
assert(typeof persist === "string");
282+
const siteRegExps = deserialiseSiteRegExps(JSON.parse(serialisedSiteRegExps));
321283

322284
validateListOptions(opts);
323285
const { limit = MAX_LIST_KEYS, prefix, cursor } = opts;
324286

325287
// Get sorted array of all keys matching prefix
326288
let keys: KVGatewayListResult["keys"] = [];
327-
for await (const name of listKeysInDirectory(persist)) {
289+
for await (let name of listKeysInDirectory(persist)) {
290+
if (!testSiteRegExps(siteRegExps, name)) continue;
291+
name = encodeSitesKey(name);
328292
if (prefix === undefined || name.startsWith(prefix)) keys.push({ name });
329293
}
330294
keys.sort((a, b) => lexicographicCompare(a.name, b.name));

0 commit comments

Comments
 (0)