Skip to content

Latest commit

 

History

History
389 lines (285 loc) · 11.6 KB

File metadata and controls

389 lines (285 loc) · 11.6 KB
title Cache
sidebarTitle Cache API
description Allows fine grained control of reading and writing from the Bunny network cache.
tag Preview
noindex true
Currently the Edge Scripting runners happen after the HTTP cache layer. See [Limitations](#limitations) for details.

Overview

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.

Accessing Cache

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);
**Note:** When using the Cache API, avoid overriding the hostname in cache requests, as this can lead to unexpected behaviour when using multiple domains associated with a PullZone.
```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`);
```

Headers

Cache Control

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.

Examples

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.

Instance Methods

Delete

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.

Syntax

delete request;

Unlike the browser Cache API, Bunny Edge Scripting does not support options on delete().

Parameters

request The Request you are looking to delete. This can be a Request object or a URL.

Return Value

A Promise that resolves to true if the cache entry is scheduled to be deleted, or false otherwise.

Exceptions

Can throw an exception if there is an issue accessing the underlying cache storage.

Examples

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,
      });
    }
  },
);

Match

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.

Syntax

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.

Parameters

request The Request for which you are attempting to find responses in the Cache. This can be a Request object or a URL.

Return Value

A Promise that resolves to the first Response that matches the request or to undefined if no match is found.

Exceptions

Can throw an exception if there is an issue accessing the underlying cache storage.

Examples

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,
      });
    }
  },
);

Put

The put() method of the Cache allows key/value pairs to be added to the current Cache object.

Syntax

put(request, response);

Parameters

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.

Return Value

A Promise that resolves with undefined.

Exceptions

Can throw an exception if there is an issue accessing the underlying cache storage.

Examples

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,
      });
    }
  },
);

Limitations

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-cache header set
  • disable Smart Cache for your PullZone
  • set Cache expiration time for your PullZone to Respect origin Cache-Control
  • set Browser cache expiration time for your PullZone to Match 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.

Quickstart

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,
      });
    }
  },
);

References