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

Commit 3c1f95c

Browse files
authored
Cache API tests (#447)
* Respect `cf.cacheKey` in Cache API * Fix caching of `ReadableStream`-bodied `Response`s * Propagate `clock` through `GatewayFactory` for tests * Catch loopback handler errors and return 500 response * Add tests for `_getRangeResponse` and `_parseRanges` helpers * Add `Miniflare` integration test helper * Bump `undici` to `5.13.0` and add `@cloudflare/workers-types` * Add Cache API tests
1 parent a7d0f63 commit 3c1f95c

File tree

14 files changed

+965
-67
lines changed

14 files changed

+965
-67
lines changed

package-lock.json

Lines changed: 35 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/tre/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@
3737
"kleur": "^4.1.5",
3838
"source-map-support": "0.5.21",
3939
"stoppable": "^1.1.0",
40-
"undici": "^5.12.0",
40+
"undici": "^5.13.0",
4141
"workerd": "^1.20221111.5",
4242
"ws": "^8.11.0",
4343
"youch": "^3.2.2",
4444
"zod": "^3.18.0"
4545
},
4646
"devDependencies": {
47+
"@cloudflare/workers-types": "^4.20221111.1",
4748
"@types/better-sqlite3": "^7.6.2",
4849
"@types/debug": "^4.1.7",
4950
"@types/estree": "^1.0.0",

packages/tre/src/index.ts

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
serializeConfig,
4747
} from "./runtime";
4848
import {
49+
Clock,
4950
HttpError,
5051
Log,
5152
MiniflareCoreError,
@@ -54,6 +55,7 @@ import {
5455
OptionalZodTypeOf,
5556
UnionToIntersection,
5657
ValueOf,
58+
defaultClock,
5759
} from "./shared";
5860
import { anyAbortSignal } from "./shared/signal";
5961
import { waitForRequest } from "./wait";
@@ -157,6 +159,7 @@ export class Miniflare {
157159
#sharedOpts: PluginSharedOptions;
158160
#workerOpts: PluginWorkerOptions[];
159161
#log: Log;
162+
readonly #clock: Clock;
160163

161164
readonly #runtimeConstructor: RuntimeConstructor;
162165
#runtime?: Runtime;
@@ -201,6 +204,7 @@ export class Miniflare {
201204
this.#sharedOpts = sharedOpts;
202205
this.#workerOpts = workerOpts;
203206
this.#log = this.#sharedOpts.core.log ?? new NoOpLog();
207+
this.#clock = this.#sharedOpts.core.clock ?? defaultClock;
204208
this.#initPlugins();
205209

206210
// Get supported shell for executing runtime binary
@@ -220,6 +224,7 @@ export class Miniflare {
220224
if (plugin.gateway !== undefined && plugin.router !== undefined) {
221225
const gatewayFactory = new GatewayFactory<any>(
222226
this.#log,
227+
this.#clock,
223228
this.#sharedOpts.core.cloudflareFetch,
224229
key,
225230
plugin.gateway,
@@ -350,24 +355,30 @@ export class Miniflare {
350355
});
351356

352357
let response: Response | undefined;
353-
const customService = request.headers.get(HEADER_CUSTOM_SERVICE);
354-
if (customService !== null) {
355-
response = await this.#handleLoopbackCustomService(
356-
request,
357-
customService
358-
);
359-
} else if (url.pathname === "/core/error") {
360-
const workerSrcOpts = this.#workerOpts.map<SourceOptions>(
361-
({ core }) => core
362-
);
363-
response = await handlePrettyErrorRequest(
364-
this.#log,
365-
workerSrcOpts,
366-
request
367-
);
368-
} else {
369-
// TODO: check for proxying/outbound fetch header first (with plans for fetch mocking)
370-
response = await this.#handleLoopbackPlugins(request, url);
358+
try {
359+
const customService = request.headers.get(HEADER_CUSTOM_SERVICE);
360+
if (customService !== null) {
361+
response = await this.#handleLoopbackCustomService(
362+
request,
363+
customService
364+
);
365+
} else if (url.pathname === "/core/error") {
366+
const workerSrcOpts = this.#workerOpts.map<SourceOptions>(
367+
({ core }) => core
368+
);
369+
response = await handlePrettyErrorRequest(
370+
this.#log,
371+
workerSrcOpts,
372+
request
373+
);
374+
} else {
375+
// TODO: check for proxying/outbound fetch header first (with plans for fetch mocking)
376+
response = await this.#handleLoopbackPlugins(request, url);
377+
}
378+
} catch (e: any) {
379+
this.#log.error(e);
380+
res.writeHead(500);
381+
return res.end(e?.stack ?? String(e));
371382
}
372383

373384
if (response === undefined) {

packages/tre/src/plugins/cache/gateway.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import assert from "assert";
12
import crypto from "crypto";
23
import http from "http";
34
import { AddressInfo } from "net";
@@ -171,12 +172,13 @@ class HttpParser {
171172
});
172173
}
173174
private listen(request: http.IncomingMessage, response: http.ServerResponse) {
174-
if (request.url) {
175-
response?.socket?.write(
176-
this.responses.get(request.url) ?? new Uint8Array()
177-
);
178-
}
179-
response.end();
175+
assert(request.url !== undefined);
176+
assert(response.socket !== null);
177+
const array = this.responses.get(request.url);
178+
assert(array !== undefined);
179+
// Write response to parse directly to underlying socket
180+
response.socket.write(array);
181+
response.socket.end();
180182
}
181183
public async parse(response: Uint8Array): Promise<ParsedHttpResponse> {
182184
await this.connected;
@@ -207,11 +209,12 @@ export class CacheGateway {
207209
private readonly clock: Clock
208210
) {}
209211

210-
async match(request: Request): Promise<Response> {
212+
async match(request: Request, cacheKey?: string): Promise<Response> {
211213
// Never cache Workers Sites requests, so we always return on-disk files
212214
if (isSitesRequest(request)) throw new CacheMiss();
213215

214-
const cached = await this.storage.get<CacheMetadata>(request.url);
216+
cacheKey ??= request.url;
217+
const cached = await this.storage.get<CacheMetadata>(cacheKey);
215218
if (cached?.metadata === undefined) throw new CacheMiss();
216219

217220
const response = new CacheResponse(
@@ -228,11 +231,15 @@ export class CacheGateway {
228231
);
229232
}
230233

231-
async put(request: Request, value: ArrayBuffer): Promise<Response> {
234+
async put(
235+
request: Request,
236+
value: Uint8Array,
237+
cacheKey?: string
238+
): Promise<Response> {
232239
// Never cache Workers Sites requests, so we always return on-disk files
233240
if (isSitesRequest(request)) return new Response(null, { status: 204 });
234241

235-
const response = await HttpParser.get().parse(new Uint8Array(value));
242+
const response = await HttpParser.get().parse(value);
236243

237244
const { storable, expiration, headers } = getExpiration(
238245
this.clock,
@@ -246,7 +253,8 @@ export class CacheGateway {
246253
throw new StorageFailure();
247254
}
248255

249-
await this.storage.put<CacheMetadata>(request.url, {
256+
cacheKey ??= request.url;
257+
await this.storage.put<CacheMetadata>(cacheKey, {
250258
value: response.body,
251259
expiration: millisToSeconds(this.clock() + expiration),
252260
metadata: {
@@ -257,8 +265,9 @@ export class CacheGateway {
257265
return new Response(null, { status: 204 });
258266
}
259267

260-
async delete(request: Request): Promise<Response> {
261-
const deleted = await this.storage.delete(request.url);
268+
async delete(request: Request, cacheKey?: string): Promise<Response> {
269+
cacheKey ??= request.url;
270+
const deleted = await this.storage.delete(cacheKey);
262271
// This is an extremely vague error, but it fits with what the cache API in workerd expects
263272
if (!deleted) throw new PurgeFailure();
264273
return new Response(null);

packages/tre/src/plugins/cache/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,4 @@ export const CACHE_PLUGIN: Plugin<
9797
};
9898

9999
export * from "./gateway";
100+
export * from "./range";

0 commit comments

Comments
 (0)