API Caching mit ETag und Cache-Control: Wie bedingte Anfragen Payloads reduzieren

Erfahren Sie, wie der Cache-Control-Header und die ETag-Validierung wiederholte API-Aufrufe in 304-Antworten verwandeln, verlorene Aktualisierungen mit If-Match verhindern und die Nutzlastgröße reduzieren.

Ashley Goolam

Ashley Goolam

31 August 2026

API Caching mit ETag und Cache-Control: Wie bedingte Anfragen Payloads reduzieren

Apidog für Unternehmen

On-Premises Bereitstellung

SSO & RBAC

SOC 2 konform

Apidog Enterprise entdecken

Ihre API sendet wahrscheinlich Tausende Male am Tag denselben JSON. Ein Client fragt GET /v1/products/42 an, erhält 18 KB zurück, fragt fünf Minuten später erneut an und erhält dieselben 18 KB. Nichts hat sich geändert. Sie haben trotzdem für die Bandbreite, die Serialisierung und das Lesen aus der Datenbank bezahlt.

HTTP hat dieses Problem bereits gelöst. Der Cache-Control-Header teilt Clients mit, wie lange eine Antwort frisch bleibt. Der ETag-Header gibt ihnen einen Fingerabdruck, um zu überprüfen, ob sich etwas geändert hat. Zusammen verwandeln sie wiederholte Anfragen in 304 Not Modified-Antworten mit leeren Bodies, und als Bonus können sie Ihre Schreibvorgänge vor verlorenen Aktualisierungen schützen. Dieselben Ideen treiben auch clientseitige Muster an; wenn Sie unseren Leitfaden zum Caching von API-Antworten in React gelesen haben, ist dies die Serverseite dieser Geschichte.

Dieser Leitfaden führt Sie durch die drei Schichten des HTTP-Cachings, zeigt den 304-Roundtrip Schritt für Schritt, entwirrt no-cache vs. no-store und endet mit funktionierendem Express-Code. Sie werden auch sehen, wie Sie all dies in Apidog überprüfen können, indem Sie bedingte Header senden und die 304 selbst bestätigen.

Schaltfläche

Die drei Schichten des HTTP-Cachings

HTTP-Caching für APIs gliedert sich in drei separate Entscheidungen. Teams geraten in Schwierigkeiten, wenn sie diese vermischen.

Schicht 1: Aktualität (Freshness). Wie lange kann ein Client eine Antwort wiederverwenden, ohne Sie überhaupt zu fragen? Das ist Cache-Control: max-age=60. Für 60 Sekunden liefert der Client die zwischengespeicherte Kopie lokal aus. Null Netzwerkverkehr. Dies ist der billigste mögliche Cache-Hit und auch der riskanteste, da der Client eine Änderung erst erkennen kann, wenn der Timer abläuft.

Schicht 2: Validierung (Validation). Sobald die Antwort veraltet ist, muss der Client sie nicht erneut herunterladen. Er fragt „Hat sich das geändert?“, indem er den Fingerabdruck sendet, den Sie ihm zuvor gegeben haben. Wenn die Ressource unverändert ist, antworten Sie mit 304 Not Modified und ohne Body. ETag mit If-None-Match ist die präzise Version davon; Last-Modified mit If-Modified-Since ist die ältere, zeitstempelbasierte Version mit Ein-Sekunden-Granularität.

Schicht 3: Invalidierung (Invalidation). Wenn sich Daten ändern, wie sterben veraltete Kopien? Private Client-Caches verfallen von selbst über max-age. Gemeinsam genutzte Caches und CDNs benötigen explizite Löschungen, kurze TTLs oder Direktiven wie stale-while-revalidate, die die Veralterung begrenzen.

Freshness spart am meisten, Validation fängt alles ab, was Freshness übersieht, und Invalidation hält beides ehrlich. Die meisten APIs benötigen alle drei.

Wie ein 304 Not Modified Roundtrip funktioniert

Hier ist der vollständige Zyklus für einen Produkt-Endpunkt, Schritt für Schritt.

Erste Anfrage. Der Client hat nichts zwischengespeichert:

GET /v1/products/42 HTTP/1.1
Host: api.example.com

Erste Antwort. Sie senden den Body plus Caching-Metadaten zurück:

HTTP/1.1 200 OK
Cache-Control: private, max-age=60
ETag: "33a64df551425fcc55e4d42a148795d9f2"
Content-Type: application/json
Content-Length: 18432

Der Client speichert den Body und das ETag. Für die nächsten 60 Sekunden kontaktiert er Sie überhaupt nicht.

Zweite Anfrage, nach 60 Sekunden. Die Kopie ist veraltet, daher revalidiert der Client:

GET /v1/products/42 HTTP/1.1
Host: api.example.com
If-None-Match: "33a64df551425fcc55e4d42a148795d9f2"

Zweite Antwort, unveränderte Ressource. Ihr Server vergleicht das eingehende ETag mit dem aktuellen. Sie stimmen überein, also:

HTTP/1.1 304 Not Modified
Cache-Control: private, max-age=60
ETag: "33a64df551425fcc55e4d42a148795d9f2"

Kein Body. Statt 18 KB besteht die Antwort aus wenigen hundert Byte Headern. Der Client markiert seine zwischengespeicherte Kopie für weitere 60 Sekunden als frisch und liefert sie aus. Hätte sich das Produkt geändert, würden Sie eine normale 200er-Antwort mit dem neuen Body und einem neuen ETag zurücksenden. Den Statuscode selbst haben wir in unserem 304 Not Modified Erklärer ausführlicher behandelt; die Kurzversion ist, dass eine 304 eine Cache-Anweisung ist, kein Fehler.

Die Wirtschaftlichkeit ist einfach. Ein bedingter GET kostet immer noch einen Roundtrip plus die Arbeit, die das aktuelle ETag berechnet. Was es eliminiert, ist die Übertragung der Nutzdaten und die clientseitige Neu-Parsen. Für große Listen-Endpunkte, die von mobilen Clients abgefragt werden, reduziert dies routinemäßig den API-Egress um 60 bis 90 Prozent.

Cache-Control-Direktiven, die für APIs wichtig sind

Cache-Control hat mehr als ein Dutzend Direktiven. Für JSON-APIs tragen fünf das meiste Gewicht.

no-store vs. no-cache. Dies ist der häufigste Caching-Bug in produktiven APIs, und er wirkt in beide Richtungen. no-store bedeutet „niemals in irgendeinen Cache schreiben“. Verwenden Sie es für wirklich sensible Nutzdaten: Token, Bankdaten, PII, die Sie nicht speichern dürfen. no-cache bedeutet fast das Gegenteil dessen, wonach es klingt: Caches DÜRFEN die Antwort speichern, müssen sich aber vor jeder Wiederverwendung beim Ursprung revalidieren. Gepaart mit einem ETag, ermöglicht Ihnen no-cache 304-Einsparungen bei jeder Anfrage, während gleichzeitig garantiert wird, dass Clients niemals veraltete Daten anzeigen. Teams, die no-store „vorsichtshalber“ auf alles anwenden, deaktivieren bedingte Anfragen vollständig und zahlen bei jedem Aufruf die vollen Nutzdatenkosten.

private. Markiert die Antwort als nur durch den Client des Endbenutzers cachebar, niemals durch gemeinsam genutzte Caches oder CDNs. Jede Antwort, die pro Benutzer variiert, was der Großteil des authentifizierten API-Verkehrs ist, sollte private tragen. Ohne dies könnte ein falsch konfigurierter Proxy die Kontodaten eines Benutzers einem anderen bereitstellen.

max-age. Aktualitätslebensdauer in Sekunden. Für APIs denken Sie klein: 30 bis 300 Sekunden decken die meisten Lese-Endpunkte ab. Sie versuchen nicht, Anfragen für einen Tag zu eliminieren; Sie versuchen, Bursts und Polling-Schleifen zu absorbieren.

stale-while-revalidate. Der pragmatische Mittelweg. Cache-Control: max-age=60, stale-while-revalidate=300 weist Caches an: liefere die veraltete Kopie für bis zu 5 zusätzliche Minuten aus, aktualisiere sie aber im Hintergrund. Benutzer erhalten sofortige Antworten; Ihr Ursprung wird kurz darauf aktualisiert. CDNs wie Cloudflare und Fastly unterstützen dies, ebenso wie Browser.

Ein sinnvoller Standard für einen authentifizierten Lese-Endpunkt sieht so aus:

Cache-Control: private, max-age=60, stale-while-revalidate=120
ETag: "9f8b2c41aa73e0d5"

Die vollständige Verhaltensspezifikation befindet sich in RFC 9111, das RFC 7234 als maßgebliches HTTP-Caching-Dokument ersetzt hat. Wenn sich ein CDN auf eine Weise verhält, die Sie überrascht, finden Sie die Antwort in diesem RFC.

Starke vs. schwache ETags

Ein ETag gibt es in zwei Varianten, und das Präfix W/ trennt sie.

Ein **starkes ETag** (`ETag: "33a64df551425fcc"`) verspricht Byte-für-Byte-Gleichheit. Zwei Antworten mit demselben starken ETag sind identisch, was starke ETags sicher für Byte-Range-Anfragen macht und für die Parallelitätskontrolle mit If-Match erforderlich ist.

Ein **schwaches ETag** (`ETag: W/"33a64df551425fcc"`) verspricht semantische Äquivalenz. Die Bytes können sich unterscheiden, vielleicht hat sich die Feldreihenfolge geändert oder ein Zeitstempelfeld wurde aktualisiert, aber die Bedeutung ist dieselbe, sodass ein Cache seine Kopie behalten kann.

Wo dies zum Problem wird: Kompressions-Middleware. Nginx und einige Frameworks schreiben starke ETags in schwache um, wenn sie eine Antwort on-the-fly gzippen, da die komprimierten Bytes nicht mehr mit den Originalen übereinstimmen. Wenn Ihre Parallelitätsprüfungen hinter einem Proxy mysteriös fehlschlagen, suchen Sie nach einem W/-Präfix, das nicht vorhanden war, als Ihr Anwendungsserver die Antwort gesendet hat.

Standardmäßig starke ETags verwenden, die auf dem unkomprimierten Body berechnet werden. Schwache ETags nur verwenden, wenn Sie wissentlich verschiedene Darstellungen derselben Daten bereitstellen.

ETags generieren: Body-Hash vs. Versionsspalte

Zwei Strategien dominieren, und die richtige hängt davon ab, wo die Kosten liegen.

Hash des Antwort-Bodys. Serialisieren Sie die Antwort, hashen Sie sie (MD5 oder SHA-1 ist hier in Ordnung; dies ist ein Fingerabdruck, keine Sicherheitsgrenze) und setzen Sie sie in Anführungszeichen. Es ist konstruktionsbedingt genau und erfordert keine Schemaänderungen. Der Haken: Sie erstellen die vollständige Antwort bei jeder Anfrage, einschließlich der 304er. Sie sparen Bandbreite, aber nicht Rechen- oder Datenbanklast.

Versionsspalte oder updated_at. Leiten Sie das ETag aus Daten ab, die Sie günstig abrufen können: ETag: "42-v17" aus dem Versionszähler der Zeile oder einem Hash von updated_at. Jetzt kostet eine bedingte Anfrage einen indizierten Lookup statt einer vollständigen Serialisierung. Der Haken: Die Version muss bei jeder Änderung, die die Antwort betrifft, erhöht werden, einschließlich Änderungen in verknüpften Tabellen. Verpassen Sie eine, und Sie liefern veraltete 304er aus, was der schlimmste Caching-Fehler ist, da er unsichtbar ist.

Beginnen Sie mit Body-Hashing. Es ist standardmäßig korrekt. Verschieben Sie stark frequentierte Endpunkte auf versionsbasierte ETags, wenn das Profiling zeigt, dass die Serialisierungskosten eine Rolle spielen.

ETags für optimistische Parallelität: If-Match und 412

Derselbe Fingerabdruck, der Bandbreite beim Lesen spart, verhindert verlorene Aktualisierungen beim Schreiben.

Das Problem verlorener Aktualisierungen: Zwei Administratoren laden gleichzeitig Produkt 42. Administrator A ändert den Preis und speichert. Administrator B korrigiert einen Tippfehler und speichert 30 Sekunden später, wobei die Preisänderung von A durch den veralteten Preis überschrieben wird, den B geladen hat. Niemand sieht einen Fehler. Die Daten sind stillschweigend falsch.

Die Lösung besteht darin, jede Aktualisierung von der Version abhängig zu machen, die der Client zuletzt gesehen hat:

PUT /v1/products/42 HTTP/1.1
If-Match: "33a64df551425fcc55e4d42a148795d9f2"
Content-Type: application/json

Der Server vergleicht If-Match mit dem aktuellen ETag der Ressource. Übereinstimmung: Aktualisierung anwenden, 200 mit einem neuen ETag zurückgeben. Keine Übereinstimmung, jemand anderes war zuerst da: Ablehnen mit 412 Precondition Failed und die Daten nicht antasten. Der Client ruft dann erneut ab, wendet seine Änderung auf die frische Version an und versucht es erneut. Strikte APIs gehen weiter und geben 428 Precondition Required bei jedem PUT zurück, das If-Match weglässt, wodurch die Sicherheitsprüfung obligatorisch wird.

Dies kostet Sie fast nichts zusätzlich, sobald ETags vorhanden sind, und es wandelt einen stillen Datenkorruptionsfehler in einen expliziten, wiederholbaren HTTP-Status um.

Was CDNs und Proxys mit diesen Headern machen

Gemeinsame Caches sitzen zwischen Ihrem Ursprung und Ihren Clients und lesen dieselben Header nach ihren eigenen Regeln.

Express-Beispiel: Rückgabe eines ETags und Umgang mit If-None-Match

Express setzt schwache ETags von selbst, aber die manuelle Handhabung gibt Ihnen starke ETags plus den 412 Schreibpfad:

import crypto from "node:crypto";
import express from "express";

const app = express();
app.use(express.json());

function etagFor(payload) {
  const hash = crypto.createHash("sha1")
    .update(JSON.stringify(payload))
    .digest("hex");
  return `"${hash}"`;
}

app.get("/v1/products/:id", async (req, res) => {
  const product = await db.products.find(req.params.id);
  const etag = etagFor(product);

  res.set("Cache-Control", "private, max-age=60, stale-while-revalidate=120");
  res.set("ETag", etag);

  if (req.get("If-None-Match") === etag) {
    return res.status(304).end();   // fingerprint matches: no body
  }
  res.json(product);
});

app.put("/v1/products/:id", async (req, res) => {
  const product = await db.products.find(req.params.id);
  const currentEtag = etagFor(product);
  const ifMatch = req.get("If-Match");

  if (!ifMatch) {
    return res.status(428).json({ error: "If-Match header required" });
  }
  if (ifMatch !== currentEtag) {
    return res.status(412).json({ error: "Resource changed since you fetched it" });
  }

  const updated = await db.products.update(req.params.id, req.body);
  res.set("ETag", etagFor(updated));
  res.json(updated);
});

Beachten Sie, dass der 304-Zweig immer noch Cache-Control- und ETag-Header sendet. Gemäß RFC 9111 aktualisiert eine 304 die Metadaten der gespeicherten Antwort, senden Sie also alles erneut, was der Client benötigt, um seine Kopie frisch zu halten.

Caching-Verhalten in Apidog überprüfen

Code, der korrekt aussieht, kann immer noch falsch cachen, sobald Middleware und Proxys ins Spiel kommen. Testen Sie auf HTTP-Ebene, nicht auf Code-Ebene.

In Apidog dauert die manuelle Überprüfung etwa eine Minute:

  1. Senden Sie GET /v1/products/42 und öffnen Sie das Panel für die Antwort-Header. Bestätigen Sie, dass ETag und Cache-Control vorhanden sind und das ETag in Anführungszeichen steht. Kopieren Sie den ETag-Wert.
  2. Fügen Sie bei derselben Anfrage einen Header If-None-Match mit dem kopierten Wert hinzu und senden Sie die Anfrage erneut. Sie sollten eine 304 mit leerem Body erhalten. Wenn Sie immer noch eine 200 erhalten, vergleicht Ihre Validierungsschicht keine Fingerabdrücke.
  3. Ändern Sie den Datensatz, senden Sie die Anfrage erneut und bestätigen Sie, dass Sie wieder eine 200 mit einem frischen ETag erhalten.

Um dies nach jeder Bereitstellung funktionsfähig zu halten, integrieren Sie denselben Ablauf in ein Testszenario. Verketten Sie zwei Anfragen: die erste extrahiert ETag aus den Antwort-Headern in eine Variable, die zweite sendet es als If-None-Match zurück und bestätigt, dass der Status 304 ist und der Body leer ist. Fügen Sie einen dritten Schritt für den Schreibpfad hinzu: Senden Sie einen PUT mit einem bewusst veralteten If-Match-Wert wie "deadbeefcafe1234" und bestätigen Sie 412. Unser Leitfaden zu API-Assertions behandelt die Assertions-Syntax für Statuscodes und Header.

Führen Sie dieses Szenario in CI aus, und ein Middleware-Upgrade, das Ihre ETags stillschweigend entfernt, wird zu einer fehlgeschlagenen Pipeline anstatt einer Bandbreitenrechnung. Laden Sie Apidog kostenlos herunter und erstellen Sie das Szenario für Ihre eigenen Endpunkte; es dauert länger, darüber zu lesen als es zusammenzuklicken.

FAQ

Was ist der Unterschied zwischen no-cache und no-store?

no-store verbietet das Caching vollständig: Es wird nichts auf Festplatte oder im Speicher geschrieben, sodass jede Anfrage die vollständige Antwort herunterlädt. no-cache erlaubt das Speichern, erzwingt aber eine Revalidierung vor jeder Wiederverwendung, sodass es, gepaart mit einem ETag, immer noch 304-Antworten und Nutzdateneinsparungen liefert. Verwenden Sie no-store nur für sensible Daten. Es überall zu verwenden, ist der teuerste Cache-Control-Fehler, den ein API-Team machen kann.

Funktionieren ETags mit POST?

Meistens nein, und das ist beabsichtigt. ETags beschreiben den Zustand einer Ressource unter einer URL, und POST erstellt normalerweise etwas Neues, anstatt einen stabilen Zustand zu lesen. Caches speichern POST-Antworten in der Praxis nicht. Die bedingten Header, die für Schreibvorgänge wichtig sind, sind If-Match bei PUT, PATCH und DELETE, wo das ETag vor verlorenen Aktualisierungen schützt. Wenn Sie versucht sind, POST-Antworten zu cachen, ist das normalerweise ein Zeichen dafür, dass die Operation ein GET sein sollte.

Macht eine 304-Antwort meine API schneller?

Es verkleinert die Übertragungen, was nicht dasselbe ist. Der Server empfängt die Anfrage immer noch, führt die Authentifizierung durch und berechnet das aktuelle ETag, daher hängen die CPU-Einsparungen am Ursprung davon ab, wie günstig Sie diesen Fingerabdruck ableiten können. Die Vorteile zeigen sich bei der Bandbreite, der mobilen Akkulaufzeit und der Renderzeit in langsamen Netzwerken. Messen Sie vorher und nachher; unser Leitfaden zum API-Performance-Testing zeigt, wie Sie Latenz und Durchsatz benchmarken, damit Sie den Unterschied beweisen können, anstatt zu raten.

Sollte ich ETag oder Last-Modified verwenden?

Senden Sie beides, wenn Sie können. ETag ist präziser: Es erfasst Änderungen im Sub-Sekunden-Bereich und inhaltsbezogene Unterschiede, die ein Zeitstempel übersieht, und If-None-Match hat Vorrang vor If-Modified-Since, wenn beide ankommen. Last-Modified bleibt nützlich als Fallback für ältere Clients und als Heuristik, die einige Caches zur Abschätzung der Aktualität verwenden. Wenn Sie nur eines versenden, versenden Sie ETag.

Praktizieren Sie API Design-First in Apidog

Entdecken Sie eine einfachere Möglichkeit, APIs zu erstellen und zu nutzen