A batteries included HTTP/1.1 client in OCaml
at main 175 lines 5.6 kB view raw
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. *)