A batteries included HTTP/1.1 client in OCaml
at claude-test 218 lines 8.4 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 headers management with case-insensitive keys 7 8 This module provides an efficient implementation of HTTP headers with 9 case-insensitive header names as per RFC 7230. Headers can have multiple 10 values for the same key (e.g., multiple Set-Cookie headers). 11 12 {2 Examples} 13 14 {[ 15 let headers = 16 Headers.empty 17 |> Headers.content_type Mime.json 18 |> Headers.bearer "token123" 19 |> Headers.set "X-Custom" "value" 20 ]} 21*) 22 23(** Log source for header operations *) 24val src : Logs.Src.t 25 26type t 27(** Abstract header collection type. Headers are stored with case-insensitive 28 keys and maintain insertion order. *) 29 30(** {1 Creation and Conversion} *) 31 32val empty : t 33(** [empty] creates an empty header collection. *) 34 35val of_list : (string * string) list -> t 36(** [of_list pairs] creates headers from an association list. 37 Later entries override earlier ones for the same key. *) 38 39val to_list : t -> (string * string) list 40(** [to_list headers] converts headers to an association list. 41 The order of headers is preserved. *) 42 43(** {1 Header Injection Prevention} *) 44 45exception Invalid_header of { name: string; reason: string } 46(** Raised when a header name or value contains invalid characters (CR/LF) 47 that could enable HTTP request smuggling attacks. *) 48 49(** {1 Manipulation} *) 50 51val add : string -> string -> t -> t 52(** [add name value headers] adds a header value. Multiple values 53 for the same header name are allowed (e.g., for Set-Cookie). 54 55 @raise Invalid_header if the header name or value contains CR/LF characters 56 (to prevent HTTP header injection attacks). *) 57 58val set : string -> string -> t -> t 59(** [set name value headers] sets a header value, replacing any 60 existing values for that header name. 61 62 @raise Invalid_header if the header name or value contains CR/LF characters 63 (to prevent HTTP header injection attacks). *) 64 65val get : string -> t -> string option 66(** [get name headers] returns the first value for a header name, 67 or [None] if the header doesn't exist. *) 68 69val get_all : string -> t -> string list 70(** [get_all name headers] returns all values for a header name. 71 Returns an empty list if the header doesn't exist. *) 72 73val remove : string -> t -> t 74(** [remove name headers] removes all values for a header name. *) 75 76val mem : string -> t -> bool 77(** [mem name headers] checks if a header name exists. *) 78 79val merge : t -> t -> t 80(** [merge base override] merges two header collections. 81 Headers from [override] replace those in [base]. *) 82 83(** {1 Common Header Builders} 84 85 Convenience functions for setting common HTTP headers. 86*) 87 88val content_type : Mime.t -> t -> t 89(** [content_type mime headers] sets the Content-Type header. *) 90 91val content_length : int64 -> t -> t 92(** [content_length length headers] sets the Content-Length header. *) 93 94val accept : Mime.t -> t -> t 95(** [accept mime headers] sets the Accept header. *) 96 97val authorization : string -> t -> t 98(** [authorization value headers] sets the Authorization header with a raw value. *) 99 100val bearer : string -> t -> t 101(** [bearer token headers] sets the Authorization header with a Bearer token. 102 Example: [bearer "abc123"] sets ["Authorization: Bearer abc123"] *) 103 104val basic : username:string -> password:string -> t -> t 105(** [basic ~username ~password headers] sets the Authorization header with 106 HTTP Basic authentication (base64-encoded username:password). *) 107 108val user_agent : string -> t -> t 109(** [user_agent ua headers] sets the User-Agent header. *) 110 111val host : string -> t -> t 112(** [host hostname headers] sets the Host header. *) 113 114val cookie : string -> string -> t -> t 115(** [cookie name value headers] adds a cookie to the Cookie header. 116 Multiple cookies can be added by calling this function multiple times. *) 117 118val range : start:int64 -> ?end_:int64 -> unit -> t -> t 119(** [range ~start ?end_ () headers] sets the Range header for partial content. 120 Example: [range ~start:0L ~end_:999L ()] requests the first 1000 bytes. *) 121 122(** {1 HTTP 100-Continue Support} 123 124 Per Recommendation #7: Expect: 100-continue protocol for large uploads. 125 RFC 9110 Section 10.1.1 (Expect) *) 126 127val expect : string -> t -> t 128(** [expect value headers] sets the Expect header. 129 Example: [expect "100-continue"] for large request bodies. *) 130 131val expect_100_continue : t -> t 132(** [expect_100_continue headers] sets [Expect: 100-continue]. 133 Use this for large uploads to allow the server to reject the request 134 before the body is sent, saving bandwidth. *) 135 136(** {1 Cache Control Headers} 137 138 Per Recommendation #17 and #19: Response caching and conditional requests. 139 RFC 9111 (HTTP Caching), RFC 9110 Section 8.8.2-8.8.3 (Last-Modified, ETag) *) 140 141val if_none_match : string -> t -> t 142(** [if_none_match etag headers] sets the If-None-Match header for conditional requests. 143 The request succeeds only if the resource's ETag does NOT match. 144 Used with GET/HEAD to implement efficient caching (returns 304 Not Modified if matches). *) 145 146val if_match : string -> t -> t 147(** [if_match etag headers] sets the If-Match header for conditional requests. 148 The request succeeds only if the resource's ETag matches. 149 Used with PUT/DELETE for optimistic concurrency (prevents lost updates). *) 150 151val if_modified_since : string -> t -> t 152(** [if_modified_since date headers] sets the If-Modified-Since header. 153 The date should be in HTTP-date format (RFC 9110 Section 5.6.7). 154 Example: ["Sun, 06 Nov 1994 08:49:37 GMT"] *) 155 156val if_unmodified_since : string -> t -> t 157(** [if_unmodified_since date headers] sets the If-Unmodified-Since header. 158 The request succeeds only if the resource has NOT been modified since the date. *) 159 160val http_date_of_ptime : Ptime.t -> string 161(** [http_date_of_ptime time] formats a Ptime.t as an HTTP-date. 162 Format: "Sun, 06 Nov 1994 08:49:37 GMT" (RFC 9110 Section 5.6.7) *) 163 164val if_modified_since_ptime : Ptime.t -> t -> t 165(** [if_modified_since_ptime time headers] sets If-Modified-Since using a Ptime.t value. *) 166 167val if_unmodified_since_ptime : Ptime.t -> t -> t 168(** [if_unmodified_since_ptime time headers] sets If-Unmodified-Since using a Ptime.t value. *) 169 170val cache_control : string -> t -> t 171(** [cache_control directives headers] sets the Cache-Control header with a raw directive string. 172 Example: [cache_control "no-cache, max-age=3600"] *) 173 174val cache_control_directives : 175 ?max_age:int -> 176 ?max_stale:int option option -> 177 ?min_fresh:int -> 178 ?no_cache:bool -> 179 ?no_store:bool -> 180 ?no_transform:bool -> 181 ?only_if_cached:bool -> 182 unit -> t -> t 183(** [cache_control_directives ?max_age ?max_stale ?min_fresh ~no_cache ~no_store 184 ~no_transform ~only_if_cached () headers] builds a Cache-Control header 185 from individual directives (RFC 9111 request directives). 186 187 - [max_age]: Maximum age in seconds the client is willing to accept 188 - [max_stale]: Accept stale responses: 189 - [None]: omit max_stale entirely 190 - [Some None]: "max-stale" (accept any staleness) 191 - [Some (Some n)]: "max-stale=N" (accept n seconds staleness) 192 - [min_fresh]: Response must be fresh for at least n more seconds 193 - [no_cache]: Force revalidation with origin server 194 - [no_store]: Response must not be stored in cache 195 - [no_transform]: Intermediaries must not transform the response 196 - [only_if_cached]: Only return cached response, 504 if not available *) 197 198val etag : string -> t -> t 199(** [etag value headers] sets the ETag header (for responses). 200 Example: [etag "\"abc123\""] *) 201 202val last_modified : string -> t -> t 203(** [last_modified date headers] sets the Last-Modified header (for responses). 204 The date should be in HTTP-date format. *) 205 206val last_modified_ptime : Ptime.t -> t -> t 207(** [last_modified_ptime time headers] sets Last-Modified using a Ptime.t value. *) 208 209(** {1 Aliases} *) 210 211val get_multi : string -> t -> string list 212(** [get_multi] is an alias for {!get_all}. *) 213 214(** Pretty printer for headers *) 215val pp : Format.formatter -> t -> unit 216 217(** Brief pretty printer showing count only *) 218val pp_brief : Format.formatter -> t -> unit