-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ef4dd60
commit 47a68f7
Showing
8 changed files
with
259 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"deno.enable": true, | ||
"deno.lint": true, | ||
"deno.unstable": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,47 @@ | ||
# deno_webcache | ||
Web Cache API in Deno - in memory, on disk, and redis storage support | ||
# deno_httpcache | ||
|
||
HTTP Caching for Deno - in memory and redis storage support. Inspired by the | ||
Service Worker Cache API. | ||
|
||
## Usage | ||
|
||
Setting, getting, and deleting items in the cache: | ||
|
||
```ts | ||
import { inMemoryCache } from "https://deno.land/x/[email protected]/in_memory.ts"; | ||
|
||
const cache = inMemoryCache(5); | ||
|
||
const req = new Request("https://deno.land/[email protected]/version.ts"); | ||
const resp = await fetch(req); | ||
|
||
await cache.set(req, resp); | ||
|
||
const cachedResp = await cache.get(req); // or `cache.get(req.url)` | ||
if (cachedResp === undefined) throw new Error("Response not found in cache"); | ||
console.log(cachedResp.status); // 200 | ||
console.log(cachedResp.headers.get("content-type")); // application/typescript; charset=utf-8 | ||
|
||
await cache.remove(req); // or `cache.remove(req.url)` | ||
console.log(await cache.get(req)); // undefined | ||
``` | ||
|
||
And with redis: | ||
|
||
```ts | ||
import { redisCache } from "https://deno.land/x/[email protected]/redis.ts"; | ||
|
||
const cache = await redisCache("redis://127.0.0.1:6379"); | ||
|
||
// you can also optionally specify a prefix to use for the cache key: | ||
const cache = await redisCache("redis://127.0.0.1:6379", "v1-"); | ||
``` | ||
|
||
## Contributing | ||
|
||
Before submitting a PR, please run these three steps and check that they pass. | ||
|
||
1. `deno fmt` | ||
2. `deno lint --unstable` | ||
3. `deno test --allow-net` _this requires you to have a redis server running at | ||
127.0.0.1:6379_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { LRU } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import { Cache, CachedResponse } from "./mod.ts"; | ||
export { Cache }; | ||
|
||
export function inMemoryCache(capacity: number): Cache { | ||
const lru = new LRU<CachedResponse>(capacity); | ||
return new Cache({ | ||
get(url) { | ||
return Promise.resolve(lru.get(url)); | ||
}, | ||
set(url, resp) { | ||
lru.set(url, resp); | ||
return Promise.resolve(); | ||
}, | ||
delete(url) { | ||
lru.remove(url); | ||
return Promise.resolve(); | ||
}, | ||
close() { | ||
lru.clear(); | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { inMemoryCache } from "./in_memory.ts"; | ||
import { assert, assertEquals } from "./test_deps.ts"; | ||
|
||
Deno.test("[in memory] cache, retrieve, delete", async () => { | ||
const cache = inMemoryCache(5); | ||
try { | ||
const originalResp = new Response("Hello World", { | ||
status: 200, | ||
headers: { | ||
"server": "deno", | ||
"cache-control": "public, max-age=604800, immutable", | ||
}, | ||
}); | ||
|
||
await cache.put("https://deno.land", originalResp); | ||
|
||
const cachedResp = await cache.match("https://deno.land"); | ||
assert(cachedResp); | ||
assertEquals(originalResp.status, cachedResp.status); | ||
assertEquals( | ||
originalResp.headers.get("server"), | ||
cachedResp.headers.get("server"), | ||
); | ||
assertEquals(await cachedResp.text(), "Hello World"); | ||
|
||
await cache.delete("https://deno.land"); | ||
|
||
const otherCachedResp = await cache.match("https://deno.land"); | ||
assert(otherCachedResp === undefined); | ||
} finally { | ||
cache.close(); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import CachePolicy from "https://cdn.skypack.dev/http-cache-semantics?dts"; | ||
|
||
function cacheRequest(req: Request): CachePolicy.Request { | ||
return { | ||
url: req.url, | ||
headers: Object.fromEntries(req.headers.entries()), | ||
method: req.method, | ||
}; | ||
} | ||
|
||
export interface CachedResponse { | ||
body: Uint8Array; | ||
policy: CachePolicy.CachePolicyObject; | ||
} | ||
|
||
export interface CacheStorage { | ||
get(url: string): Promise<CachedResponse | undefined>; | ||
set(url: string, resp: CachedResponse): Promise<void>; | ||
delete(url: string): Promise<void>; | ||
close(): void; | ||
} | ||
|
||
export class Cache { | ||
#storage: CacheStorage; | ||
|
||
constructor(storage: CacheStorage) { | ||
this.#storage = storage; | ||
} | ||
|
||
close() { | ||
this.#storage.close(); | ||
} | ||
|
||
async match(request: RequestInfo): Promise<Response | undefined> { | ||
const req = request instanceof Request ? request : new Request(request); | ||
|
||
const cached = await this.#storage.get(req.url); | ||
if (cached === undefined) return Promise.resolve(undefined); | ||
|
||
const policy = CachePolicy.fromObject(cached.policy); | ||
|
||
const usable = policy.satisfiesWithoutRevalidation(cacheRequest(req)); | ||
if (!usable) return Promise.resolve(undefined); | ||
|
||
const resp = new Response(cached.body, { | ||
headers: policy.responseHeaders() as Record<string, string>, | ||
status: cached.policy.st, | ||
}); | ||
|
||
return Promise.resolve(resp); | ||
} | ||
|
||
async put(request: RequestInfo, response: Response): Promise<void> { | ||
const req = request instanceof Request ? request : new Request(request); | ||
|
||
const status = response.status; | ||
const headers = Object.fromEntries(response.headers.entries()); | ||
|
||
const policy = new CachePolicy(cacheRequest(req), { status, headers }, { | ||
shared: true, | ||
}); | ||
|
||
if (!policy.storable()) return; | ||
|
||
const body = await response.arrayBuffer(); | ||
|
||
await this.#storage.set(req.url, { | ||
body: new Uint8Array(body), | ||
policy: policy.toObject(), | ||
}); | ||
} | ||
|
||
async delete(request: RequestInfo): Promise<void> { | ||
const req = request instanceof Request ? request : new Request(request); | ||
await this.#storage.delete(req.url); | ||
return Promise.resolve(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { connect, parseURL } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import { | ||
decode, | ||
encode, | ||
} from "https://deno.land/[email protected]/encoding/base64.ts"; | ||
import { Cache } from "./mod.ts"; | ||
export { Cache }; | ||
|
||
export async function redisCache( | ||
databaseUrl: string, | ||
prefix = "", | ||
): Promise<Cache> { | ||
const conn = await connect(parseURL(databaseUrl)); | ||
return new Cache({ | ||
async get(url) { | ||
const bulk = await conn.get(prefix + url); | ||
if (!bulk) return undefined; | ||
const [policyBase64, bodyBase64] = bulk.split("\n"); | ||
const policy = JSON.parse(atob(policyBase64)); | ||
const body = decode(bodyBase64); | ||
return { policy, body }; | ||
}, | ||
async set(url, resp) { | ||
const policyBase64 = btoa(JSON.stringify(resp.policy)); | ||
const bodyBase64 = encode(resp.body); | ||
// TODO(lucacasonato): add ttl | ||
await conn.set(prefix + url, `${policyBase64}\n${bodyBase64}`); | ||
}, | ||
async delete(url) { | ||
await conn.del(prefix + url); | ||
}, | ||
close() { | ||
conn.close(); | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { redisCache } from "./redis.ts"; | ||
import { assert, assertEquals } from "./test_deps.ts"; | ||
|
||
Deno.test("[redis] cache, retrieve, delete", async () => { | ||
const cache = await redisCache("redis://127.0.0.1:6379", "cache-"); | ||
try { | ||
const originalResp = new Response("Hello World", { | ||
status: 200, | ||
headers: { | ||
"server": "deno", | ||
"cache-control": "public, max-age=604800, immutable", | ||
}, | ||
}); | ||
|
||
await cache.put("https://deno.land", originalResp); | ||
|
||
const cachedResp = await cache.match("https://deno.land"); | ||
assert(cachedResp); | ||
assertEquals(originalResp.status, cachedResp.status); | ||
assertEquals( | ||
originalResp.headers.get("server"), | ||
cachedResp.headers.get("server"), | ||
); | ||
assertEquals(await cachedResp.text(), "Hello World"); | ||
|
||
await cache.delete("https://deno.land"); | ||
|
||
const otherCachedResp = await cache.match("https://deno.land"); | ||
assert(otherCachedResp === undefined); | ||
} finally { | ||
cache.close(); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export { | ||
assert, | ||
assertEquals, | ||
} from "https://deno.land/[email protected]/testing/asserts.ts"; |