My agentic slop goes here. Not intended for anyone else!
at main 20 kB view raw
1(** Requests - A modern HTTP client library for OCaml 2 3 Requests is an HTTP client library for OCaml inspired by Python's requests 4 and urllib3 libraries. It provides a simple, intuitive API for making HTTP 5 requests while handling complexities like TLS configuration, connection 6 pooling, retries, and cookie management. 7 8 {2 High-Level API} 9 10 The Requests library offers two main ways to make HTTP requests: 11 12 {b 1. Main API} (Recommended for most use cases) 13 14 The main API maintains state across requests, handles cookies automatically, 15 spawns requests in concurrent fibers, and provides a simple interface: 16 17 {[ 18 open Eio_main 19 20 let () = run @@ fun env -> 21 Switch.run @@ fun sw -> 22 23 (* Create a requests instance *) 24 let req = Requests.create ~sw env in 25 26 (* Configure authentication once *) 27 Requests.set_auth req (Requests.Auth.bearer "your-token"); 28 29 (* Make concurrent requests using Fiber.both *) 30 let (user, repos) = Eio.Fiber.both 31 (fun () -> Requests.get req "https://api.github.com/user") 32 (fun () -> Requests.get req "https://api.github.com/user/repos") in 33 34 (* Process responses *) 35 let user_data = Response.body user |> Eio.Flow.read_all in 36 let repos_data = Response.body repos |> Eio.Flow.read_all in 37 38 (* No cleanup needed - responses auto-close with the switch *) 39 ]} 40 41 {b 2. One-shot requests} (For stateless operations) 42 43 The One module provides lower-level control for stateless, 44 one-off requests without session state: 45 46 {[ 47 (* Create a one-shot client *) 48 let client = Requests.One.create ~clock:env#clock ~net:env#net () in 49 50 (* Make a simple GET request *) 51 let response = Requests.One.get ~sw ~client "https://api.github.com" in 52 Printf.printf "Status: %d\n" (Requests.Response.status_code response); 53 54 (* POST with custom headers and body *) 55 let response = Requests.One.post ~sw ~client 56 ~headers:(Requests.Headers.empty 57 |> Requests.Headers.content_type Requests.Mime.json 58 |> Requests.Headers.set "X-API-Key" "secret") 59 ~body:(Requests.Body.json {|{"name": "Alice"}|}) 60 "https://api.example.com/users" 61 62 (* No cleanup needed - responses auto-close with the switch *) 63 ]} 64 65 {2 Features} 66 67 - {b Simple API}: Intuitive functions for GET, POST, PUT, DELETE, etc. 68 - {b Authentication}: Built-in support for Basic, Bearer, Digest, and OAuth 69 - {b Streaming}: Upload and download large files efficiently 70 - {b Retries}: Automatic retry with exponential backoff 71 - {b Timeouts}: Configurable connection and read timeouts 72 - {b Cookie Management}: Automatic cookie handling with persistence 73 - {b TLS/SSL}: Secure connections with certificate verification 74 - {b Error Handling}: Comprehensive error types and recovery 75 76 {2 Common Use Cases} 77 78 {b Working with JSON APIs:} 79 {[ 80 let response = Requests.post req "https://api.example.com/data" 81 ~body:(Requests.Body.json {|{"key": "value"}|}) in 82 let body_text = 83 Requests.Response.body response 84 |> Eio.Flow.read_all in 85 print_endline body_text 86 (* Response auto-closes with switch *) 87 ]} 88 89 {b File uploads:} 90 {[ 91 let body = Requests.Body.multipart [ 92 { name = "file"; filename = Some "document.pdf"; 93 content_type = Requests.Mime.pdf; 94 content = `File (Eio.Path.(fs / "document.pdf")) }; 95 { name = "description"; filename = None; 96 content_type = Requests.Mime.text_plain; 97 content = `String "Important document" } 98 ] in 99 let response = Requests.post req "https://example.com/upload" 100 ~body 101 (* Response auto-closes with switch *) 102 ]} 103 104 {b Streaming downloads:} 105 {[ 106 Requests.One.download ~sw ~client 107 "https://example.com/large-file.zip" 108 ~sink:(Eio.Path.(fs / "download.zip" |> sink)) 109 ]} 110 111 {2 Choosing Between Main API and One} 112 113 Use the {b main API (Requests.t)} when you need: 114 - Cookie persistence across requests 115 - Automatic retry handling 116 - Shared authentication across requests 117 - Request/response history tracking 118 - Configuration persistence to disk 119 120 Use {b One} when you need: 121 - One-off stateless requests 122 - Fine-grained control over connections 123 - Minimal overhead 124 - Custom connection pooling 125 - Direct streaming without cookies 126*) 127 128(** {1 Main API} 129 130 The main Requests API provides stateful HTTP clients with automatic cookie 131 management and persistent configuration. Requests execute synchronously by default. 132 Use Eio.Fiber.both or Eio.Fiber.all for concurrent execution. 133*) 134 135type ('clock, 'net) t 136(** A stateful HTTP client that maintains cookies, auth, configuration, and 137 connection pools across requests. *) 138 139(** {2 Creation and Configuration} *) 140 141val create : 142 sw:Eio.Switch.t -> 143 ?http_pool:('clock Eio.Time.clock, 'net Eio.Net.t) Conpool.t -> 144 ?https_pool:('clock Eio.Time.clock, 'net Eio.Net.t) Conpool.t -> 145 ?cookie_jar:Cookeio.jar -> 146 ?default_headers:Headers.t -> 147 ?auth:Auth.t -> 148 ?timeout:Timeout.t -> 149 ?follow_redirects:bool -> 150 ?max_redirects:int -> 151 ?verify_tls:bool -> 152 ?tls_config:Tls.Config.client -> 153 ?max_connections_per_host:int -> 154 ?connection_idle_timeout:float -> 155 ?connection_lifetime:float -> 156 ?retry:Retry.config -> 157 ?persist_cookies:bool -> 158 ?xdg:Xdge.t -> 159 < clock: 'clock Eio.Resource.t; net: 'net Eio.Resource.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> 160 ('clock Eio.Resource.t, 'net Eio.Resource.t) t 161(** Create a new requests instance with persistent state and connection pooling. 162 All resources are bound to the provided switch and will be cleaned up automatically. 163 164 @param sw Switch for resource management 165 @param http_pool Optional pre-configured HTTP connection pool (creates new if not provided) 166 @param https_pool Optional pre-configured HTTPS connection pool (creates new if not provided) 167 @param cookie_jar Cookie storage (default: empty in-memory jar) 168 @param default_headers Headers included in every request 169 @param auth Default authentication 170 @param timeout Default timeout configuration 171 @param follow_redirects Whether to follow HTTP redirects (default: true) 172 @param max_redirects Maximum redirects to follow (default: 10) 173 @param verify_tls Whether to verify TLS certificates (default: true) 174 @param tls_config Custom TLS configuration for HTTPS pool (default: system CA certs) 175 @param max_connections_per_host Maximum pooled connections per host:port (default: 10) 176 @param connection_idle_timeout Max idle time before closing pooled connection (default: 60s) 177 @param connection_lifetime Max lifetime of any pooled connection (default: 300s) 178 @param retry Retry configuration for failed requests 179 @param persist_cookies Whether to persist cookies to disk (default: false) 180 @param xdg XDG directory context for cookies (required if persist_cookies=true) 181 182 {b Note:} HTTP caching has been disabled for simplicity. See CACHEIO.md for integration notes 183 if you need to restore caching functionality in the future. 184*) 185 186(** {2 Configuration Management} *) 187 188val set_default_header : ('clock, 'net) t -> string -> string -> ('clock, 'net) t 189(** Add or update a default header. Returns a new session with the updated header. 190 The original session's connection pools are shared. *) 191 192val remove_default_header : ('clock, 'net) t -> string -> ('clock, 'net) t 193(** Remove a default header. Returns a new session without the header. *) 194 195val set_auth : ('clock, 'net) t -> Auth.t -> ('clock, 'net) t 196(** Set default authentication. Returns a new session with auth configured. *) 197 198val clear_auth : ('clock, 'net) t -> ('clock, 'net) t 199(** Clear authentication. Returns a new session without auth. *) 200 201val set_timeout : ('clock, 'net) t -> Timeout.t -> ('clock, 'net) t 202(** Set default timeout. Returns a new session with the timeout configured. *) 203 204val set_retry : ('clock, 'net) t -> Retry.config -> ('clock, 'net) t 205(** Set retry configuration. Returns a new session with retry configured. *) 206 207(** {2 Request Methods} 208 209 All request methods execute synchronously. To make concurrent requests, 210 you must explicitly use Eio.Fiber.both or Eio.Fiber.all. 211 The response will auto-close when the parent switch closes. 212 213 Example of concurrent requests using Fiber.both: 214 {[ 215 let req = Requests.create ~sw env in 216 217 (* Use Fiber.both for two concurrent requests *) 218 let (r1, r2) = Eio.Fiber.both 219 (fun () -> Requests.get req "https://api1.example.com") 220 (fun () -> Requests.post req "https://api2.example.com" ~body) 221 in 222 223 (* Process responses *) 224 let body1 = Response.body r1 |> Eio.Flow.read_all in 225 let body2 = Response.body r2 |> Eio.Flow.read_all in 226 ]} 227 228 Example using Fiber.all for multiple requests: 229 {[ 230 let req = Requests.create ~sw env in 231 232 (* Use Fiber.all for multiple concurrent requests *) 233 let urls = [ 234 "https://api1.example.com"; 235 "https://api2.example.com"; 236 "https://api3.example.com"; 237 ] in 238 239 let responses = ref [] in 240 Eio.Fiber.all [ 241 (fun () -> responses := Requests.get req (List.nth urls 0) :: !responses); 242 (fun () -> responses := Requests.get req (List.nth urls 1) :: !responses); 243 (fun () -> responses := Requests.get req (List.nth urls 2) :: !responses); 244 ]; 245 246 (* Process all responses *) 247 List.iter (fun r -> 248 let body = Response.body r |> Eio.Flow.read_all in 249 print_endline body 250 ) !responses 251 ]} 252 253 Example using Promise for concurrent requests with individual control: 254 {[ 255 let req = Requests.create ~sw env in 256 257 (* Start requests in parallel using promises *) 258 let p1, r1 = Eio.Promise.create () in 259 let p2, r2 = Eio.Promise.create () in 260 let p3, r3 = Eio.Promise.create () in 261 262 Eio.Fiber.fork ~sw (fun () -> 263 Eio.Promise.resolve r1 (Requests.get req "https://api1.example.com") 264 ); 265 Eio.Fiber.fork ~sw (fun () -> 266 Eio.Promise.resolve r2 (Requests.post req "https://api2.example.com" ~body) 267 ); 268 Eio.Fiber.fork ~sw (fun () -> 269 Eio.Promise.resolve r3 (Requests.get req "https://api3.example.com") 270 ); 271 272 (* Wait for all promises and process *) 273 let resp1 = Eio.Promise.await p1 in 274 let resp2 = Eio.Promise.await p2 in 275 let resp3 = Eio.Promise.await p3 in 276 277 (* Process responses *) 278 let body1 = Response.body resp1 |> Eio.Flow.read_all in 279 let body2 = Response.body resp2 |> Eio.Flow.read_all in 280 let body3 = Response.body resp3 |> Eio.Flow.read_all in 281 ]} 282*) 283 284val request : 285 (_ Eio.Time.clock, _ Eio.Net.t) t -> 286 ?headers:Headers.t -> 287 ?body:Body.t -> 288 ?auth:Auth.t -> 289 ?timeout:Timeout.t -> 290 ?follow_redirects:bool -> 291 ?max_redirects:int -> 292 method_:Method.t -> 293 string -> 294 Response.t 295(** Make a concurrent HTTP request *) 296 297val get : 298 (_ Eio.Time.clock, _ Eio.Net.t) t -> 299 ?headers:Headers.t -> 300 ?auth:Auth.t -> 301 ?timeout:Timeout.t -> 302 ?params:(string * string) list -> 303 string -> 304 Response.t 305(** Concurrent GET request *) 306 307val post : 308 (_ Eio.Time.clock, _ Eio.Net.t) t -> 309 ?headers:Headers.t -> 310 ?body:Body.t -> 311 ?auth:Auth.t -> 312 ?timeout:Timeout.t -> 313 string -> 314 Response.t 315(** Concurrent POST request *) 316 317val put : 318 (_ Eio.Time.clock, _ Eio.Net.t) t -> 319 ?headers:Headers.t -> 320 ?body:Body.t -> 321 ?auth:Auth.t -> 322 ?timeout:Timeout.t -> 323 string -> 324 Response.t 325(** Concurrent PUT request *) 326 327val patch : 328 (_ Eio.Time.clock, _ Eio.Net.t) t -> 329 ?headers:Headers.t -> 330 ?body:Body.t -> 331 ?auth:Auth.t -> 332 ?timeout:Timeout.t -> 333 string -> 334 Response.t 335(** Concurrent PATCH request *) 336 337val delete : 338 (_ Eio.Time.clock, _ Eio.Net.t) t -> 339 ?headers:Headers.t -> 340 ?auth:Auth.t -> 341 ?timeout:Timeout.t -> 342 string -> 343 Response.t 344(** Concurrent DELETE request *) 345 346val head : 347 (_ Eio.Time.clock, _ Eio.Net.t) t -> 348 ?headers:Headers.t -> 349 ?auth:Auth.t -> 350 ?timeout:Timeout.t -> 351 string -> 352 Response.t 353(** Concurrent HEAD request *) 354 355val options : 356 (_ Eio.Time.clock, _ Eio.Net.t) t -> 357 ?headers:Headers.t -> 358 ?auth:Auth.t -> 359 ?timeout:Timeout.t -> 360 string -> 361 Response.t 362(** Concurrent OPTIONS request *) 363 364(** {2 Cookie Management} *) 365 366val cookies : ('clock, 'net) t -> Cookeio.jar 367(** Get the cookie jar for direct manipulation *) 368 369val clear_cookies : ('clock, 'net) t -> unit 370(** Clear all cookies *) 371 372(** {1 Cmdliner Integration} *) 373 374module Cmd : sig 375 (** Cmdliner integration for Requests configuration. 376 377 This module provides command-line argument handling for configuring 378 HTTP requests, including XDG directory paths, timeouts, retries, 379 and other parameters. *) 380 381 (** Configuration from command line and environment *) 382 type config = { 383 xdg : Xdge.t * Xdge.Cmd.t; (** XDG paths and their sources *) 384 persist_cookies : bool; (** Whether to persist cookies *) 385 verify_tls : bool; (** Whether to verify TLS certificates *) 386 timeout : float option; (** Request timeout in seconds *) 387 max_retries : int; (** Maximum number of retries *) 388 retry_backoff : float; (** Retry backoff factor *) 389 follow_redirects : bool; (** Whether to follow redirects *) 390 max_redirects : int; (** Maximum number of redirects *) 391 user_agent : string option; (** User-Agent header *) 392 } 393 394 val create : config -> < clock: ([> float Eio.Time.clock_ty ] as 'clock) Eio.Resource.t; net: ([> [>`Generic] Eio.Net.ty ] as 'net) Eio.Resource.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> Eio.Switch.t -> ('clock Eio.Resource.t, 'net Eio.Resource.t) t 395 (** [create config env sw] creates a requests instance from command-line configuration *) 396 397 (** {2 Individual Terms} *) 398 399 val persist_cookies_term : string -> bool Cmdliner.Term.t 400 (** Term for [--persist-cookies] flag with app-specific env var *) 401 402 val verify_tls_term : string -> bool Cmdliner.Term.t 403 (** Term for [--no-verify-tls] flag with app-specific env var *) 404 405 val timeout_term : string -> float option Cmdliner.Term.t 406 (** Term for [--timeout SECONDS] option with app-specific env var *) 407 408 val retries_term : string -> int Cmdliner.Term.t 409 (** Term for [--max-retries N] option with app-specific env var *) 410 411 val retry_backoff_term : string -> float Cmdliner.Term.t 412 (** Term for [--retry-backoff FACTOR] option with app-specific env var *) 413 414 val follow_redirects_term : string -> bool Cmdliner.Term.t 415 (** Term for [--no-follow-redirects] flag with app-specific env var *) 416 417 val max_redirects_term : string -> int Cmdliner.Term.t 418 (** Term for [--max-redirects N] option with app-specific env var *) 419 420 val user_agent_term : string -> string option Cmdliner.Term.t 421 (** Term for [--user-agent STRING] option with app-specific env var *) 422 423 (** {2 Combined Terms} *) 424 425 val config_term : string -> Eio.Fs.dir_ty Eio.Path.t -> config Cmdliner.Term.t 426 (** [config_term app_name fs] creates a complete configuration term. 427 428 This combines all individual terms plus XDG configuration into 429 a single term that can be used to configure requests. 430 431 {b Generated Flags:} 432 - [--config-dir DIR]: Configuration directory 433 - [--data-dir DIR]: Data directory 434 - [--cache-dir DIR]: Cache directory 435 - [--persist-cookies]: Enable cookie persistence 436 - [--no-verify-tls]: Disable TLS verification 437 - [--timeout SECONDS]: Request timeout 438 - [--max-retries N]: Maximum retries 439 - [--retry-backoff FACTOR]: Retry backoff multiplier 440 - [--no-follow-redirects]: Disable redirect following 441 - [--max-redirects N]: Maximum redirects to follow 442 - [--user-agent STRING]: User-Agent header 443 444 {b Example:} 445 {[ 446 let open Cmdliner in 447 let config_t = Requests.Cmd.config_term "myapp" env#fs in 448 let main config = 449 Eio.Switch.run @@ fun sw -> 450 let req = Requests.Cmd.create config env sw in 451 (* Use requests *) 452 in 453 let cmd = Cmd.v info Term.(const main $ config_t) in 454 Cmd.eval cmd 455 ]} *) 456 457 val requests_term : string -> < clock: ([> float Eio.Time.clock_ty ] as 'clock) Eio.Resource.t; net: ([> [>`Generic] Eio.Net.ty ] as 'net) Eio.Resource.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> Eio.Switch.t -> ('clock Eio.Resource.t, 'net Eio.Resource.t) t Cmdliner.Term.t 458 (** [requests_term app_name env sw] creates a term that directly produces a requests instance. 459 460 This is a convenience function that combines configuration parsing 461 with requests creation. 462 463 {b Example:} 464 {[ 465 let open Cmdliner in 466 let main req = 467 (* Use requests directly *) 468 let resp = Requests.get req "https://example.com" in 469 (* ... *) 470 in 471 Eio.Switch.run @@ fun sw -> 472 let req_t = Requests.Cmd.requests_term "myapp" env sw in 473 let cmd = Cmd.v info Term.(const main $ req_t) in 474 Cmd.eval cmd 475 ]} *) 476 477 val minimal_term : string -> Eio.Fs.dir_ty Eio.Path.t -> (Xdge.t * bool) Cmdliner.Term.t 478 (** [minimal_term app_name fs] creates a minimal configuration term. 479 480 This only provides: 481 - [--cache-dir DIR]: Cache directory for responses 482 - [--persist-cookies]: Cookie persistence flag 483 484 Returns the XDG context and persist_cookies boolean. 485 486 {b Example:} 487 {[ 488 let open Cmdliner in 489 let minimal_t = Requests.Cmd.minimal_term "myapp" env#fs in 490 let main (xdg, persist) = 491 Eio.Switch.run @@ fun sw -> 492 let req = Requests.create ~sw ~xdg ~persist_cookies:persist env in 493 (* Use requests *) 494 in 495 let cmd = Cmd.v info Term.(const main $ minimal_t) in 496 Cmd.eval cmd 497 ]} *) 498 499 (** {2 Documentation} *) 500 501 val env_docs : string -> string 502 (** [env_docs app_name] generates environment variable documentation. 503 504 Returns formatted documentation for all environment variables that 505 affect requests configuration, including XDG variables. 506 507 {b Included Variables:} 508 - [${APP_NAME}_CONFIG_DIR]: Configuration directory 509 - [${APP_NAME}_DATA_DIR]: Data directory 510 - [${APP_NAME}_CACHE_DIR]: Cache directory 511 - [${APP_NAME}_STATE_DIR]: State directory 512 - [XDG_CONFIG_HOME], [XDG_DATA_HOME], [XDG_CACHE_HOME], [XDG_STATE_HOME] 513 - [HTTP_PROXY], [HTTPS_PROXY], [NO_PROXY] (when proxy support is added) 514 515 {b Example:} 516 {[ 517 let env_info = Cmdliner.Cmd.Env.info 518 ~docs:Cmdliner.Manpage.s_environment 519 ~doc:(Requests.Cmd.env_docs "myapp") 520 () 521 ]} *) 522 523 val pp_config : Format.formatter -> config -> unit 524 (** Pretty print configuration for debugging *) 525end 526 527(** Retry policies and backoff strategies *) 528module Retry = Retry 529 530(** {1 One-Shot API} 531 532 The One module provides direct control over HTTP requests without 533 session state. Use this for stateless operations or when you need 534 fine-grained control. 535*) 536 537(** One-shot HTTP client for stateless requests *) 538module One = One 539 540(** {1 Core Types} 541 542 These modules define the fundamental types used throughout the library. 543*) 544 545(** HTTP response handling *) 546module Response = Response 547 548(** Request body construction and encoding *) 549module Body = Body 550 551(** HTTP headers manipulation *) 552module Headers = Headers 553 554(** Authentication schemes (Basic, Bearer, OAuth, etc.) *) 555module Auth = Auth 556 557(** Error types and exception handling *) 558module Error = Error 559 560(** {1 Supporting Types} *) 561 562(** HTTP status codes and reason phrases *) 563module Status = Status 564 565(** HTTP request methods (GET, POST, etc.) *) 566module Method = Method 567 568(** MIME types for content negotiation *) 569module Mime = Mime 570 571(** Timeout configuration for requests *) 572module Timeout = Timeout 573 574(** {2 Logging} *) 575 576(** Log source for the requests library. 577 Use [Logs.Src.set_level src] to control logging verbosity. 578 Example: [Logs.Src.set_level Requests.src (Some Logs.Debug)] *) 579val src : Logs.Src.t