My agentic slop goes here. Not intended for anyone else!
at jsont 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 t 136(** A stateful HTTP client that maintains cookies, auth, configuration, and 137 connection pools across requests. The internal clock and network types are 138 hidden from external users. *) 139 140(** {2 Creation and Configuration} *) 141 142val create : 143 sw:Eio.Switch.t -> 144 ?http_pool:(([> float Eio.Time.clock_ty] as 'clock) Eio.Resource.t, 145 ([> [> `Generic] Eio.Net.ty] as 'net) Eio.Resource.t) Conpool.t -> 146 ?https_pool:('clock Eio.Resource.t, 'net Eio.Resource.t) Conpool.t -> 147 ?cookie_jar:Cookeio.jar -> 148 ?default_headers:Headers.t -> 149 ?auth:Auth.t -> 150 ?timeout:Timeout.t -> 151 ?follow_redirects:bool -> 152 ?max_redirects:int -> 153 ?verify_tls:bool -> 154 ?tls_config:Tls.Config.client -> 155 ?max_connections_per_host:int -> 156 ?connection_idle_timeout:float -> 157 ?connection_lifetime:float -> 158 ?retry:Retry.config -> 159 ?persist_cookies:bool -> 160 ?xdg:Xdge.t -> 161 < clock: 'clock Eio.Resource.t; net: 'net Eio.Resource.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> 162 t 163(** Create a new requests instance with persistent state and connection pooling. 164 All resources are bound to the provided switch and will be cleaned up automatically. 165 166 @param sw Switch for resource management 167 @param http_pool Optional pre-configured HTTP connection pool (creates new if not provided) 168 @param https_pool Optional pre-configured HTTPS connection pool (creates new if not provided) 169 @param cookie_jar Cookie storage (default: empty in-memory jar) 170 @param default_headers Headers included in every request 171 @param auth Default authentication 172 @param timeout Default timeout configuration 173 @param follow_redirects Whether to follow HTTP redirects (default: true) 174 @param max_redirects Maximum redirects to follow (default: 10) 175 @param verify_tls Whether to verify TLS certificates (default: true) 176 @param tls_config Custom TLS configuration for HTTPS pool (default: system CA certs) 177 @param max_connections_per_host Maximum pooled connections per host:port (default: 10) 178 @param connection_idle_timeout Max idle time before closing pooled connection (default: 60s) 179 @param connection_lifetime Max lifetime of any pooled connection (default: 300s) 180 @param retry Retry configuration for failed requests 181 @param persist_cookies Whether to persist cookies to disk (default: false) 182 @param xdg XDG directory context for cookies (required if persist_cookies=true) 183 184 {b Note:} HTTP caching has been disabled for simplicity. See CACHEIO.md for integration notes 185 if you need to restore caching functionality in the future. 186*) 187 188(** {2 Configuration Management} *) 189 190val set_default_header : t -> string -> string -> t 191(** Add or update a default header. Returns a new session with the updated header. 192 The original session's connection pools are shared. *) 193 194val remove_default_header : t -> string -> t 195(** Remove a default header. Returns a new session without the header. *) 196 197val set_auth : t -> Auth.t -> t 198(** Set default authentication. Returns a new session with auth configured. *) 199 200val clear_auth : t -> t 201(** Clear authentication. Returns a new session without auth. *) 202 203val set_timeout : t -> Timeout.t -> t 204(** Set default timeout. Returns a new session with the timeout configured. *) 205 206val set_retry : t -> Retry.config -> t 207(** Set retry configuration. Returns a new session with retry configured. *) 208 209(** {2 Request Methods} 210 211 All request methods execute synchronously. To make concurrent requests, 212 you must explicitly use Eio.Fiber.both or Eio.Fiber.all. 213 The response will auto-close when the parent switch closes. 214 215 Example of concurrent requests using Fiber.both: 216 {[ 217 let req = Requests.create ~sw env in 218 219 (* Use Fiber.both for two concurrent requests *) 220 let (r1, r2) = Eio.Fiber.both 221 (fun () -> Requests.get req "https://api1.example.com") 222 (fun () -> Requests.post req "https://api2.example.com" ~body) 223 in 224 225 (* Process responses *) 226 let body1 = Response.body r1 |> Eio.Flow.read_all in 227 let body2 = Response.body r2 |> Eio.Flow.read_all in 228 ]} 229 230 Example using Fiber.all for multiple requests: 231 {[ 232 let req = Requests.create ~sw env in 233 234 (* Use Fiber.all for multiple concurrent requests *) 235 let urls = [ 236 "https://api1.example.com"; 237 "https://api2.example.com"; 238 "https://api3.example.com"; 239 ] in 240 241 let responses = ref [] in 242 Eio.Fiber.all [ 243 (fun () -> responses := Requests.get req (List.nth urls 0) :: !responses); 244 (fun () -> responses := Requests.get req (List.nth urls 1) :: !responses); 245 (fun () -> responses := Requests.get req (List.nth urls 2) :: !responses); 246 ]; 247 248 (* Process all responses *) 249 List.iter (fun r -> 250 let body = Response.body r |> Eio.Flow.read_all in 251 print_endline body 252 ) !responses 253 ]} 254 255 Example using Promise for concurrent requests with individual control: 256 {[ 257 let req = Requests.create ~sw env in 258 259 (* Start requests in parallel using promises *) 260 let p1, r1 = Eio.Promise.create () in 261 let p2, r2 = Eio.Promise.create () in 262 let p3, r3 = Eio.Promise.create () in 263 264 Eio.Fiber.fork ~sw (fun () -> 265 Eio.Promise.resolve r1 (Requests.get req "https://api1.example.com") 266 ); 267 Eio.Fiber.fork ~sw (fun () -> 268 Eio.Promise.resolve r2 (Requests.post req "https://api2.example.com" ~body) 269 ); 270 Eio.Fiber.fork ~sw (fun () -> 271 Eio.Promise.resolve r3 (Requests.get req "https://api3.example.com") 272 ); 273 274 (* Wait for all promises and process *) 275 let resp1 = Eio.Promise.await p1 in 276 let resp2 = Eio.Promise.await p2 in 277 let resp3 = Eio.Promise.await p3 in 278 279 (* Process responses *) 280 let body1 = Response.body resp1 |> Eio.Flow.read_all in 281 let body2 = Response.body resp2 |> Eio.Flow.read_all in 282 let body3 = Response.body resp3 |> Eio.Flow.read_all in 283 ]} 284*) 285 286val request : 287 t -> 288 ?headers:Headers.t -> 289 ?body:Body.t -> 290 ?auth:Auth.t -> 291 ?timeout:Timeout.t -> 292 ?follow_redirects:bool -> 293 ?max_redirects:int -> 294 method_:Method.t -> 295 string -> 296 Response.t 297(** Make a concurrent HTTP request *) 298 299val get : 300 t -> 301 ?headers:Headers.t -> 302 ?auth:Auth.t -> 303 ?timeout:Timeout.t -> 304 ?params:(string * string) list -> 305 string -> 306 Response.t 307(** Concurrent GET request *) 308 309val post : 310 t -> 311 ?headers:Headers.t -> 312 ?body:Body.t -> 313 ?auth:Auth.t -> 314 ?timeout:Timeout.t -> 315 string -> 316 Response.t 317(** Concurrent POST request *) 318 319val put : 320 t -> 321 ?headers:Headers.t -> 322 ?body:Body.t -> 323 ?auth:Auth.t -> 324 ?timeout:Timeout.t -> 325 string -> 326 Response.t 327(** Concurrent PUT request *) 328 329val patch : 330 t -> 331 ?headers:Headers.t -> 332 ?body:Body.t -> 333 ?auth:Auth.t -> 334 ?timeout:Timeout.t -> 335 string -> 336 Response.t 337(** Concurrent PATCH request *) 338 339val delete : 340 t -> 341 ?headers:Headers.t -> 342 ?auth:Auth.t -> 343 ?timeout:Timeout.t -> 344 string -> 345 Response.t 346(** Concurrent DELETE request *) 347 348val head : 349 t -> 350 ?headers:Headers.t -> 351 ?auth:Auth.t -> 352 ?timeout:Timeout.t -> 353 string -> 354 Response.t 355(** Concurrent HEAD request *) 356 357val options : 358 t -> 359 ?headers:Headers.t -> 360 ?auth:Auth.t -> 361 ?timeout:Timeout.t -> 362 string -> 363 Response.t 364(** Concurrent OPTIONS request *) 365 366(** {2 Cookie Management} *) 367 368val cookies : t -> Cookeio.jar 369(** Get the cookie jar for direct manipulation *) 370 371val clear_cookies : t -> unit 372(** Clear all cookies *) 373 374(** {1 Cmdliner Integration} *) 375 376module Cmd : sig 377 (** Cmdliner integration for Requests configuration. 378 379 This module provides command-line argument handling for configuring 380 HTTP requests, including XDG directory paths, timeouts, retries, 381 and other parameters. *) 382 383 (** Configuration from command line and environment *) 384 type config = { 385 xdg : Xdge.t * Xdge.Cmd.t; (** XDG paths and their sources *) 386 persist_cookies : bool; (** Whether to persist cookies *) 387 verify_tls : bool; (** Whether to verify TLS certificates *) 388 timeout : float option; (** Request timeout in seconds *) 389 max_retries : int; (** Maximum number of retries *) 390 retry_backoff : float; (** Retry backoff factor *) 391 follow_redirects : bool; (** Whether to follow redirects *) 392 max_redirects : int; (** Maximum number of redirects *) 393 user_agent : string option; (** User-Agent header *) 394 } 395 396 val create : config -> 397 < clock: [> float Eio.Time.clock_ty] Eio.Resource.t; 398 net: [> [> `Generic] Eio.Net.ty] Eio.Resource.t; 399 fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> 400 Eio.Switch.t -> t 401 (** [create config env sw] creates a requests instance from command-line configuration *) 402 403 (** {2 Individual Terms} *) 404 405 val persist_cookies_term : string -> bool Cmdliner.Term.t 406 (** Term for [--persist-cookies] flag with app-specific env var *) 407 408 val verify_tls_term : string -> bool Cmdliner.Term.t 409 (** Term for [--no-verify-tls] flag with app-specific env var *) 410 411 val timeout_term : string -> float option Cmdliner.Term.t 412 (** Term for [--timeout SECONDS] option with app-specific env var *) 413 414 val retries_term : string -> int Cmdliner.Term.t 415 (** Term for [--max-retries N] option with app-specific env var *) 416 417 val retry_backoff_term : string -> float Cmdliner.Term.t 418 (** Term for [--retry-backoff FACTOR] option with app-specific env var *) 419 420 val follow_redirects_term : string -> bool Cmdliner.Term.t 421 (** Term for [--no-follow-redirects] flag with app-specific env var *) 422 423 val max_redirects_term : string -> int Cmdliner.Term.t 424 (** Term for [--max-redirects N] option with app-specific env var *) 425 426 val user_agent_term : string -> string option Cmdliner.Term.t 427 (** Term for [--user-agent STRING] option with app-specific env var *) 428 429 (** {2 Combined Terms} *) 430 431 val config_term : string -> Eio.Fs.dir_ty Eio.Path.t -> config Cmdliner.Term.t 432 (** [config_term app_name fs] creates a complete configuration term. 433 434 This combines all individual terms plus XDG configuration into 435 a single term that can be used to configure requests. 436 437 {b Generated Flags:} 438 - [--config-dir DIR]: Configuration directory 439 - [--data-dir DIR]: Data directory 440 - [--cache-dir DIR]: Cache directory 441 - [--persist-cookies]: Enable cookie persistence 442 - [--no-verify-tls]: Disable TLS verification 443 - [--timeout SECONDS]: Request timeout 444 - [--max-retries N]: Maximum retries 445 - [--retry-backoff FACTOR]: Retry backoff multiplier 446 - [--no-follow-redirects]: Disable redirect following 447 - [--max-redirects N]: Maximum redirects to follow 448 - [--user-agent STRING]: User-Agent header 449 450 {b Example:} 451 {[ 452 let open Cmdliner in 453 let config_t = Requests.Cmd.config_term "myapp" env#fs in 454 let main config = 455 Eio.Switch.run @@ fun sw -> 456 let req = Requests.Cmd.create config env sw in 457 (* Use requests *) 458 in 459 let cmd = Cmd.v info Term.(const main $ config_t) in 460 Cmd.eval cmd 461 ]} *) 462 463 val requests_term : string -> 464 < clock: [> float Eio.Time.clock_ty] Eio.Resource.t; 465 net: [> [> `Generic] Eio.Net.ty] Eio.Resource.t; 466 fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> 467 Eio.Switch.t -> t Cmdliner.Term.t 468 (** [requests_term app_name env sw] creates a term that directly produces a requests instance. 469 470 This is a convenience function that combines configuration parsing 471 with requests creation. 472 473 {b Example:} 474 {[ 475 let open Cmdliner in 476 let main req = 477 (* Use requests directly *) 478 let resp = Requests.get req "https://example.com" in 479 (* ... *) 480 in 481 Eio.Switch.run @@ fun sw -> 482 let req_t = Requests.Cmd.requests_term "myapp" env sw in 483 let cmd = Cmd.v info Term.(const main $ req_t) in 484 Cmd.eval cmd 485 ]} *) 486 487 val minimal_term : string -> Eio.Fs.dir_ty Eio.Path.t -> (Xdge.t * bool) Cmdliner.Term.t 488 (** [minimal_term app_name fs] creates a minimal configuration term. 489 490 This only provides: 491 - [--cache-dir DIR]: Cache directory for responses 492 - [--persist-cookies]: Cookie persistence flag 493 494 Returns the XDG context and persist_cookies boolean. 495 496 {b Example:} 497 {[ 498 let open Cmdliner in 499 let minimal_t = Requests.Cmd.minimal_term "myapp" env#fs in 500 let main (xdg, persist) = 501 Eio.Switch.run @@ fun sw -> 502 let req = Requests.create ~sw ~xdg ~persist_cookies:persist env in 503 (* Use requests *) 504 in 505 let cmd = Cmd.v info Term.(const main $ minimal_t) in 506 Cmd.eval cmd 507 ]} *) 508 509 (** {2 Documentation} *) 510 511 val env_docs : string -> string 512 (** [env_docs app_name] generates environment variable documentation. 513 514 Returns formatted documentation for all environment variables that 515 affect requests configuration, including XDG variables. 516 517 {b Included Variables:} 518 - [${APP_NAME}_CONFIG_DIR]: Configuration directory 519 - [${APP_NAME}_DATA_DIR]: Data directory 520 - [${APP_NAME}_CACHE_DIR]: Cache directory 521 - [${APP_NAME}_STATE_DIR]: State directory 522 - [XDG_CONFIG_HOME], [XDG_DATA_HOME], [XDG_CACHE_HOME], [XDG_STATE_HOME] 523 - [HTTP_PROXY], [HTTPS_PROXY], [NO_PROXY] (when proxy support is added) 524 525 {b Example:} 526 {[ 527 let env_info = Cmdliner.Cmd.Env.info 528 ~docs:Cmdliner.Manpage.s_environment 529 ~doc:(Requests.Cmd.env_docs "myapp") 530 () 531 ]} *) 532 533 val pp_config : Format.formatter -> config -> unit 534 (** Pretty print configuration for debugging *) 535end 536 537(** Retry policies and backoff strategies *) 538module Retry = Retry 539 540(** {1 One-Shot API} 541 542 The One module provides direct control over HTTP requests without 543 session state. Use this for stateless operations or when you need 544 fine-grained control. 545*) 546 547(** One-shot HTTP client for stateless requests *) 548module One = One 549 550(** {1 Core Types} 551 552 These modules define the fundamental types used throughout the library. 553*) 554 555(** HTTP response handling *) 556module Response = Response 557 558(** Request body construction and encoding *) 559module Body = Body 560 561(** HTTP headers manipulation *) 562module Headers = Headers 563 564(** Authentication schemes (Basic, Bearer, OAuth, etc.) *) 565module Auth = Auth 566 567(** Error types and exception handling *) 568module Error = Error 569 570(** {1 Supporting Types} *) 571 572(** HTTP status codes and reason phrases *) 573module Status = Status 574 575(** HTTP request methods (GET, POST, etc.) *) 576module Method = Method 577 578(** MIME types for content negotiation *) 579module Mime = Mime 580 581(** Timeout configuration for requests *) 582module Timeout = Timeout 583 584(** {2 Logging} *) 585 586(** Log source for the requests library. 587 Use [Logs.Src.set_level src] to control logging verbosity. 588 Example: [Logs.Src.set_level Requests.src (Some Logs.Debug)] *) 589val src : Logs.Src.t