forked from
anil.recoil.org/ocaml-requests
A batteries included HTTP/1.1 client in OCaml
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** HTTP Response Caching per RFC 9111
7
8 This module provides an in-memory cache for HTTP responses following RFC
9 9111 (HTTP Caching). It handles cache storage, validation, and freshness
10 calculations.
11
12 {2 Cache Keys}
13
14 Cache entries are keyed by method and effective request URI per RFC 9111.
15 Only safe methods (GET, HEAD) are cached by default.
16
17 {2 Examples}
18
19 {[
20 (* Create an in-memory cache *)
21 let cache = Cache.Memory.create ~max_entries:1000 () in
22
23 (* Store a response *)
24 Cache.Memory.store cache ~key ~response ~request_time ~response_time;
25
26 (* Lookup a cached response *)
27 match Cache.Memory.lookup cache ~key ~now with
28 | Some (entry, status) ->
29 Printf.printf "Cache %s: %s\n"
30 (match status with `Fresh -> "hit" | `Stale -> "stale")
31 entry.url
32 | None -> Printf.printf "Cache miss\n"
33 ]} *)
34
35val src : Logs.Src.t
36(** Log source for cache operations. *)
37
38(** {1 Cache Entry} *)
39
40type entry = {
41 url : string; (** The effective request URI *)
42 method_ : Method.t; (** HTTP method used for the request *)
43 status : int; (** Response status code *)
44 headers : Headers.t; (** Response headers *)
45 body : string; (** Response body *)
46 request_time : Ptime.t; (** When the request was initiated *)
47 response_time : Ptime.t; (** When the response was received *)
48 date_value : Ptime.t option; (** Parsed Date header value *)
49 age_value : int; (** Age header value (0 if not present) *)
50 cache_control : Cache_control.response; (** Parsed Cache-Control header *)
51 etag : string option; (** ETag header for validation *)
52 last_modified : string option; (** Last-Modified header for validation *)
53 vary_headers : (string * string) list;
54 (** Request header values for Vary matching *)
55 freshness_lifetime : int option;
56 (** Calculated freshness lifetime in seconds *)
57}
58(** A cached response entry *)
59
60(** Cache lookup result status *)
61type lookup_status =
62 | Fresh (** Entry is fresh and can be served directly *)
63 | Stale
64 (** Entry is stale but might be served with revalidation or max-stale *)
65
66(** {1 Cache Key} *)
67
68type key = {
69 method_key : Method.t;
70 uri : string;
71 vary_values : (string * string) list;
72 (** Values of Vary headers from request *)
73}
74(** A cache key for lookups *)
75
76val key :
77 method_:Method.t ->
78 uri:string ->
79 ?request_headers:Headers.t ->
80 ?vary:string list ->
81 unit ->
82 key
83(** Create a cache key.
84 @param method_ The HTTP method
85 @param uri The effective request URI
86 @param request_headers Request headers for Vary matching
87 @param vary List of header names from Vary response header. *)
88
89(** {1 In-Memory Cache} *)
90
91module Memory : sig
92 (** In-memory HTTP response cache using a Hashtbl. Thread-safe using
93 Eio.Mutex. *)
94
95 type t
96 (** The cache type *)
97
98 val create : ?max_entries:int -> unit -> t
99 (** Create a new in-memory cache.
100 @param max_entries Maximum number of entries (default 10000) *)
101
102 val store :
103 t ->
104 url:string ->
105 method_:Method.t ->
106 status:int ->
107 headers:Headers.t ->
108 body:string ->
109 request_time:Ptime.t ->
110 response_time:Ptime.t ->
111 ?request_headers:Headers.t ->
112 unit ->
113 bool
114 (** Store a response in the cache. Returns true if stored, false if not
115 cacheable.
116
117 @param url The effective request URI
118 @param method_ The HTTP method
119 @param status Response status code
120 @param headers Response headers
121 @param body Response body
122 @param request_time When the request was initiated
123 @param response_time When the response was received
124 @param request_headers Request headers for Vary matching *)
125
126 val lookup :
127 t ->
128 method_:Method.t ->
129 uri:string ->
130 ?request_headers:Headers.t ->
131 now:Ptime.t ->
132 unit ->
133 (entry * lookup_status) option
134 (** Look up a cached response.
135
136 @param method_ The HTTP method
137 @param uri The effective request URI
138 @param request_headers Request headers for Vary matching
139 @param now Current time for freshness check
140 @return Some (entry, status) if found, None if not in cache *)
141
142 val invalidate : t -> uri:string -> unit
143 (** Remove all entries for a URI (used after unsafe methods). *)
144
145 val clear : t -> unit
146 (** Clear all entries from the cache. *)
147
148 val size : t -> int
149 (** Return the number of entries in the cache. *)
150
151 val stats : t -> int * int * int
152 (** Return cache statistics: (hits, misses, stores). *)
153end
154
155(** {1 Cache Validation} *)
156
157val needs_validation : entry -> bool
158(** Check if a cached entry needs revalidation with the origin server. True if
159 must-revalidate or no-cache is set. *)
160
161val validation_headers : entry -> Headers.t
162(** Get headers to send for a conditional request. Includes If-None-Match (from
163 ETag) and/or If-Modified-Since (from Last-Modified). *)
164
165val is_not_modified : status:int -> bool
166(** Check if a response indicates the cached version is still valid (304). *)
167
168(** {1 Vary Header Support} *)
169
170val parse_vary : string -> string list
171(** Parse a Vary header value into a list of header names. *)
172
173val vary_matches :
174 cached_vary:(string * string) list -> request_headers:Headers.t -> bool
175(** Check if request headers match the cached Vary values. *)