| title | Cache |
|---|---|
| sidebarTitle | Cache API |
| description | Allows fine grained control of reading and writing from the Bunny network cache. |
| tag | Preview |
| noindex | true |
The Cache API is an implementation of the MSDN Cache Interface to provide a persistent storage mechanism for Request / Response object pairs that are cached in long lived memory.
The Cache API is available globally but the contents of the cache do not replicate outside of the originating region. A GET /users response can be cached in the originating region, but will not exist in another region until a request for the resources is made from there.
An origin can have multiple, named Cache objects. You are responsible for implementing how your script handles Cache updates. Items in a Cache respect the Cache-Control request header, it is expected that you will use this to control the life cycle of your caches. Cache entries will automatically be purged a short time after they expire. Make sure to version caches by name and use the caches only from the version of the script that they can safely operate on.
The cache instances are shared across all domains associated with your PullZone, however they must be accessed via the currently requesting host name. For this reason we either recommend using the current Request object as they key or constructing the key using the current request url e.g. const key = new URL(req.url).origin + "/img/example.png";. This will ensure your caches work reliably across all domains associated with your PullZone.
Note: There is a hard limit of 100MB per cache file.
The caches.default API is strongly influenced by the web browsers’ Cache API Interface.
await caches.default.match(request);You may create and manage additional Cache instances via the caches.open method.
let cache = await caches.open("cache:v1");
await cache.match(request);```js
// recommended approach: use current request url
const url = new URL(request.url);
const cache = await caches.open("cache:v1");
const response = await cache.match(`${url.origin}/example.html`);
```
Caches are controlled via the Cache-Control response header when putting resources into the cache. Response resources will be cached based on how this header is defined. For more information please see: Cache Control.
await cache.put(
`${url.origin}/example.html`,
new Response("content", {
headers: { "Cache-Control": "max-age=600" },
}),
);This will cache the string content under the key of /example.html for 10min.
The delete() method of the Cache finds the Cache entry whose key is the request, and if found, deletes the Cache entry and returns a Promise that resolves to true.
delete request;Unlike the browser Cache API, Bunny Edge Scripting does not support options on delete().
request
The Request you are looking to delete. This can be a Request object or a URL.
A Promise that resolves to true if the cache entry is scheduled to be deleted, or false otherwise.
Can throw an exception if there is an issue accessing the underlying cache storage.
import * as BunnySDK from "@bunny.net/edgescript-sdk";
BunnySDK.net.http.serve(
async (request: Request): Response | Promise<Response> => {
const url = new URL(request.url);
try {
await caches
.open("cache:v1")
.then((cache) => cache.delete(`${url.origin}/example.html`));
return new Response("Cache File Removed", {
headers: { "Cache-Control": "no-cache" },
});
} catch (error) {
return new Response(`Error deleting cache file: ${error.message}`, {
status: 500,
});
}
},
);The match() method of the Cache returns a Promise that resolves to the Response associated with the first matching request in the Cache object. If no match is found, the Promise resolves to undefined.
match(request);Unlike the browser Cache API, Bunny Edge Scripting does not support options on match(). You can accomplish this behavior by removing query strings or HTTP headers at put() time.
Our implementation of the Cache API respects the following HTTP headers on the request passed to match():
If-None-Match- Results in a 304 response if a matching response is found with an ETag header with a value that matches a value in If-None-Match.
request
The Request for which you are attempting to find responses in the Cache. This can be a Request object or a URL.
A Promise that resolves to the first Response that matches the request or to undefined if no match is found.
Can throw an exception if there is an issue accessing the underlying cache storage.
import * as BunnySDK from "@bunny.net/edgescript-sdk";
BunnySDK.net.http.serve(
async (request: Request): Response | Promise<Response> => {
const url = new URL(request.url);
try {
const res = await caches
.open("cache:v1")
.then((cache) => cache.match(`${url.origin}/example.html`));
if (res) {
return res;
} else {
return new Response("Cache Miss", {
headers: { "Cache-Control": "no-cache" },
});
}
} catch (error) {
return new Response(`Error matching cache file: ${error.message}`, {
status: 500,
});
}
},
);The put() method of the Cache allows key/value pairs to be added to the current Cache object.
put(request, response);request
The Request object or URL that you want to add to the cache.
response
The Response you want to match up to the request.
A Promise that resolves with undefined.
Can throw an exception if there is an issue accessing the underlying cache storage.
import * as BunnySDK from "@bunny.net/edgescript-sdk";
BunnySDK.net.http.serve(
async (request: Request): Response | Promise<Response> => {
const url = new URL(request.url);
const now = new Date().toISOString();
try {
const cache = await caches.open("cache:v3");
const cachedResponse = await cache.match(`${url.origin}/example.html`);
if (cachedResponse) {
return cachedResponse;
}
const response = new Response(
`<!DOCTYPE html><html><body><p>Hello World at: ${now}</p></body></html>`,
{
headers: {
"Cache-Control": "max-age=10",
"Content-Type": "text/html; charset=utf-8",
},
},
);
// we clone the resource as this will consume the request body
await cache.put(`${url.origin}/example.html`, response.clone());
return response;
} catch (error) {
return new Response(`Error putting cache: ${error.message}`, {
status: 500,
});
}
},
);Currently the Edge Scripting runners happen after the HTTP cache layer. This means the whole Edge Scripting response can be cached. To avoid this:
- always return a response from the script with the
Cache-Control: no-cacheheader set - disable
Smart Cachefor your PullZone - set
Cache expiration timefor your PullZone toRespect origin Cache-Control - set
Browser cache expiration timefor your PullZone toMatch server cache expiration
Only CacheStorage open methods are currently supported. We recommend versioning your cache names to ensure that a cache is completely purged when updating scripts.
Currently we only support Cache match, put, delete.
Here is a practical example of caching a HTML page on the edge, but updating the content based on a query parameter.
import * as BunnySDK from "@bunny.net/edgescript-sdk";
BunnySDK.net.http.serve(
async (request: Request): Response | Promise<Response> => {
const url = new URL(request.url);
const now = new Date();
const expires = new Date(now.getTime() + 60 * 1000); // + 1min
try {
const cache = await caches.open("cache:v1");
// 1. check to see if we already have a cache entry
let res = await cache.match(`${url.origin}/example.html`);
// 2. if we don't, create one and put it into the cache for 60 seconds
if (!res) {
res = new Response(
`<!DOCTYPE html><html><body><p>Hello <span id="name"></span></p><ul><li>created at: ${now.toISOString()}</li><li>expires at: ${expires.toISOString()}</li></ul></body></html>`,
{
headers: {
"Cache-Control": "max-age=60",
"Content-Type": "text/html; charset=utf-8",
},
},
);
await cache.put(`${url.origin}/example.html`, res.clone());
}
// 3. modify the contents
const content = new ExperimentalHTMLRewriter()
.on("span", {
element(el) {
el.setInnerContent(
url.searchParams.get("name") ??
"Nameless User (use ?name= to define this)",
);
},
})
.transform(res);
// 3. return modified contents to the user
return new Response(await content.text(), {
headers: {
"Cache-Control": "no-cache",
"Content-Type": "text/html; charset=utf-8",
},
});
} catch (error) {
return new Response(`Error: ${error.message}`, {
status: 500,
});
}
},
);Here is an example of updating a cache in middleware
import * as BunnySDK from "@bunny.net/edgescript-sdk";
async function onOriginRequest(context: {
request: Request;
}): Promise<Response> | Response | Promise<Request> | Request | void {
const now = new Date();
try {
const cache = await caches.open("cache:v1");
if (context.request.method == "POST") {
const res = Response(await `Content from POST: ${now.toISOString()}`, {
headers: {
"Cache-Control": "max-age=3600",
"Content-Type": "text/plain; charset=utf-8",
},
});
cache.put(context.request, res);
}
} catch (e) {
console.log(e);
}
}
BunnySDK.net.http.servePullZone().onOriginRequest(onOriginRequest);import * as BunnySDK from "@bunny.net/edgescript-sdk";
BunnySDK.net.http.serve(
async (request: Request): Response | Promise<Response> => {
try {
const cache = await caches.open("cache:v1");
let res = await cache.match(request);
if (!res) {
const res = Response(await "Content from GET", {
headers: {
"Cache-Control": "no-cache",
"Content-Type": "text/plain; charset=utf-8",
},
});
}
return res;
} catch (error) {
return new Response(`Error: ${error.message}`, {
status: 500,
});
}
},
);- MSDN CacheStorage - Mozilla's CacheStorage interface documentation
- MSDN Cache API - Mozilla's Cache interface documentation