HTTP Cache Semantics Policy

4.2.0 · active · verified Tue Apr 21

This library provides a robust implementation of HTTP caching logic as defined by RFC 7234/9111 (for user agents and shared caches) and RFC 5861 (for `stale-if-error` and `stale-while-revalidate`). It allows developers to determine the cacheability of HTTP responses and whether a stored response can satisfy a new request, taking into account complex factors like the `Vary` header, proxy revalidation, and authenticated responses. The current stable version is 4.2.0. The library helps in building correct HTTP caches and proxies by abstracting away the intricacies of cache control directives, providing methods like `storable()` to check if a response can be cached, and `satisfiesWithoutRevalidation()` to validate if a cached entry is suitable for a subsequent request. It offers fine-grained control via constructor options like `shared`, `cacheHeuristic`, and `immutableMinTimeToLive`.

Common errors

Warnings

Install

Imports

Quickstart

Demonstrates the basic workflow of `http-cache-semantics`: creating a `CachePolicy` from an initial request/response, checking its storability, caching it, and then using `satisfiesWithoutRevalidation()` to determine if a subsequent request can be served from the cache, respecting `Vary` headers and other HTTP rules.

import { CachePolicy } from 'http-cache-semantics';

// Simulate an initial request and its corresponding response
const initialRequest = {
    url: 'https://api.example.com/data/items',
    method: 'GET',
    headers: {
        'accept': 'application/json',
        'authorization': 'Bearer mysecrettoken'
    },
};

const initialResponse = {
    status: 200,
    headers: {
        'cache-control': 'public, max-age=3600, s-maxage=600',
        'content-type': 'application/json',
        'vary': 'Accept, Authorization', // Note: Library expects lowercase, but here we simulate a common server response
        'date': new Date().toUTCString(),
    },
    body: '{"id": 1, "name": "Cached Item"}',
};

// Options for the cache policy evaluation
const options = {
    shared: true, // Evaluate from a shared cache perspective (e.g., a proxy)
    cacheHeuristic: 0.1, // 10% of response's age as fallback TTL
    immutableMinTimeToLive: 24 * 3600 * 1000, // 24 hours for 'immutable'
};

// --- Step 1: Create and store the cache policy for an incoming response ---
// Ensure headers are lowercase before passing to CachePolicy
const lowercaseInitialRequest = { ...initialRequest, headers: Object.fromEntries(Object.entries(initialRequest.headers).map(([k, v]) => [k.toLowerCase(), v])) };
const lowercaseInitialResponse = { ...initialResponse, headers: Object.fromEntries(Object.entries(initialResponse.headers).map(([k, v]) => [k.toLowerCase(), v])) };

const policy = new CachePolicy(lowercaseInitialRequest, lowercaseInitialResponse, options);

if (!policy.storable()) {
    console.log("Initial response is not storable in the cache. Discarding.");
} else {
    const letsPretendThisIsSomeCache = new Map<string, { policy: CachePolicy, body: string }>();
    const timeToLive = policy.timeToLive();
    console.log(`Response is storable. Estimated time to live: ${timeToLive}ms`);

    // Store the policy object along with the response body
    letsPretendThisIsSomeCache.set(initialRequest.url, { policy, body: initialResponse.body });

    // --- Step 2: Later, an identical new request comes in ---
    const newRequest = {
        url: 'https://api.example.com/data/items',
        method: 'GET',
        headers: {
            'accept': 'application/json',
            'authorization': 'Bearer mysecrettoken'
        },
    };

    const cachedEntry = letsPretendThisIsSomeCache.get(newRequest.url);

    if (cachedEntry) {
        // Ensure headers are lowercase for the new request too
        const lowercaseNewRequest = { ...newRequest, headers: Object.fromEntries(Object.entries(newRequest.headers).map(([k, v]) => [k.toLowerCase(), v])) };

        if (cachedEntry.policy.satisfiesWithoutRevalidation(lowercaseNewRequest)) {
            // The cached response is valid for the new request without revalidation
            const responseHeaders = cachedEntry.policy.responseHeaders();
            console.log("Cached response can be used without revalidation.");
            console.log("Updated response headers for client (includes Age, removes private headers):", responseHeaders);
            // In a real application, you would send { headers: responseHeaders, body: cachedEntry.body } to the client.
        } else {
            console.log("Cache hit, but revalidation is required or response is not suitable for this new request.");
            // Implement revalidation logic using policy.revalidationHeaders() and policy.revalidatedPolicy()
        }
    } else {
        console.log("Cache miss. No entry found for this URL.");
        // Fetch fresh data
    }
}

view raw JSON →