Skip to content

Commit

Permalink
An initial implementation of the cache API (#428)
Browse files Browse the repository at this point in the history
* Initial cache implementation

* Address PR comments

* Address comments
  • Loading branch information
penalosa authored Nov 9, 2022
1 parent 2f1c201 commit be72411
Show file tree
Hide file tree
Showing 16 changed files with 626 additions and 32 deletions.
26 changes: 26 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion packages/tre/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"exit-hook": "^2.2.1",
"get-port": "^5.1.1",
"glob-to-regexp": "^0.4.1",
"http-cache-semantics": "^4.1.0",
"kleur": "^4.1.5",
"stoppable": "^1.1.0",
"undici": "^5.10.0",
Expand All @@ -51,6 +52,7 @@
"@types/debug": "^4.1.7",
"@types/estree": "^1.0.0",
"@types/glob-to-regexp": "^0.4.1",
"@types/stoppable": "^1.1.1"
"@types/stoppable": "^1.1.1",
"@types/http-cache-semantics": "^4.0.1"
}
}
42 changes: 42 additions & 0 deletions packages/tre/src/plugins/cache/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# The Cache API
The Cache API within `workerd` is _extremely_ lenient with errors, and will work with a service that doesn't fully support the expected API. This is what it _should_ behave like:

## .add()
Unimplemented in the runtime

## .addAll()
Unimplemented in the runtime

## .match()
`workerd` guarantees:
- The method will always be `GET`
- The request headers will include `Cache-Control: only-if-cached` (which Miniflare ignores)
- The request headers will include `Cf-Cache-Namespaces` if this is a namespaced cache (i.e. `caches.open(...)`)
`workerd` expects:
- The `Cf-Cache-Status` header to be present with the value:
- `MISS` if it's a cache miss, in which case the rest of the response is ignored by `workerd`
- `HIT` if it's a cache hit, in which case `workerd` sends the response on to the user, including the full headers and full body

## .put()
`workerd` guarantees:
- The method will always be `PUT`, and the cache key method will always be `GET`
- The headers will be the headers of the cache key, and the URL will be the URL of the cache key
- The headers will include `Cf-Cache-Namespaces` if this is a namespaced cache (i.e. `caches.open(...)`)
- The body contains the serialised response for storage
- The serialised response will never:
- Have a `206` status code
- Have a `Vary: *` header
- Have a `304` status code
`workerd` expects:
- A `204` (success) or `413` (failure) response code. It doesn't do anything with either

## .delete()
`workerd` guarantees:
- The method will always be `PURGE`, and the cache key method will always be `GET`
- The headers will include `Cf-Cache-Namespaces` if this is a namespaced cache (i.e. `caches.open(...)`)
- The header `X-Real-IP` will be set to `127.0.0.1`
- The remaining headers will be the cache key headers
`workerd` expects:
- Status `200` on success
- Status `404` on failure
- Status `429` on rate limit (which will throw in the user worker)
65 changes: 65 additions & 0 deletions packages/tre/src/plugins/cache/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { HeadersInit, Response } from "undici";
import { CfHeader } from "../shared/constants";

enum Status {
PayloadTooLarge = 413,
NotFound = 404,
CacheMiss = 504,
}

export async function fallible<T>(promise: Promise<T>): Promise<T | Response> {
try {
return await promise;
} catch (e) {
if (e instanceof CacheError) {
return e.toResponse();
}
throw e;
}
}

export class CacheError extends Error {
constructor(
private status: number,
message: string,
readonly headers: HeadersInit = []
) {
super(message);
this.name = "CacheError";
}

toResponse() {
return new Response(null, {
status: this.status,
headers: this.headers,
});
}

context(info: string) {
this.message += ` (${info})`;
return this;
}
}

export class StorageFailure extends CacheError {
constructor() {
super(Status.PayloadTooLarge, "Cache storage failed");
}
}

export class PurgeFailure extends CacheError {
constructor() {
super(Status.NotFound, "Couldn't find asset to purge");
}
}

export class CacheMiss extends CacheError {
constructor() {
super(
// workerd ignores this, but it's the correct status code
Status.CacheMiss,
"Asset not found in cache",
[[CfHeader.CacheStatus, "MISS"]]
);
}
}
Loading

0 comments on commit be72411

Please sign in to comment.