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 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