Cache_controlHTTP 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
(* 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.tLog source for cache control operations
RFC 9111 Section 5.2.2: Cache-Control Response Directives
type response_directive = | Max_age of intmax-age=N - response is fresh for N seconds
*)| S_maxage of ints-maxage=N - shared cache max-age
*)| No_cache of string listno-cache=headers - must revalidate
| No_storeno-store - must not be stored
*)| No_transformno-transform - must not be transformed
*)| Must_revalidatemust-revalidate - stale must be revalidated
*)| Proxy_revalidateproxy-revalidate - shared caches must revalidate
*)| Must_understandmust-understand - RFC 9111
*)| Private of string listprivate=headers - only private cache
| Publicpublic - can be stored by any cache
*)| Immutableimmutable - will not change during freshness
*)| Stale_while_revalidate of intstale-while-revalidate=N
*)| Stale_if_error of intstale-if-error=N
*)| Response_extension of string * string optionUnknown directive
*)RFC 9111 Section 5.2.1: Cache-Control Request Directives
type request_directive = | Req_max_age of intmax-age=N
*)| Req_max_stale of int optionmax-stale=N
| Req_min_fresh of intmin-fresh=N
*)| Req_no_cacheno-cache
*)| Req_no_storeno-store
*)| Req_no_transformno-transform
*)| Req_only_if_cachedonly-if-cached
*)| Request_extension of string * string optionUnknown directive
*)type response = {max_age : int option;max-age directive value in seconds
*)s_maxage : int option;s-maxage directive value for shared caches
*)no_cache : string list option;None = not present, Some [] = present without headers, Some headers = must revalidate for these headers
no_store : bool;If true, the response must not be stored
*)no_transform : bool;If true, intermediaries must not transform the response
*)must_revalidate : bool;If true, stale responses must be revalidated
*)proxy_revalidate : bool;Like must_revalidate but only for shared caches
*)must_understand : bool;If true, cache must understand the caching rules
*)private_ : string list option;None = not present, Some [] = entirely private, Some headers = these headers are private
public : bool;If true, response may be stored by any cache
*)immutable : bool;If true, response will not change during freshness lifetime
*)stale_while_revalidate : int option;Seconds stale responses may be served while revalidating
*)stale_if_error : int option;Seconds stale responses may be served on error
*)extensions : (string * string option) list;Unknown directives for forward compatibility
*)}Parsed response Cache-Control header
type request = {req_max_age : int option;max-age directive - maximum age client will accept
*)req_max_stale : int option option;None = not present, Some None = accept any stale, Some (Some n) = accept stale up to n seconds
req_min_fresh : int option;min-fresh directive - response must be fresh for at least n more seconds
*)req_no_cache : bool;If true, force revalidation with origin server
*)req_no_store : bool;If true, response must not be stored
*)req_no_transform : bool;If true, intermediaries must not transform
*)req_only_if_cached : bool;If true, return cached response or 504 Gateway Timeout
*)req_extensions : (string * string option) list;Unknown directives for forward compatibility
*)}Parsed request Cache-Control header
val empty_response : responseAn empty response Cache-Control (no directives set)
val empty_request : requestAn empty request Cache-Control (no directives set)
val parse_response : string -> responseparse_response header_value parses a response Cache-Control header value. Unknown directives are preserved in extensions for forward compatibility.
val parse_request : string -> requestparse_request header_value parses a request Cache-Control header value. Unknown directives are preserved in req_extensions for forward compatibility.
RFC 9111 Section 4.2: Freshness
val freshness_lifetime :
response_cc:response ->
?expires:string ->
?date:string ->
unit ->
int optionfreshness_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)
Per RFC 9111 Section 4.2.3: Calculating Age.
type age_inputs = {date_value : Ptime.t option;Value of Date header (when response was generated)
*)age_value : int;Value of Age header in seconds (0 if not present)
*)request_time : Ptime.t;Time when the request was initiated
*)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 -> intcalculate_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
Per RFC 9111 Section 4.2.2: Calculating Heuristic Freshness.
Default heuristic fraction: 10% of time since Last-Modified. RFC 9111 recommends this as a typical value.
val heuristic_freshness :
?last_modified:string ->
response_time:Ptime.t ->
?fraction:float ->
?max_age:int ->
unit ->
int optionheuristic_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.
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 ->
boolcan_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).
val is_cacheable : response_cc:response -> status:int -> boolis_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:
val must_revalidate : response_cc:response -> boolmust_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 -> boolis_public ~response_cc returns true if the response may be stored in shared caches (CDNs, proxies).
val is_private : response_cc:response -> boolis_private ~response_cc returns true if the response may only be stored in private caches (browser cache).
val pp_response : Format.formatter -> response -> unitPretty print a parsed response Cache-Control
val pp_request : Format.formatter -> request -> unitPretty print a parsed request Cache-Control