Öffentliche API-Antworten zwischenspeichern

CloudflareBeginner
Jetzt üben

Einführung

Ein öffentlicher Supportkatalog erhält wiederholt Anfragen für dieselbe Sprache und Kategorie. Durch die Wiederverwendung von Antworten lässt sich wiederholte Arbeit reduzieren. Ein Cache darf jedoch niemals kundenspezifische Daten vermischen oder einen Fehler als öffentlichen Inhalt zwischenspeichern. Sie beobachten zunächst die ungepufferte Generierung, fügen anschließend eine explizite Cache-Richtlinie hinzu, testen Ablauf und gezielte Invalidierung und stellen den Worker danach bereit, um seine Grenzen zu überprüfen.

Dieses unabhängige Lab beginnt in /home/labex/project/public-cache mit Node.js 22.22.0, dem lokalen Wrangler 4.131.1, Miniflare 4.20260730.0 für die isolierte Bewertung und einem synthetischen Antwort-Fixture. Verwenden Sie Ihr eigenes Lernkonto sowie die zuvor vermittelten Abläufe für Autorisierung, Bereitstellung und Secrets. Eine vorherige VM, Ressource, gekaufte Domain, Datenbank oder kostenpflichtige Erweiterung ist nicht erforderlich. Anfragen werden auf die normale Kontonutzung angerechnet.

Lassen Sie ein Terminal geöffnet. Alle Katalogdaten und Zugangsdaten sind synthetisch. Die Inhalte der Cache API sind auf einen Bereitstellungsort begrenzt; ein globales Netzwerk ist kein global replizierter Cache. Löschen Sie zum Abschluss den Worker, entfernen Sie das lokale Secret und melden Sie sich ab.

Frische Antworten des öffentlichen Katalogs beobachten

In diesem Schritt untersuchen Sie einen bereitgestellten synthetischen Katalog und stellen sein Verhalten ohne Caching fest. Das Fixture erzeugt für jede Antwort eine neue UUID. Dadurch lässt sich die Wiederverwendung beobachten, ohne Zeitmessungen oder eine Datenbank zu benötigen.

cd /home/labex/project/public-cache
node --version
npx wrangler --version
cat src/catalog.js

Erwarten Sie Node.js v22.22.0 und Wrangler 4.131.1. Das Setup hat die exakten Projektabhängigkeiten installiert. Mit npm ci können Sie eine vorhandene Installation anhand ihrer Lock-Datei reproduzieren. Die Laufzeit für die Bewertung verwendet ebenfalls Miniflare 4.20260730.0 und damit dasselbe Kompatibilitätsdatum. Das Fixture unterscheidet sich nach Sprache, Kategorie und synthetischem Kunden. Mit X-Demo-Failure: 1 können Sie einen Fehler 503 simulieren. Diese Werte dienen als Testeingaben und sind keine echten Identitätsnachweise.

WORKER_NAME="labex-cache-$(openssl rand -hex 6)"
cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-07-30",
  "workers_dev": true,
  "preview_urls": false
}
CONFIG
cat > src/index.js <<'JS'
import {catalog} from './catalog.js';

function deliver(response, cacheStatus) {
  const headers = new Headers(response.headers);
  headers.set('X-Lab-Cache', cacheStatus);
  // This lab caches inside the Worker, not in the caller's browser.
  headers.set('Cache-Control', 'no-store');
  return new Response(response.body, {status: response.status, headers});
}

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    if (url.pathname === '/health' && request.method === 'GET') {
      return Response.json({status: 'ok'}, {headers: {'Cache-Control': 'no-store'}});
    }
    if (url.pathname !== '/api/catalog') {
      return Response.json({error: 'not_found'}, {status: 404});
    }
    const language = url.searchParams.get('lang') || 'en';
    const category = url.searchParams.get('category') || 'network';
    if (!['en', 'fr'].includes(language) || !['network', 'printer'].includes(category) ||
        [...url.searchParams.keys()].some(key => !['lang', 'category'].includes(key)) ||
        url.searchParams.getAll('lang').length > 1 || url.searchParams.getAll('category').length > 1) {
      return Response.json({error: 'invalid_query'}, {status: 400, headers: {'Cache-Control': 'no-store'}});
    }
    if (request.method !== 'GET') {
      return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'GET'}});
    }
    return deliver(catalog(request, language, category), 'BYPASS');
  }
};
JS
npx wrangler dev --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
cat dev.log

Warten Sie vor den Anfragen, bis der Server bereit ist. Wenn der Start noch läuft, führen Sie cat dev.log erneut aus. Lassen Sie dieses Terminal geöffnet, damit seine Shell-Variablen verfügbar bleiben.

curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network"
curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network"

Beide Anfragen liefern 200 mit audience gleich public und unterschiedlichen UUIDs in generation. X-Lab-Cache: BYPASS bedeutet, dass dieser Handler den Cache weder gelesen noch beschrieben hat. Cache-Control: no-store auf der Clientseite hält den Browser- beziehungsweise Client-Cache aus dem Experiment heraus. Führen Sie die Verifizierung durch, bevor Sie diesen Ausgangszustand ersetzen.

Nur geeignete öffentliche Antworten zwischenspeichern

In diesem Schritt fügen Sie das Lesen und Speichern über die Cache API hinzu. Beenden Sie den aktuellen Entwicklungsprozess, den Sie mit jobs anzeigen. Das Beispiel geht davon aus, dass es sich um Job 1 handelt.

jobs
kill %1
cat > src/index.js <<'JS'
import {catalog} from './catalog.js';

function deliver(response, cacheStatus) {
  const headers = new Headers(response.headers);
  headers.set('X-Lab-Cache', cacheStatus);
  // This lab caches inside the Worker, not in the caller's browser.
  headers.set('Cache-Control', 'no-store');
  return new Response(response.body, {status: response.status, headers});
}

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    if (url.pathname === '/health' && request.method === 'GET') {
      return Response.json({status: 'ok'}, {headers: {'Cache-Control': 'no-store'}});
    }
    if (url.pathname !== '/api/catalog') {
      return Response.json({error: 'not_found'}, {status: 404});
    }
    const language = url.searchParams.get('lang') || 'en';
    const category = url.searchParams.get('category') || 'network';
    if (!['en', 'fr'].includes(language) || !['network', 'printer'].includes(category) ||
        [...url.searchParams.keys()].some(key => !['lang', 'category'].includes(key)) ||
        url.searchParams.getAll('lang').length > 1 || url.searchParams.getAll('category').length > 1) {
      return Response.json({error: 'invalid_query'}, {status: 400, headers: {'Cache-Control': 'no-store'}});
    }
    const keyUrl = new URL('/api/catalog', url.origin);
    keyUrl.searchParams.set('category', category);
    keyUrl.searchParams.set('lang', language);
    const key = new Request(keyUrl, {method: 'GET'});
    const cache = caches.default;
    if (request.method !== 'GET') {
      return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'GET'}});
    }
    // Decide eligibility before lookup: a warm public entry must not mask private work or errors.
    const bypass = ['Authorization', 'Cookie', 'X-Demo-Customer', 'X-Demo-Failure']
      .some(name => request.headers.has(name));
    if (bypass) return deliver(catalog(request, language, category), 'BYPASS');
    const cached = await cache.match(key);
    if (cached) return deliver(cached, 'HIT');
    const response = catalog(request, language, category);
    if (response.status !== 200 || response.headers.has('Set-Cookie')) {
      return deliver(response, 'BYPASS');
    }
    const stored = response.clone();
    stored.headers.set('Cache-Control', 'public, max-age=10');
    // Await completion here so the next request can observe the write.
    await cache.put(key, stored);
    return deliver(response, 'MISS');
  }
};
JS

Der Schlüssel verwendet den aktuellen Ursprung sowie eine festgelegte Route, Kategorie und Sprache. Die Reihenfolge der Parameter ist kanonisch, während beide Inhaltsdimensionen getrennt bleiben. Unbekannte Parameter und doppelte Dimensionen werden abgewiesen, statt die Bedeutung des Schlüssels stillschweigend zu verändern.

Die Eignung wird vor dem Lesen aus dem Cache geprüft. Authorization, Cookie und synthetische Kunden-Header umgehen einen bereits gefüllten öffentlichen Eintrag. Auch das Failure-Fixture umgeht das Lesen, damit ein Fehler nicht durch einen zwischengespeicherten Erfolg verborgen wird. Gespeichert wird nur eine erfolgreiche Antwort ohne Set-Cookie. Die Antwort wird geklont, weil Antworttextkörper Streams sind. Die gespeicherte Kopie erhält eine TTL von zehn Sekunden, und der Schreibvorgang wird abgewartet. Zurückgegebene Antworten behalten no-store; der interne Eintrag der Cache API besitzt seine eigene Cache-Richtlinie.

npx wrangler dev --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
cat dev.log

Warten Sie vor den Anfragen, bis der Server bereit ist. Wenn der Start noch läuft, führen Sie cat dev.log erneut aus. Lassen Sie dieses Terminal geöffnet, damit seine Shell-Variablen verfügbar bleiben.

curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network"
curl -i "http://127.0.0.1:8080/api/catalog?category=network&lang=en"
curl -i "http://127.0.0.1:8080/api/catalog?lang=fr&category=network"
curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=printer"

Führen Sie die ersten beiden Anfragen innerhalb von zehn Sekunden aus. Eine erste, noch nicht zwischengespeicherte Antwort meldet MISS; eine Wiederholung meldet HIT und enthält weiterhin dieselbe generation. Eine umgekehrte Reihenfolge der Query-Parameter ändert den Schlüssel nicht. Die französische Variante und die Druckervariante enthalten die angeforderten Dimensionen und besitzen unabhängige Einträge. Wenn die TTL während der Prüfung abläuft, wiederholen Sie ein Anfragepaar zügig. Gehen Sie nicht davon aus, dass Cache-Inhalte dauerhaft bestehen.

curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network" -H "X-Demo-Customer: alice"
curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network" -H "X-Demo-Customer: bob"
curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network" -H "Authorization: Bearer synthetic"
curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network" -H "Cookie: demo=synthetic"
curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network" -H "X-Demo-Failure: 1"
curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network"

Anfragen mit Kunden- oder Identitätsdaten liefern BYPASS und die passende synthetische Zielgruppe. Sie erhalten niemals das Ergebnis eines anderen Kunden. Der simulierte Fehler liefert auch bei warmen öffentlichen Daten 503 BYPASS. Eine spätere öffentliche Anfrage liefert weiterhin öffentliche Daten und nicht den Fehler. Führen Sie die Verifizierung bei laufendem lokalen Server durch. Dabei wird der Handler außerdem in einer isolierten lokalen Laufzeit ausgeführt; ein Cloud-Cache wird nicht verändert.

Einen lokalen Cache-Eintrag ablaufen lassen und invalidieren

In diesem Schritt fügen Sie eine authentifizierte Invalidierungsoperation für denselben kanonischen Schlüssel hinzu. Dabei handelt es sich um das Löschen in einem lokalen Rechenzentrum und nicht um eine globale Löschung. Beenden Sie den tatsächlichen Entwicklungsprozess, bevor Sie die Datei bearbeiten.

jobs
kill %1
umask 077
PURGE_TOKEN=$(openssl rand -hex 24)
printf 'PURGE_TOKEN=%s\n' "$PURGE_TOKEN" > .dev.vars
cat .gitignore

Halten Sie das synthetische Secret aus Git, öffentlichen Konfigurationen, URLs und Logs heraus. Es schützt die DELETE-Operation dieses Labs und ist kein Cloudflare API-Token.

cat > src/index.js <<'JS'
import {catalog} from './catalog.js';

function deliver(response, cacheStatus) {
  const headers = new Headers(response.headers);
  headers.set('X-Lab-Cache', cacheStatus);
  // This lab caches inside the Worker, not in the caller's browser.
  headers.set('Cache-Control', 'no-store');
  return new Response(response.body, {status: response.status, headers});
}

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    if (url.pathname === '/health' && request.method === 'GET') {
      return Response.json({status: 'ok'}, {headers: {'Cache-Control': 'no-store'}});
    }
    if (url.pathname !== '/api/catalog') {
      return Response.json({error: 'not_found'}, {status: 404});
    }
    const language = url.searchParams.get('lang') || 'en';
    const category = url.searchParams.get('category') || 'network';
    if (!['en', 'fr'].includes(language) || !['network', 'printer'].includes(category) ||
        [...url.searchParams.keys()].some(key => !['lang', 'category'].includes(key)) ||
        url.searchParams.getAll('lang').length > 1 || url.searchParams.getAll('category').length > 1) {
      return Response.json({error: 'invalid_query'}, {status: 400, headers: {'Cache-Control': 'no-store'}});
    }
    const keyUrl = new URL('/api/catalog', url.origin);
    keyUrl.searchParams.set('category', category);
    keyUrl.searchParams.set('lang', language);
    const key = new Request(keyUrl, {method: 'GET'});
    const cache = caches.default;
    if (request.method === 'DELETE') {
      if (!env.PURGE_TOKEN) return Response.json({error: 'purge_unconfigured'}, {status: 503});
      if (request.headers.get('Authorization') !== `Bearer ${env.PURGE_TOKEN}`) {
        return Response.json({error: 'unauthorized'}, {status: 401, headers: {'Cache-Control': 'no-store'}});
      }
      const invalidated = await cache.delete(key);
      return Response.json({invalidated, scope: 'this-location'}, {
        headers: {'Cache-Control': 'no-store', 'X-Lab-Cache': 'BYPASS'}
      });
    }
    if (request.method !== 'GET') {
      return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'GET, DELETE'}});
    }
    // Decide eligibility before lookup: a warm public entry must not mask private work or errors.
    const bypass = ['Authorization', 'Cookie', 'X-Demo-Customer', 'X-Demo-Failure']
      .some(name => request.headers.has(name));
    if (bypass) return deliver(catalog(request, language, category), 'BYPASS');
    const cached = await cache.match(key);
    if (cached) return deliver(cached, 'HIT');
    const response = catalog(request, language, category);
    if (response.status !== 200 || response.headers.has('Set-Cookie')) {
      return deliver(response, 'BYPASS');
    }
    const stored = response.clone();
    stored.headers.set('Cache-Control', 'public, max-age=10');
    // Await completion here so the next request can observe the write.
    await cache.put(key, stored);
    return deliver(response, 'MISS');
  }
};
JS

DELETE prüft die Zugangsdaten, bevor cache.delete mit demselben GET-Schlüssel aufgerufen wird, der auch zum Lesen und Speichern verwendet wird. Der zurückgegebene boolesche Wert gibt an, ob hier ein Eintrag vorhanden war. Eine nicht autorisierte Löschung darf den Eintrag nicht verändern. An einem anderen Standort können weiterhin eigene Einträge gefunden werden.

npx wrangler dev --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
cat dev.log

Warten Sie vor den Anfragen, bis der Server bereit ist. Wenn der Start noch läuft, führen Sie cat dev.log erneut aus. Lassen Sie dieses Terminal geöffnet, damit seine Shell-Variablen verfügbar bleiben.

curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network"
curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network"
curl -i -X DELETE "http://127.0.0.1:8080/api/catalog?lang=en&category=network"
curl -i -X DELETE "http://127.0.0.1:8080/api/catalog?lang=en&category=network" -H "Authorization: Bearer $PURGE_TOKEN"
curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network"

Ein nicht autorisiertes DELETE liefert 401. Ein gültiges DELETE liefert scope: this-location und bei einem noch gültigen Eintrag normalerweise invalidated: true. false ist ebenfalls aussagekräftig, wenn die kurze TTL bereits abgelaufen ist. Das nächste GET liefert MISS mit einer neuen generation. Wenn Sie true demonstrieren möchten, führen Sie unmittelbar vor dem autorisierten DELETE ein GET aus.

sleep 11
curl -i "http://127.0.0.1:8080/api/catalog?lang=en&category=network"

Nach elf Sekunden zeigt ein neues MISS den Ablauf ohne explizite Löschung. Führen Sie die Verifizierung bei laufendem Server durch: Eine isolierte Laufzeit prüft Wiederverwendung, die Trennung der Dimensionen, den Ausschluss privater und fehlerhafter Antworten, eine abgewiesene Löschung, eine erfolgreiche gezielte Löschung, den Erhalt eines anderen Schlüssels und den Ablauf. Diese kontrollierten lokalen Prüfungen liefern wiederholbare Nachweise, ohne einen globalen Cache-Zustand vorauszusetzen.

Die Dokumentation zur Cache API erläutert den Rechenzentrumsbereich, das Verhalten von Antwort-Headern und cache.delete. Die Cache API und das Plattform-Caching, das die Worker-Ausführung überspringt, sind voneinander getrennte Mechanismen.

Bereitstellen und Cache-Grenzen prüfen

In diesem Schritt stellen Sie den fertigen Handler in Ihrem Lernkonto bereit. Beenden Sie den lokalen Prozess, autorisieren Sie diese neue VM und prüfen Sie anschließend die Kontoidentität.

jobs
kill %1
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_tail:read

Öffnen Sie den angezeigten Geräte-Link beziehungsweise Code in Ihrem angemeldeten Browser. Prüfen Sie die unveränderten Berechtigungen und den Background Access und wählen Sie Ihr Lernkonto aus. Warten Sie, bis das Terminal den Erfolg meldet.

npx wrangler whoami --json

Bestätigen Sie den Namen des vorgesehenen Kontos. Ersetzen Sie unten YOUR_ACCOUNT_ID durch die tatsächliche ID. Lassen Sie Ihren eindeutigen Namen unverändert.

cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-07-30",
  "workers_dev": true,
  "preview_urls": false,
  "account_id": "YOUR_ACCOUNT_ID"
}
CONFIG
npx wrangler deploy
npx wrangler secret bulk .dev.vars
npx wrangler secret list

Die lokale Secret-Datei wird durch deploy nicht hochgeladen. Der ausdrückliche Bulk-Befehl erstellt PURGE_TOKEN als secret_text. Warten Sie nach der Bereitstellung kurz, damit die Änderung übernommen wird. Übernehmen Sie die tatsächliche öffentliche URL aus der Wrangler-Ausgabe in den folgenden Befehl.

APP_URL="https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev"
curl -i "$APP_URL/api/catalog?lang=en&category=network"
curl -i "$APP_URL/api/catalog?category=network&lang=en"
curl -i "$APP_URL/api/catalog?lang=fr&category=network"
curl -i "$APP_URL/api/catalog?lang=en&category=network" -H "X-Demo-Customer: alice"
curl -i "$APP_URL/api/catalog?lang=en&category=network" -H "X-Demo-Customer: bob"
curl -i "$APP_URL/api/catalog?lang=en&category=network" -H "X-Demo-Failure: 1"

Öffentliche Antworten müssen die angeforderte Sprache und Kategorie sowie die öffentliche Zielgruppe enthalten. Wiederholte Anfragen am selben Standort können innerhalb der TTL HIT anzeigen und dieselbe generation behalten. An einem anderen Standort oder nach Ablauf der TTL kann rechtmäßig MISS erscheinen. Leiten Sie aus zwei Anfragen keine global gemeinsam genutzten Inhalte ab. Private Anfragen müssen immer den Cache umgehen, und der Fehler muss 503 BYPASS liefern.

Öffnen Sie im selben Dashboard-Konto Compute → Workers & Pages und bestätigen Sie den exakten Worker sowie seine workers.dev-URL. Prüfen Sie mit der Verifizierung die Eigentümerschaft, die bereitgestellte Secret-Bindung und die Antwortgrenzen. Dabei wird keine Cloud-Invalidierung durchgeführt. Das Verhalten von cache.delete wurde lokal getestet; es handelt sich nicht um einen globalen Purge-Mechanismus. Wenn die Bereitstellung noch übernommen wird, warten Sie kurz und wiederholen Sie die Antwortprüfungen. Untersuchen Sie eine dauerhaft abweichende Antwort, statt sie zu akzeptieren.

Den temporären Worker löschen

In diesem Schritt entfernen Sie die Bereitstellung dieses Labs, während Sie noch autorisiert sind. Bestätigen Sie den eindeutigen Namen und das Konto und löschen Sie anschließend nur diesen Worker.

cat wrangler.jsonc
npx wrangler delete

Drücken Sie bei der Eingabeaufforderung für den passenden Namen die einzelne Taste y. Wrangler 4.131.1 kann nach der Löschung die bekannte Diagnose zur Authentifizierung von Legacy Workers Sites KV anzeigen. Erweitern Sie die Berechtigungen nicht und betrachten Sie diesen Fehler nicht als Beweis. Aktualisieren Sie das Dashboard und verwenden Sie die Verifizierung: Eine erfolgreiche authentifizierte Bestandsaufnahme muss zeigen, dass dieser genaue Worker nicht vorhanden ist. Lassen Sie das Lernkonto und seine Subdomain bestehen. Das Löschen des Workers bedeutet nicht, dass alle Cache-Einträge global gelöscht wurden; die synthetischen Einträge haben eine TTL von zehn Sekunden, und es darf keine laufende Anwendung zurückbleiben.

Lokales Secret entfernen und Verbindung trennen

In diesem Schritt entfernen Sie nach der bestätigten Löschung die lokal gespeicherten temporären Zugangsdaten und trennen anschließend diese VM.

rm .dev.vars
unset PURGE_TOKEN
npx wrangler logout
npx wrangler whoami --json

Verlangen Sie ausdrücklich loggedIn: false; der strukturierte Befehl ohne Authentifizierung kann mit einem Fehlercode beendet werden. Verwenden Sie die Verifizierung und beenden Sie anschließend die VM. Eine Browser-Anmeldung kann weiterhin bestehen. Das Abmelden oder Beenden der VM löscht keine Cloud-Bereitstellung.

Zusammenfassung

Sie haben die ungepufferte Kataloggenerierung durch explizites Caching öffentlicher Antworten ersetzt, die Trennung der Schlüssel für Sprache und Kategorie beibehalten und private sowie fehlerhafte Anfragen vor dem Lesen aus dem Cache umgangen. In einer kontrollierten lokalen Laufzeit haben Sie Einträge mit kurzer Lebensdauer und eine authentifizierte Invalidierung getestet. Anschließend haben Sie die Identität der bereitgestellten Anwendung und ihre Antwortgrenzen überprüft, ohne global gemeinsam genutzte Cache-Inhalte vorauszusetzen.

Die TTL der gespeicherten Kopie und die Cache-Richtlinie des aufrufenden Clients erfüllen unterschiedliche Zwecke. Eine bewusst festgelegte Eignung, vollständige Schlüssel und beobachtbare Antwortgenerationen machen diesen Unterschied überprüfbar. Vor dem Trennen der VM haben Sie die temporäre Bereitstellung und die lokalen Zugangsdaten entfernt.