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(** Requests - A modern HTTP/1.1 client library for OCaml
7
8 Requests is an HTTP client library for OCaml inspired by Python's requests
9 and urllib3 libraries. It provides a simple, intuitive API for making HTTP
10 requests while handling complexities like TLS configuration, connection
11 pooling, retries, and cookie management.
12
13 The library implements {{:https://datatracker.ietf.org/doc/html/rfc9110}RFC 9110}
14 (HTTP Semantics) and {{:https://datatracker.ietf.org/doc/html/rfc9112}RFC 9112}
15 (HTTP/1.1).
16
17 {2 Choosing an API}
18
19 The library offers two APIs optimized for different use cases:
20
21 {3 Session API (Requests.t)}
22
23 Use {!Requests.create} when you need:
24 - {b Cookie persistence} across requests (automatic session handling)
25 - {b Connection pooling} for efficient reuse of TCP/TLS connections
26 - {b Shared authentication} configured once for all requests
27 - {b Automatic retry handling} with exponential backoff
28 - {b Base URL support} for API clients ({{:https://datatracker.ietf.org/doc/html/rfc3986#section-5}RFC 3986 Section 5})
29
30 {[
31 open Eio_main
32
33 let () = run @@ fun env ->
34 Eio.Switch.run @@ fun sw ->
35
36 (* Create a session with connection pooling *)
37 let session = Requests.create ~sw env in
38
39 (* Configure authentication once for all requests *)
40 let session = Requests.set_auth session (Requests.Auth.bearer "your-token") in
41
42 (* Make concurrent requests - connections are reused *)
43 let (user, repos) = Eio.Fiber.both
44 (fun () -> Requests.get session "https://api.github.com/user")
45 (fun () -> Requests.get session "https://api.github.com/user/repos") in
46
47 (* Process responses *)
48 let user_data = Requests.Response.text user in
49 let repos_data = Requests.Response.text repos in
50 Printf.printf "User: %s\nRepos: %s\n" user_data repos_data
51
52 (* Resources auto-cleanup when switch closes *)
53 ]}
54
55 {3 One-Shot API (Requests.One)}
56
57 Use {!Requests.One} for stateless, single requests when you need:
58 - {b Minimal overhead} for one-off requests
59 - {b Fine-grained control} over TLS and connection settings
60 - {b No session state} (no cookies, no connection reuse)
61 - {b Direct streaming} without middleware
62
63 {[
64 open Eio_main
65
66 let () = run @@ fun env ->
67 Eio.Switch.run @@ fun sw ->
68
69 (* Direct stateless request - no session needed *)
70 let response = Requests.One.get ~sw
71 ~clock:env#clock ~net:env#net
72 "https://api.github.com" in
73
74 Printf.printf "Status: %d\n" (Requests.Response.status_code response);
75
76 (* POST with JSON body *)
77 let json_body = Jsont.Object ([
78 ("name", Jsont.String "Alice")
79 ], Jsont.Meta.none) in
80
81 let response = Requests.One.post ~sw
82 ~clock:env#clock ~net:env#net
83 ~headers:(Requests.Headers.empty
84 |> Requests.Headers.content_type Requests.Mime.json)
85 ~body:(Requests.Body.json json_body)
86 "https://api.example.com/users" in
87
88 Printf.printf "Created: %d\n" (Requests.Response.status_code response)
89 ]}
90
91 {2 Features}
92
93 - {b Simple API}: Intuitive functions for GET, POST, PUT, DELETE, etc.
94 - {b Authentication}: Built-in support for Basic, Bearer, Digest, and OAuth
95 - {b Streaming}: Upload and download large files efficiently
96 - {b Retries}: Automatic retry with exponential backoff (see {!Retry})
97 - {b Timeouts}: Configurable connection and read timeouts (see {!Timeout})
98 - {b Cookie Management}: Automatic cookie handling with persistence (see {!Cookeio} and {!Cookeio_jar})
99 - {b TLS/SSL}: Secure connections with certificate verification
100 - {b Connection Pooling}: Efficient TCP/TLS connection reuse (see {!Conpool})
101 - {b Error Handling}: Comprehensive error types and recovery (see {!module:Error})
102
103 {2 Related Libraries}
104
105 This library integrates with several companion libraries:
106
107 {ul
108 {- [Conpool] - TCP/IP connection pooling with retry logic and statistics}
109 {- [Cookeio] - HTTP cookie parsing and validation per RFC 6265}
110 {- [Cookeio_jar] - Cookie jar storage with XDG persistence}
111 {- [Xdge] - XDG Base Directory support for cookie persistence paths}}
112
113 {2 Error Handling}
114
115 The Requests library uses exceptions as its primary error handling mechanism,
116 following Eio's structured concurrency model. This approach ensures that
117 errors are propagated cleanly through the switch hierarchy.
118
119 {b Exception-Based Errors:}
120
121 All request functions may raise [Eio.Io] exceptions with {!Error.E} payload:
122 - [Error.Timeout]: Request exceeded timeout limit
123 - [Error.Dns_resolution_failed], [Error.Tcp_connect_failed]: Network connection failed
124 - [Error.Too_many_redirects]: Exceeded maximum redirect count
125 - [Error.Http_error]: HTTP error response received (4xx/5xx status)
126 - [Error.Tls_handshake_failed]: TLS/SSL connection error
127 - [Error.Authentication_failed]: Authentication failed
128
129 {b Note on HTTP Status Codes:}
130
131 By default, the library does {b NOT} raise exceptions for HTTP error status
132 codes (4xx, 5xx). The response is returned normally and you should check
133 the status code explicitly:
134
135 {[
136 let resp = Requests.get req "https://api.example.com/data" in
137 if Requests.Response.ok resp then
138 (* Success: 2xx status *)
139 let body = Requests.Response.body resp |> Eio.Flow.read_all in
140 process_success body
141 else
142 (* Error: non-2xx status *)
143 let status = Requests.Response.status_code resp in
144 handle_error status
145 ]}
146
147 To automatically retry on certain HTTP status codes, configure retry behavior:
148
149 {[
150 let retry_config = Requests.Retry.create_config
151 ~max_retries:3
152 ~status_forcelist:[429; 500; 502; 503; 504] (* Retry these codes *)
153 () in
154 let req = Requests.create ~sw ~retry:retry_config env in
155 ]}
156
157 {b Catching Exceptions:}
158
159 {[
160 try
161 let resp = Requests.get req url in
162 handle_success resp
163 with
164 | Requests.Error.Timeout ->
165 (* Handle timeout specifically *)
166 retry_with_longer_timeout ()
167 | Requests.Error.ConnectionError msg ->
168 (* Handle connection errors *)
169 log_error "Connection failed: %s" msg
170 | exn ->
171 (* Handle other errors *)
172 log_error "Unexpected error: %s" (Printexc.to_string exn)
173 ]}
174
175 The {!module:Error} module also provides a Result-based API for functional error
176 handling, though the primary API uses exceptions for better integration
177 with Eio's structured concurrency.
178
179 {2 Common Use Cases}
180
181 {b Working with JSON APIs:}
182 {[
183 let response = Requests.post req "https://api.example.com/data"
184 ~body:(Requests.Body.json {|{"key": "value"}|}) in
185 let body_text =
186 Requests.Response.body response
187 |> Eio.Flow.read_all in
188 print_endline body_text
189 (* Response auto-closes with switch *)
190 ]}
191
192 {b File uploads:}
193 {[
194 let body = Requests.Body.multipart [
195 { name = "file"; filename = Some "document.pdf";
196 content_type = Requests.Mime.pdf;
197 content = `File (Eio.Path.(fs / "document.pdf")) };
198 { name = "description"; filename = None;
199 content_type = Requests.Mime.text_plain;
200 content = `String "Important document" }
201 ] in
202 let response = Requests.post req "https://example.com/upload"
203 ~body
204 (* Response auto-closes with switch *)
205 ]}
206
207 {b Streaming downloads:}
208 {[
209 Requests.One.download ~sw ~client
210 "https://example.com/large-file.zip"
211 ~sink:(Eio.Path.(fs / "download.zip" |> sink))
212 ]}
213
214*)
215
216(** {1 Main API}
217
218 The main Requests API provides stateful HTTP clients with automatic cookie
219 management and persistent configuration. Requests execute synchronously by default.
220 Use Eio.Fiber.both or Eio.Fiber.all for concurrent execution.
221*)
222
223type t
224(** A stateful HTTP client that maintains cookies, auth, configuration, and
225 connection pools across requests. The clock and network resources are
226 existentially quantified and hidden behind this abstract type. *)
227
228(** {2 TLS Configuration} *)
229
230type tls_version =
231 | TLS_1_2 (** TLS 1.2 minimum (default, widely compatible) *)
232 | TLS_1_3 (** TLS 1.3 minimum (most secure, may not work with older servers) *)
233(** Minimum TLS version to require for HTTPS connections.
234 Per Recommendation #6: Allow enforcing minimum TLS version for security. *)
235
236(** {2 Creation and Configuration} *)
237
238val create :
239 sw:Eio.Switch.t ->
240 ?http_pool:unit Conpool.t ->
241 ?https_pool:unit Conpool.t ->
242 ?cookie_jar:Cookeio_jar.t ->
243 ?default_headers:Headers.t ->
244 ?auth:Auth.t ->
245 ?timeout:Timeout.t ->
246 ?follow_redirects:bool ->
247 ?max_redirects:int ->
248 ?verify_tls:bool ->
249 ?tls_config:Tls.Config.client ->
250 ?min_tls_version:tls_version ->
251 ?max_connections_per_host:int ->
252 ?connection_idle_timeout:float ->
253 ?connection_lifetime:float ->
254 ?retry:Retry.config ->
255 ?persist_cookies:bool ->
256 ?xdg:Xdge.t ->
257 ?auto_decompress:bool ->
258 ?expect_100_continue:Expect_continue.config ->
259 ?base_url:string ->
260 ?xsrf_cookie_name:string option ->
261 ?xsrf_header_name:string ->
262 ?proxy:Proxy.config ->
263 ?allow_insecure_auth:bool ->
264 < clock: _ Eio.Time.clock; net: _ Eio.Net.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > ->
265 t
266(** Create a new requests instance with persistent state and connection pooling.
267 All resources are bound to the provided switch and will be cleaned up automatically.
268
269 @param sw Switch for resource management
270 @param http_pool Optional pre-configured HTTP {!Conpool.t} connection pool (creates new if not provided)
271 @param https_pool Optional pre-configured HTTPS {!Conpool.t} connection pool (creates new if not provided)
272 @param cookie_jar {!Cookeio_jar.t} cookie storage (default: empty in-memory jar)
273 @param default_headers Headers included in every request
274 @param auth Default authentication
275 @param timeout Default timeout configuration
276 @param follow_redirects Whether to follow HTTP redirects (default: true)
277 @param max_redirects Maximum redirects to follow (default: 10)
278 @param verify_tls Whether to verify TLS certificates (default: true)
279 @param tls_config Custom TLS configuration for HTTPS pool (default: system CA certs)
280 @param min_tls_version Minimum TLS version to require (default: TLS_1_2)
281 @param max_connections_per_host Maximum pooled connections per host:port (default: 10)
282 @param connection_idle_timeout Max idle time before closing pooled connection (default: 60s)
283 @param connection_lifetime Max lifetime of any pooled connection (default: 300s)
284 @param retry Retry configuration for failed requests
285 @param persist_cookies Whether to persist cookies to disk via {!Cookeio_jar} (default: false)
286 @param xdg {!Xdge.t} XDG directory context for cookies (required if persist_cookies=true)
287 @param auto_decompress Whether to automatically decompress gzip/deflate responses (default: true)
288 @param expect_100_continue HTTP 100-continue configuration (default: [`Threshold 1_048_576L]).
289 Use [`Disabled] to never send Expect: 100-continue,
290 [`Always] to always send it for requests with bodies, or
291 [`Threshold n] to send it only for bodies >= n bytes.
292 @param base_url Base URL for relative paths (per Recommendation #11). Relative URLs are resolved against this.
293 @param xsrf_cookie_name Cookie name to extract XSRF token from (default: Some "XSRF-TOKEN", per Recommendation #24). Set to None to disable.
294 @param xsrf_header_name Header name to inject XSRF token into (default: "X-XSRF-TOKEN")
295 @param proxy HTTP/HTTPS proxy configuration. When set, requests are routed through the proxy.
296 HTTP requests use absolute-URI form (RFC 9112 Section 3.2.2).
297 HTTPS requests use CONNECT tunneling (RFC 9110 Section 9.3.6).
298 @param allow_insecure_auth Allow Basic/Bearer/Digest authentication over plaintext HTTP (default: false).
299 Per {{:https://datatracker.ietf.org/doc/html/rfc7617#section-4}RFC 7617 Section 4} and
300 {{:https://datatracker.ietf.org/doc/html/rfc6750#section-5.1}RFC 6750 Section 5.1},
301 these auth schemes transmit credentials that SHOULD be protected by TLS.
302 {b Only set to [true] for local development or testing environments.}
303 Example for local dev server:
304 {[
305 let session = Requests.create ~sw ~allow_insecure_auth:true env in
306 let session = Requests.set_auth session (Requests.Auth.basic ~username:"dev" ~password:"dev") in
307 (* Can now make requests to http://localhost:8080 with Basic auth *)
308 ]}
309*)
310
311(** {2 Configuration Management} *)
312
313val set_default_header : t -> string -> string -> t
314(** Add or update a default header. Returns a new session with the updated header.
315 The original session's connection pools are shared. *)
316
317val remove_default_header : t -> string -> t
318(** Remove a default header. Returns a new session without the header. *)
319
320val set_auth : t -> Auth.t -> t
321(** Set default authentication. Returns a new session with auth configured. *)
322
323val clear_auth : t -> t
324(** Clear authentication. Returns a new session without auth. *)
325
326val set_timeout : t -> Timeout.t -> t
327(** Set default timeout. Returns a new session with the timeout configured. *)
328
329val set_retry : t -> Retry.config -> t
330(** Set retry configuration. Returns a new session with retry configured. *)
331
332(** {2 Request Methods}
333
334 All request methods execute synchronously. To make concurrent requests,
335 you must explicitly use Eio.Fiber.both or Eio.Fiber.all.
336 The response will auto-close when the parent switch closes.
337
338 Example of concurrent requests using Fiber.both:
339 {[
340 let req = Requests.create ~sw env in
341
342 (* Use Fiber.both for two concurrent requests *)
343 let (r1, r2) = Eio.Fiber.both
344 (fun () -> Requests.get req "https://api1.example.com")
345 (fun () -> Requests.post req "https://api2.example.com" ~body)
346 in
347
348 (* Process responses *)
349 let body1 = Response.body r1 |> Eio.Flow.read_all in
350 let body2 = Response.body r2 |> Eio.Flow.read_all in
351 ]}
352
353 Example using Fiber.all for multiple requests:
354 {[
355 let req = Requests.create ~sw env in
356
357 (* Use Fiber.all for multiple concurrent requests *)
358 let urls = [
359 "https://api1.example.com";
360 "https://api2.example.com";
361 "https://api3.example.com";
362 ] in
363
364 let responses = ref [] in
365 Eio.Fiber.all [
366 (fun () -> responses := Requests.get req (List.nth urls 0) :: !responses);
367 (fun () -> responses := Requests.get req (List.nth urls 1) :: !responses);
368 (fun () -> responses := Requests.get req (List.nth urls 2) :: !responses);
369 ];
370
371 (* Process all responses *)
372 List.iter (fun r ->
373 let body = Response.body r |> Eio.Flow.read_all in
374 print_endline body
375 ) !responses
376 ]}
377
378 Example using Promise for concurrent requests with individual control:
379 {[
380 let req = Requests.create ~sw env in
381
382 (* Start requests in parallel using promises *)
383 let p1, r1 = Eio.Promise.create () in
384 let p2, r2 = Eio.Promise.create () in
385 let p3, r3 = Eio.Promise.create () in
386
387 Eio.Fiber.fork ~sw (fun () ->
388 Eio.Promise.resolve r1 (Requests.get req "https://api1.example.com")
389 );
390 Eio.Fiber.fork ~sw (fun () ->
391 Eio.Promise.resolve r2 (Requests.post req "https://api2.example.com" ~body)
392 );
393 Eio.Fiber.fork ~sw (fun () ->
394 Eio.Promise.resolve r3 (Requests.get req "https://api3.example.com")
395 );
396
397 (* Wait for all promises and process *)
398 let resp1 = Eio.Promise.await p1 in
399 let resp2 = Eio.Promise.await p2 in
400 let resp3 = Eio.Promise.await p3 in
401
402 (* Process responses *)
403 let body1 = Response.body resp1 |> Eio.Flow.read_all in
404 let body2 = Response.body resp2 |> Eio.Flow.read_all in
405 let body3 = Response.body resp3 |> Eio.Flow.read_all in
406 ]}
407*)
408
409val request :
410 t ->
411 ?headers:Headers.t ->
412 ?body:Body.t ->
413 ?auth:Auth.t ->
414 ?timeout:Timeout.t ->
415 ?follow_redirects:bool ->
416 ?max_redirects:int ->
417 ?path_params:(string * string) list ->
418 method_:Method.t ->
419 string ->
420 Response.t
421(** Make a concurrent HTTP request.
422 @param path_params List of (key, value) pairs for URL template substitution (per Recommendation #29).
423 Example: [request ~path_params:[("id", "123")] ~method_:`GET "/users/{id}"] *)
424
425val get :
426 t ->
427 ?headers:Headers.t ->
428 ?auth:Auth.t ->
429 ?timeout:Timeout.t ->
430 ?params:(string * string) list ->
431 ?path_params:(string * string) list ->
432 string ->
433 Response.t
434(** Concurrent GET request.
435 @param params Query parameters to append to URL
436 @param path_params Path template substitutions (e.g., ["/users/{id}"] with [("id", "123")]) *)
437
438val post :
439 t ->
440 ?headers:Headers.t ->
441 ?body:Body.t ->
442 ?auth:Auth.t ->
443 ?timeout:Timeout.t ->
444 ?path_params:(string * string) list ->
445 string ->
446 Response.t
447(** Concurrent POST request *)
448
449val put :
450 t ->
451 ?headers:Headers.t ->
452 ?body:Body.t ->
453 ?auth:Auth.t ->
454 ?timeout:Timeout.t ->
455 ?path_params:(string * string) list ->
456 string ->
457 Response.t
458(** Concurrent PUT request *)
459
460val patch :
461 t ->
462 ?headers:Headers.t ->
463 ?body:Body.t ->
464 ?auth:Auth.t ->
465 ?timeout:Timeout.t ->
466 ?path_params:(string * string) list ->
467 string ->
468 Response.t
469(** Concurrent PATCH request *)
470
471val delete :
472 t ->
473 ?headers:Headers.t ->
474 ?auth:Auth.t ->
475 ?timeout:Timeout.t ->
476 ?path_params:(string * string) list ->
477 string ->
478 Response.t
479(** Concurrent DELETE request *)
480
481val head :
482 t ->
483 ?headers:Headers.t ->
484 ?auth:Auth.t ->
485 ?timeout:Timeout.t ->
486 ?path_params:(string * string) list ->
487 string ->
488 Response.t
489(** Concurrent HEAD request *)
490
491val options :
492 t ->
493 ?headers:Headers.t ->
494 ?auth:Auth.t ->
495 ?timeout:Timeout.t ->
496 ?path_params:(string * string) list ->
497 string ->
498 Response.t
499(** Concurrent OPTIONS request *)
500
501(** {2 Cookie Management} *)
502
503val cookies : t -> Cookeio_jar.t
504(** Get the cookie jar for direct manipulation *)
505
506val clear_cookies : t -> unit
507(** Clear all cookies *)
508
509(** {2 Proxy Configuration} *)
510
511val set_proxy : t -> Proxy.config -> t
512(** Set HTTP/HTTPS proxy configuration. Returns a new session with proxy configured.
513 When set, requests are routed through the proxy:
514 - HTTP requests use absolute-URI form (RFC 9112 Section 3.2.2)
515 - HTTPS requests use CONNECT tunneling (RFC 9110 Section 9.3.6)
516
517 Example:
518 {[
519 let proxy = Proxy.http ~port:8080 "proxy.example.com" in
520 let session = Requests.set_proxy session proxy
521 ]} *)
522
523val clear_proxy : t -> t
524(** Remove proxy configuration. Returns a new session without proxy. *)
525
526val proxy : t -> Proxy.config option
527(** Get the current proxy configuration, if any. *)
528
529(** {1 Cmdliner Integration} *)
530
531module Cmd : sig
532 (** Cmdliner integration for Requests configuration.
533
534 This module provides command-line argument handling for configuring
535 HTTP requests, including XDG directory paths, timeouts, retries,
536 proxy settings, and other parameters.
537
538 {2 Source Tracking}
539
540 Configuration values include source tracking to indicate where
541 each value came from (command line, environment variable, or default).
542 This enables transparent debugging and helps users understand
543 how their configuration was resolved.
544
545 {[
546 let config = ... in
547 if show_sources then
548 Format.printf "%a@." (Cmd.pp_config ~show_sources:true) config
549 ]} *)
550
551 (** {2 Source Tracking Types} *)
552
553 (** Source of a configuration value.
554 Tracks where each configuration value originated from for debugging
555 and transparency. *)
556 type source =
557 | Default (** Value from hardcoded default *)
558 | Env of string (** Value from environment variable (stores var name) *)
559 | Cmdline (** Value from command-line argument *)
560
561 (** Wrapper for values with source tracking *)
562 type 'a with_source = {
563 value : 'a; (** The actual configuration value *)
564 source : source; (** Where the value came from *)
565 }
566
567 (** Proxy configuration from command line and environment *)
568 type proxy_config = {
569 proxy_url : string with_source option; (** Proxy URL (from HTTP_PROXY/HTTPS_PROXY/etc) *)
570 no_proxy : string with_source option; (** NO_PROXY patterns *)
571 }
572
573 (** {2 Configuration Type} *)
574
575 (** Configuration from command line and environment.
576 All values include source tracking for debugging. *)
577 type config = {
578 xdg : Xdge.t * Xdge.Cmd.t; (** XDG paths and their sources *)
579 persist_cookies : bool with_source; (** Whether to persist cookies *)
580 verify_tls : bool with_source; (** Whether to verify TLS certificates *)
581 timeout : float option with_source; (** Request timeout in seconds *)
582 max_retries : int with_source; (** Maximum number of retries *)
583 retry_backoff : float with_source; (** Retry backoff factor *)
584 follow_redirects : bool with_source; (** Whether to follow redirects *)
585 max_redirects : int with_source; (** Maximum number of redirects *)
586 user_agent : string option with_source; (** User-Agent header *)
587 verbose_http : bool with_source; (** Enable verbose HTTP-level logging *)
588 proxy : proxy_config; (** Proxy configuration *)
589 }
590
591 val create : config -> < clock: _ Eio.Time.clock; net: _ Eio.Net.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> Eio.Switch.t -> t
592 (** [create config env sw] creates a requests instance from command-line configuration.
593 Proxy configuration from the config is applied automatically. *)
594
595 (** {2 Individual Terms}
596
597 Each term returns a value with source tracking to indicate whether
598 the value came from the command line, environment, or default.
599 Source precedence: Cmdline > Env > Default *)
600
601 val persist_cookies_term : string -> bool with_source Cmdliner.Term.t
602 (** Term for [--persist-cookies] flag with app-specific env var.
603 Env var: [{APP_NAME}_PERSIST_COOKIES] *)
604
605 val verify_tls_term : string -> bool with_source Cmdliner.Term.t
606 (** Term for [--no-verify-tls] flag with app-specific env var.
607 Env var: [{APP_NAME}_NO_VERIFY_TLS] *)
608
609 val timeout_term : string -> float option with_source Cmdliner.Term.t
610 (** Term for [--timeout SECONDS] option with app-specific env var.
611 Env var: [{APP_NAME}_TIMEOUT] *)
612
613 val retries_term : string -> int with_source Cmdliner.Term.t
614 (** Term for [--max-retries N] option with app-specific env var.
615 Env var: [{APP_NAME}_MAX_RETRIES] *)
616
617 val retry_backoff_term : string -> float with_source Cmdliner.Term.t
618 (** Term for [--retry-backoff FACTOR] option with app-specific env var.
619 Env var: [{APP_NAME}_RETRY_BACKOFF] *)
620
621 val follow_redirects_term : string -> bool with_source Cmdliner.Term.t
622 (** Term for [--no-follow-redirects] flag with app-specific env var.
623 Env var: [{APP_NAME}_NO_FOLLOW_REDIRECTS] *)
624
625 val max_redirects_term : string -> int with_source Cmdliner.Term.t
626 (** Term for [--max-redirects N] option with app-specific env var.
627 Env var: [{APP_NAME}_MAX_REDIRECTS] *)
628
629 val user_agent_term : string -> string option with_source Cmdliner.Term.t
630 (** Term for [--user-agent STRING] option with app-specific env var.
631 Env var: [{APP_NAME}_USER_AGENT] *)
632
633 val verbose_http_term : string -> bool with_source Cmdliner.Term.t
634 (** Term for [--verbose-http] flag with app-specific env var.
635
636 Enables verbose HTTP-level logging including hexdumps, TLS details,
637 and low-level protocol information. Typically used in conjunction
638 with debug-level logging.
639 Env var: [{APP_NAME}_VERBOSE_HTTP] *)
640
641 val proxy_term : string -> proxy_config Cmdliner.Term.t
642 (** Term for [--proxy URL] and [--no-proxy HOSTS] options.
643
644 Provides cmdliner integration for proxy configuration with proper
645 source tracking. Environment variables are checked in order:
646 HTTP_PROXY, http_proxy, HTTPS_PROXY, https_proxy, ALL_PROXY, all_proxy.
647
648 {b Generated Flags:}
649 - [--proxy URL]: HTTP/HTTPS proxy URL (e.g., http://proxy:8080)
650 - [--no-proxy HOSTS]: Comma-separated list of hosts to bypass proxy
651
652 {b Environment Variables:}
653 - [HTTP_PROXY] / [http_proxy]: HTTP proxy URL
654 - [HTTPS_PROXY] / [https_proxy]: HTTPS proxy URL
655 - [ALL_PROXY] / [all_proxy]: Fallback proxy URL for all protocols
656 - [NO_PROXY] / [no_proxy]: Hosts to bypass proxy *)
657
658 (** {2 Combined Terms} *)
659
660 val config_term : string -> Eio.Fs.dir_ty Eio.Path.t -> config Cmdliner.Term.t
661 (** [config_term app_name fs] creates a complete configuration term.
662
663 This combines all individual terms plus XDG configuration into
664 a single term that can be used to configure requests. All values
665 include source tracking.
666
667 {b Generated Flags:}
668 - [--config-dir DIR]: Configuration directory
669 - [--data-dir DIR]: Data directory
670 - [--cache-dir DIR]: Cache directory
671 - [--persist-cookies]: Enable cookie persistence
672 - [--no-verify-tls]: Disable TLS verification
673 - [--timeout SECONDS]: Request timeout
674 - [--max-retries N]: Maximum retries
675 - [--retry-backoff FACTOR]: Retry backoff multiplier
676 - [--no-follow-redirects]: Disable redirect following
677 - [--max-redirects N]: Maximum redirects to follow
678 - [--user-agent STRING]: User-Agent header
679 - [--verbose-http]: Enable verbose HTTP-level logging
680 - [--proxy URL]: HTTP/HTTPS proxy URL
681 - [--no-proxy HOSTS]: Hosts to bypass proxy
682
683 {b Example:}
684 {[
685 let open Cmdliner in
686 let config_t = Requests.Cmd.config_term "myapp" env#fs in
687 let main config =
688 Eio.Switch.run @@ fun sw ->
689 let req = Requests.Cmd.create config env sw in
690 (* Use requests *)
691 in
692 let cmd = Cmd.v info Term.(const main $ config_t) in
693 Cmd.eval cmd
694 ]} *)
695
696 val requests_term : string -> < clock: _ Eio.Time.clock; net: _ Eio.Net.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> Eio.Switch.t -> t Cmdliner.Term.t
697 (** [requests_term app_name env sw] creates a term that directly produces a requests instance.
698
699 This is a convenience function that combines configuration parsing
700 with requests creation.
701
702 {b Example:}
703 {[
704 let open Cmdliner in
705 let main req =
706 (* Use requests directly *)
707 let resp = Requests.get req "https://example.com" in
708 (* ... *)
709 in
710 Eio.Switch.run @@ fun sw ->
711 let req_t = Requests.Cmd.requests_term "myapp" env sw in
712 let cmd = Cmd.v info Term.(const main $ req_t) in
713 Cmd.eval cmd
714 ]} *)
715
716 val minimal_term : string -> Eio.Fs.dir_ty Eio.Path.t -> (Xdge.t * bool) Cmdliner.Term.t
717 (** [minimal_term app_name fs] creates a minimal configuration term.
718
719 This only provides:
720 - [--cache-dir DIR]: Cache directory for responses
721 - [--persist-cookies]: Cookie persistence flag
722
723 Returns the XDG context and persist_cookies boolean (without source tracking
724 for simplified usage).
725
726 {b Example:}
727 {[
728 let open Cmdliner in
729 let minimal_t = Requests.Cmd.minimal_term "myapp" env#fs in
730 let main (xdg, persist) =
731 Eio.Switch.run @@ fun sw ->
732 let req = Requests.create ~sw ~xdg ~persist_cookies:persist env in
733 (* Use requests *)
734 in
735 let cmd = Cmd.v info Term.(const main $ minimal_t) in
736 Cmd.eval cmd
737 ]} *)
738
739 (** {2 Documentation and Pretty-Printing} *)
740
741 val env_docs : string -> string
742 (** [env_docs app_name] generates environment variable documentation.
743
744 Returns formatted documentation for all environment variables that
745 affect requests configuration, including XDG variables and proxy settings.
746
747 {b Included Variables:}
748 - [${APP_NAME}_CONFIG_DIR]: Configuration directory
749 - [${APP_NAME}_DATA_DIR]: Data directory
750 - [${APP_NAME}_CACHE_DIR]: Cache directory
751 - [${APP_NAME}_STATE_DIR]: State directory
752 - [XDG_CONFIG_HOME], [XDG_DATA_HOME], [XDG_CACHE_HOME], [XDG_STATE_HOME]
753 - [HTTP_PROXY], [HTTPS_PROXY], [ALL_PROXY]: Proxy URLs
754 - [NO_PROXY]: Hosts to bypass proxy
755
756 {b Example:}
757 {[
758 let env_info = Cmdliner.Cmd.Env.info
759 ~docs:Cmdliner.Manpage.s_environment
760 ~doc:(Requests.Cmd.env_docs "myapp")
761 ()
762 ]} *)
763
764 val pp_source : Format.formatter -> source -> unit
765 (** Pretty print a source type.
766 Output format: "default", "env(VAR_NAME)", or "cmdline" *)
767
768 val pp_with_source : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a with_source -> unit
769 (** [pp_with_source pp_val ppf ws] pretty prints a value with its source.
770 Output format: "value [source]"
771
772 {b Example:}
773 {[
774 let pp_bool_with_source = Cmd.pp_with_source Format.pp_print_bool in
775 Format.printf "%a@." pp_bool_with_source config.verify_tls
776 (* Output: true [env(MYAPP_NO_VERIFY_TLS)] *)
777 ]} *)
778
779 val pp_config : ?show_sources:bool -> Format.formatter -> config -> unit
780 (** [pp_config ?show_sources ppf config] pretty prints configuration for debugging.
781
782 @param show_sources If true (default), shows the source of each value
783 (e.g., "default", "env(VAR_NAME)", "cmdline"). If false, only
784 shows the values without source annotations.
785
786 {b Example:}
787 {[
788 (* Show full config with sources *)
789 Format.printf "%a@." (Cmd.pp_config ~show_sources:true) config;
790
791 (* Show config without sources for cleaner output *)
792 Format.printf "%a@." (Cmd.pp_config ~show_sources:false) config;
793 ]} *)
794
795 (** {2 Logging Configuration} *)
796
797 val setup_log_sources : ?verbose_http:bool -> Logs.level option -> unit
798 (** [setup_log_sources ~verbose_http level] configures Requests library log sources.
799
800 This helper function configures all Requests logging sources based on
801 the specified log level and verbose_http flag. It's designed to work
802 with Logs_cli and provides fine-grained control over HTTP-level logging.
803
804 {b Log Level Behavior:}
805 - [Some Debug]: Enables debug logging for all application-level modules
806 (Auth, Body, Response, Retry, Headers, Error, Method, Mime, Status, Timeout).
807 If [verbose_http] is true, also enables debug logging for protocol-level
808 modules (One, Http_client, Conpool, and TLS tracing). If [verbose_http]
809 is false, TLS tracing is set to Warning level to suppress hexdumps.
810 - [Some Info]: Enables info logging for main modules (src, Response, One).
811 TLS tracing is set to Warning level.
812 - [None] or other levels: TLS tracing is set to Warning level to suppress
813 verbose protocol output.
814
815 {b Example with Logs_cli:}
816 {[
817 let setup_logging =
818 let open Cmdliner.Term in
819 const (fun style level verbose_http ->
820 Fmt_tty.setup_std_outputs ?style_renderer:style ();
821 Logs.set_level level;
822 Logs.set_reporter (Logs_fmt.reporter ());
823 Requests.Cmd.setup_log_sources ~verbose_http level)
824 $ Fmt_cli.style_renderer ()
825 $ Logs_cli.level ()
826 $ Requests.Cmd.verbose_http_term "myapp"
827 ]} *)
828end
829
830(** Retry policies and backoff strategies *)
831module Retry = Retry
832
833(** {1 One-Shot API}
834
835 The {!One} module provides direct control over HTTP requests without
836 session state. Each request opens a new TCP connection that is closed
837 when the switch closes.
838
839 Use {!One} for:
840 - Single, stateless requests without session overhead
841 - Fine-grained control over TLS configuration per request
842 - Direct streaming uploads and downloads
843 - Situations where connection pooling is not needed
844
845 See the module documentation for examples and full API.
846*)
847
848(** One-shot HTTP client for stateless requests.
849
850 Provides {!One.get}, {!One.post}, {!One.put}, {!One.patch}, {!One.delete},
851 {!One.head}, {!One.upload}, and {!One.download} functions.
852
853 Example:
854 {[
855 let response = Requests.One.get ~sw
856 ~clock:env#clock ~net:env#net
857 "https://example.com" in
858 Printf.printf "Status: %d\n" (Requests.Response.status_code response)
859 ]} *)
860module One = One
861
862(** Low-level HTTP client over pooled connections *)
863module Http_client = Http_client
864
865(** {1 Core Types}
866
867 These modules define the fundamental types used throughout the library.
868*)
869
870(** HTTP response handling *)
871module Response = Response
872
873(** Request body construction and encoding *)
874module Body = Body
875
876(** HTTP headers manipulation *)
877module Headers = Headers
878
879(** Authentication schemes (Basic, Bearer, OAuth, etc.) *)
880module Auth = Auth
881
882(** HTTP/HTTPS proxy configuration *)
883module Proxy = Proxy
884
885(** HTTPS proxy tunneling via CONNECT *)
886module Proxy_tunnel = Proxy_tunnel
887
888(** Error types and exception handling *)
889module Error = Error
890
891(** {1 Supporting Types} *)
892
893(** Efficient URI serialization for Buf_write.
894
895 For all URI operations (parsing, manipulation, etc.), use the external
896 [uri] opam library directly. This module only provides {!Huri.write} for
897 efficient serialization to [Eio.Buf_write] without intermediate strings. *)
898module Huri = Huri
899
900(** HTTP status codes and reason phrases *)
901module Status = Status
902
903(** HTTP request methods (GET, POST, etc.) *)
904module Method = Method
905
906(** HTTP protocol version types and ALPN support *)
907module Http_version = Http_version
908
909(** MIME types for content negotiation *)
910module Mime = Mime
911
912(** Timeout configuration for requests *)
913module Timeout = Timeout
914
915(** HTTP Cache-Control header parsing (RFC 9111) *)
916module Cache_control = Cache_control
917
918(** HTTP response size limits for DoS prevention *)
919module Response_limits = Response_limits
920
921(** HTTP 100-Continue configuration for large uploads *)
922module Expect_continue = Expect_continue
923
924(** HTTP Link header parsing (RFC 8288) for pagination and API discovery *)
925module Link = Link
926
927(** HTTP request timing metrics for performance analysis *)
928module Timing = Timing
929
930(** HTTP header name types and utilities *)
931module Header_name = Header_name
932
933(** HTTP header value parsing for complex headers (RFC 9110)
934 @see <https://www.rfc-editor.org/rfc/rfc9110> *)
935module Header_parsing = Header_parsing
936
937(** WebSocket handshake support (RFC 6455)
938 @see <https://www.rfc-editor.org/rfc/rfc6455> *)
939module Websocket = Websocket
940
941(** HTTP Message Signatures (RFC 9421) *)
942module Signature = Signature
943
944(** {2 Logging} *)
945
946(** Log source for the requests library.
947 Use [Logs.Src.set_level src] to control logging verbosity.
948 Example: [Logs.Src.set_level Requests.src (Some Logs.Debug)] *)
949val src : Logs.Src.t
950