A batteries included HTTP/1.1 client in OCaml
at main 950 lines 35 kB view raw
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