http-cache-semantics

repository·main·Indexed 42 days ago

https://github.com/kornelski/http-cache-semantics

A JavaScript library for implementing HTTP cache semantics (RFC 7234/9111). It parses Cache-Control and other headers to help build correct HTTP caches and proxies, providing tools to determine if responses are storable, if cached responses satisfy new requests, and how to handle revalidation and stale-while-revalidate logic. Version 4.2.0.

Tokens
3.3K
Snippets
8
Records
17
Agent score
4%

What's inside http-cache-semantics

  1. How server `Date` headers are handled

    main

    To avoid issues with incorrect server timezones or clock drift (which can break techniques like the max-age=1 trick), this library does not trust server-supplied Date headers.

    Instead, max-age is interpreted relative to the time the response was received by the client, rather than the time it was sent by the server.

    Note: This behavior affects compliance with RFC 1149 networks.

  2. Perform cache revalidation

    main

    When a cache entry is stale, you can refresh it by contacting the origin server. Use these methods to handle the process correctly:

    1. Prepare the revalidation request

    Use policy.revalidationHeaders(newRequest) to get the correct headers (like If-None-Match or If-Modified-Since) to send to the server. This ensures the server can respond with a 304 Not Modified if the content hasn't changed.

    2. Update the policy after revalidation

    After receiving a response from the server, use policy.revalidatedPolicy(revalidationRequest, revalidationResponse) to update your cache. It returns:

    • policy: A new CachePolicy instance based on the revalidation response.
    • modified: A boolean. If true, the response body changed and you should use the new body. If false, the body is still valid (e.g., a 304 Not Modified was received) and you should reuse the old body.
  3. Configure CachePolicy options

    main

    When instantiating CachePolicy, you can pass an options object to fine-tune behavior:

    • shared (boolean, default: true): If true, evaluates from a shared cache perspective (respects s-maxage, private is not cacheable). If false, evaluates from a single-user cache perspective (private is cacheable, s-maxage is ignored). Recommended true for HTTP clients.
    • cacheHeuristic (number, default: 0.1): A fraction of the response's age used as a fallback cache duration (e.g., 0.1 means a file unchanged for 100 days is cached for 10 days).
    • immutableMinTimeToLive (number): Milliseconds to assume as default for Cache-Control: immutable responses. max-age still overrides this.
    • ignoreCargoCult (boolean, default: false): If true, ignores common anti-cache directives if non-standard pre-check and post-check directives are present.
  4. Initialize a CachePolicy

    main

    To manage HTTP cacheability, create a CachePolicy instance by providing a request and a response. Both objects must have a headers property where all header names are in lowercase.

    url, status, and method are optional and default to any URL, status 200, and the GET method respectively.

    const request = {
        url: '/',
        method: 'GET',
        headers: {
            accept: '*/*',
        },
    };
    
    const response = {
        status: 200,
        headers: {
            'cache-control': 'public, max-age=7234',
        },
    };
    
    const options = {
        shared: true,
        cacheHeuristic: 0.1,
        immutableMinTimeToLive: 24 * 3600 * 1000, // 24h
        ignoreCargoCult: false,
    };
    
    const policy = new CachePolicy(request, response, options);
  5. Determine cache entry lifetime with timeToLive()

    main

    The timeToLive() method returns a value in milliseconds suggesting how long the cache entry may be useful.

    Note: This is not a guarantee of freshness. Always use satisfiesWithoutRevalidation() to check if a response is actually usable. The value returned by timeToLive() may be longer than the response's max-age to accommodate stale-if-error and stale-while-revalidate logic.

  6. Serialize and deserialize CachePolicy

    main

    To persist a CachePolicy in a storage engine (like Redis or a file system), use the following methods:

    • policy.toObject(): Returns a plain JSON-serializable object.
    • CachePolicy.fromObject(json): Reconstructs a CachePolicy instance from a plain object.
  7. Get updated response headers for clients

    main

    When serving a cached response, you must call policy.responseHeaders() to get the correct set of headers. This is required to:

    1. Remove hop-by-hop headers (e.g., TE, Connection).
    2. Update the Age header to prevent doubling cache time.
    cachedResponse.headers = cachePolicy.responseHeaders();
  8. Verify if a cached response satisfies a new request

    main

    Use policy.satisfiesWithoutRevalidation(newRequest) to check if a cached response is still fresh and compatible with a new incoming request. This method accounts for Vary headers and other caching constraints.

    • If true: The response can be reused immediately without contacting the server. You must update the headers using policy.responseHeaders() before returning the response to the client.
    • If false: The response may be incompatible (different URL/method) or may require revalidation.
  9. Evaluate a request using evaluateRequest()

    main

    The evaluateRequest(newRequest) method is the high-level way to decide how to handle an incoming request against a cached policy. It returns an object that may contain revalidation and/or response properties.

    Return Object Shape

    {
        // If defined, you must send a request to the server.
        revalidation: {
            headers: {}, // HTTP headers to use for the revalidation request
            // If true, you MUST wait for the server response before using the cache.
            // If false, this is 'stale-while-revalidate' (use cache while updating in background).
            synchronous: boolean,
        },
        // If defined, you can use this cached response.
        response: {
            headers: {}, // Updated cached HTTP headers to use when responding to the client
        }
    }
  10. Understand the limitations of http-cache-semantics

    main

    The library currently has the following unimplemented features:

    • Merging of range requests: While it correctly supports If-Range by treating those requests as non-cacheable, it does not merge range requests.
    • Revalidation of multiple representations: The library does not support revalidating multiple representations at once.
  11. Handle revalidation with `revalidatedPolicy()`

    main

    When you perform a revalidation request to the origin server and receive a response (e.g., a 304 Not Modified), use revalidatedPolicy(request, response) to update your existing CachePolicy.

    Return Value

    Returns an object: { policy: CachePolicy, modified: boolean, matches: boolean }

    • policy: The new CachePolicy instance representing the updated state.
    • modified: true if the response body has changed and the old cached body is no longer valid. false if the body can still be reused (e.g., on a 304).
    • matches: true if the new response is a valid update for this cache entry.
    const { policy: newPolicy, modified, matches } = oldPolicy.revalidatedPolicy(request, revalidationResponse);
    
    if (matches && !modified) {
      // The server said 304: Update the policy but keep the old body
      await myCache.updatePolicy(url, newPolicy);
    } else if (matches && modified) {
      // The server provided a new body: Update both policy and body
      await myCache.set(url, newResponseBody, newPolicy);
    }