Module Cache_control

HTTP Cache-Control header parsing per RFC 9111 (HTTP Caching)

This module provides parsing and representation of Cache-Control directives for both requests and responses. It supports all standard directives from RFC 9111 Section 5.2.

Per Recommendation #17: Response Caching with RFC 7234/9111 Compliance

Examples

  (* Parse response Cache-Control *)
  let cc = Cache_control.parse_response "max-age=3600, public" in
  Printf.printf "Max age: %d\n" (Option.get cc.max_age);

  (* Check if cacheable *)
  if Cache_control.is_cacheable ~response_cc:cc ~status:200 then
    Printf.printf "Response is cacheable\n"
val src : Logs.Src.t

Log source for cache control operations

Response Cache-Control Directives

RFC 9111 Section 5.2.2: Cache-Control Response Directives

type response_directive =
  1. | Max_age of int
    (*

    max-age=N - response is fresh for N seconds

    *)
  2. | S_maxage of int
    (*

    s-maxage=N - shared cache max-age

    *)
  3. | No_cache of string list
    (*

    no-cache=headers - must revalidate

    *)
  4. | No_store
    (*

    no-store - must not be stored

    *)
  5. | No_transform
    (*

    no-transform - must not be transformed

    *)
  6. | Must_revalidate
    (*

    must-revalidate - stale must be revalidated

    *)
  7. | Proxy_revalidate
    (*

    proxy-revalidate - shared caches must revalidate

    *)
  8. | Must_understand
    (*

    must-understand - RFC 9111

    *)
  9. | Private of string list
    (*

    private=headers - only private cache

    *)
  10. | Public
    (*

    public - can be stored by any cache

    *)
  11. | Immutable
    (*

    immutable - will not change during freshness

    *)
  12. | Stale_while_revalidate of int
    (*

    stale-while-revalidate=N

    *)
  13. | Stale_if_error of int
    (*

    stale-if-error=N

    *)
  14. | Response_extension of string * string option
    (*

    Unknown directive

    *)

Request Cache-Control Directives

RFC 9111 Section 5.2.1: Cache-Control Request Directives

type request_directive =
  1. | Req_max_age of int
    (*

    max-age=N

    *)
  2. | Req_max_stale of int option
    (*

    max-stale=N

    *)
  3. | Req_min_fresh of int
    (*

    min-fresh=N

    *)
  4. | Req_no_cache
    (*

    no-cache

    *)
  5. | Req_no_store
    (*

    no-store

    *)
  6. | Req_no_transform
    (*

    no-transform

    *)
  7. | Req_only_if_cached
    (*

    only-if-cached

    *)
  8. | Request_extension of string * string option
    (*

    Unknown directive

    *)

Parsed Cache-Control Types

type response = {
  1. max_age : int option;
    (*

    max-age directive value in seconds

    *)
  2. s_maxage : int option;
    (*

    s-maxage directive value for shared caches

    *)
  3. no_cache : string list option;
    (*

    None = not present, Some [] = present without headers, Some headers = must revalidate for these headers

    *)
  4. no_store : bool;
    (*

    If true, the response must not be stored

    *)
  5. no_transform : bool;
    (*

    If true, intermediaries must not transform the response

    *)
  6. must_revalidate : bool;
    (*

    If true, stale responses must be revalidated

    *)
  7. proxy_revalidate : bool;
    (*

    Like must_revalidate but only for shared caches

    *)
  8. must_understand : bool;
    (*

    If true, cache must understand the caching rules

    *)
  9. private_ : string list option;
    (*

    None = not present, Some [] = entirely private, Some headers = these headers are private

    *)
  10. public : bool;
    (*

    If true, response may be stored by any cache

    *)
  11. immutable : bool;
    (*

    If true, response will not change during freshness lifetime

    *)
  12. stale_while_revalidate : int option;
    (*

    Seconds stale responses may be served while revalidating

    *)
  13. stale_if_error : int option;
    (*

    Seconds stale responses may be served on error

    *)
  14. extensions : (string * string option) list;
    (*

    Unknown directives for forward compatibility

    *)
}

Parsed response Cache-Control header

type request = {
  1. req_max_age : int option;
    (*

    max-age directive - maximum age client will accept

    *)
  2. req_max_stale : int option option;
    (*

    None = not present, Some None = accept any stale, Some (Some n) = accept stale up to n seconds

    *)
  3. req_min_fresh : int option;
    (*

    min-fresh directive - response must be fresh for at least n more seconds

    *)
  4. req_no_cache : bool;
    (*

    If true, force revalidation with origin server

    *)
  5. req_no_store : bool;
    (*

    If true, response must not be stored

    *)
  6. req_no_transform : bool;
    (*

    If true, intermediaries must not transform

    *)
  7. req_only_if_cached : bool;
    (*

    If true, return cached response or 504 Gateway Timeout

    *)
  8. req_extensions : (string * string option) list;
    (*

    Unknown directives for forward compatibility

    *)
}

Parsed request Cache-Control header

Empty Values

val empty_response : response

An empty response Cache-Control (no directives set)

val empty_request : request

An empty request Cache-Control (no directives set)

Parsing Functions

val parse_response : string -> response

parse_response header_value parses a response Cache-Control header value. Unknown directives are preserved in extensions for forward compatibility.

val parse_request : string -> request

parse_request header_value parses a request Cache-Control header value. Unknown directives are preserved in req_extensions for forward compatibility.

Freshness Calculation

RFC 9111 Section 4.2: Freshness

val freshness_lifetime : response_cc:response -> ?expires:string -> ?date:string -> unit -> int option

freshness_lifetime ~response_cc ?expires ?date () calculates the freshness lifetime of a response in seconds, based on Cache-Control directives and optional Expires/Date headers.

Priority (per RFC 9111 Section 4.2.1): 1. max-age directive 2. Expires header minus Date header 3. Returns None if no explicit freshness (caller should use heuristics)

  • parameter response_cc

    Parsed Cache-Control from response

  • parameter expires

    Optional Expires header value (HTTP-date format)

  • parameter date

    Optional Date header value (HTTP-date format)

Age Calculation

Per RFC 9111 Section 4.2.3: Calculating Age.

type age_inputs = {
  1. date_value : Ptime.t option;
    (*

    Value of Date header (when response was generated)

    *)
  2. age_value : int;
    (*

    Value of Age header in seconds (0 if not present)

    *)
  3. request_time : Ptime.t;
    (*

    Time when the request was initiated

    *)
  4. response_time : Ptime.t;
    (*

    Time when the response was received

    *)
}

Inputs required for age calculation per RFC 9111 Section 4.2.3.

val calculate_age : inputs:age_inputs -> now:Ptime.t -> int

calculate_age ~inputs ~now calculates the current age of a cached response.

Per RFC 9111 Section 4.2.3:

apparent_age = max(0, response_time - date_value)
response_delay = response_time - request_time
corrected_age_value = age_value + response_delay
corrected_initial_age = max(apparent_age, corrected_age_value)
resident_time = now - response_time
current_age = corrected_initial_age + resident_time
  • returns

    Current age in seconds

Heuristic Freshness

Per RFC 9111 Section 4.2.2: Calculating Heuristic Freshness.

val default_heuristic_fraction : float

Default heuristic fraction: 10% of time since Last-Modified. RFC 9111 recommends this as a typical value.

val default_max_heuristic_age : int

Maximum heuristic freshness lifetime: 1 day (86400 seconds).

val heuristic_freshness : ?last_modified:string -> response_time:Ptime.t -> ?fraction:float -> ?max_age:int -> unit -> int option

heuristic_freshness ?last_modified ~response_time ?fraction ?max_age () calculates heuristic freshness lifetime when no explicit caching info provided.

Per RFC 9111 Section 4.2.2, caches MAY use heuristics when explicit freshness is not available. The typical heuristic is 10% of time since Last-Modified.

  • parameter last_modified

    Value of Last-Modified header

  • parameter response_time

    When the response was received

  • parameter fraction

    Fraction of (now - last_modified) to use (default 10%)

  • parameter max_age

    Maximum heuristic age in seconds (default 1 day)

  • returns

    Heuristic freshness lifetime in seconds, or None

val is_fresh : current_age:int -> freshness_lifetime:int -> bool

is_fresh ~current_age ~freshness_lifetime returns true if a cached response is still fresh (current_age < freshness_lifetime).

val can_serve_stale : request_cc:request -> current_age:int -> freshness_lifetime:int -> bool

can_serve_stale ~request_cc ~current_age ~freshness_lifetime returns true if a stale response can still be served based on request Cache-Control directives (specifically max-stale).

Cacheability Checks

val is_cacheable : response_cc:response -> status:int -> bool

is_cacheable ~response_cc ~status returns true if the response may be cached based on its Cache-Control directives and HTTP status code.

A response is cacheable if:

  • no-store is NOT present
  • Status is cacheable by default (200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501) OR explicit caching directive is present
val must_revalidate : response_cc:response -> bool

must_revalidate ~response_cc returns true if cached response must be revalidated with the origin server before use.

True if any of: must-revalidate, proxy-revalidate, or no-cache is set.

val is_public : response_cc:response -> bool

is_public ~response_cc returns true if the response may be stored in shared caches (CDNs, proxies).

val is_private : response_cc:response -> bool

is_private ~response_cc returns true if the response may only be stored in private caches (browser cache).

Pretty Printers

val pp_response : Format.formatter -> response -> unit

Pretty print a parsed response Cache-Control

val pp_request : Format.formatter -> request -> unit

Pretty print a parsed request Cache-Control