(** Requests - A modern HTTP client library for OCaml Requests is an HTTP client library for OCaml inspired by Python's requests and urllib3 libraries. It provides a simple, intuitive API for making HTTP requests while handling complexities like TLS configuration, connection pooling, retries, and cookie management. {2 High-Level API} The Requests library offers two main ways to make HTTP requests: {b 1. Main API} (Recommended for most use cases) The main API maintains state across requests, handles cookies automatically, spawns requests in concurrent fibers, and provides a simple interface: {[ open Eio_main let () = run @@ fun env -> Switch.run @@ fun sw -> (* Create a requests instance *) let req = Requests.create ~sw env in (* Configure authentication once *) Requests.set_auth req (Requests.Auth.bearer "your-token"); (* Make concurrent requests using Fiber.both *) let (user, repos) = Eio.Fiber.both (fun () -> Requests.get req "https://api.github.com/user") (fun () -> Requests.get req "https://api.github.com/user/repos") in (* Process responses *) let user_data = Response.body user |> Eio.Flow.read_all in let repos_data = Response.body repos |> Eio.Flow.read_all in (* No cleanup needed - responses auto-close with the switch *) ]} {b 2. One-shot requests} (For stateless operations) The One module provides lower-level control for stateless, one-off requests without session state: {[ (* Create a one-shot client *) let client = Requests.One.create ~clock:env#clock ~net:env#net () in (* Make a simple GET request *) let response = Requests.One.get ~sw ~client "https://api.github.com" in Printf.printf "Status: %d\n" (Requests.Response.status_code response); (* POST with custom headers and body *) let response = Requests.One.post ~sw ~client ~headers:(Requests.Headers.empty |> Requests.Headers.content_type Requests.Mime.json |> Requests.Headers.set "X-API-Key" "secret") ~body:(Requests.Body.json {|{"name": "Alice"}|}) "https://api.example.com/users" (* No cleanup needed - responses auto-close with the switch *) ]} {2 Features} - {b Simple API}: Intuitive functions for GET, POST, PUT, DELETE, etc. - {b Authentication}: Built-in support for Basic, Bearer, Digest, and OAuth - {b Streaming}: Upload and download large files efficiently - {b Retries}: Automatic retry with exponential backoff - {b Timeouts}: Configurable connection and read timeouts - {b Cookie Management}: Automatic cookie handling with persistence - {b TLS/SSL}: Secure connections with certificate verification - {b Error Handling}: Comprehensive error types and recovery {2 Common Use Cases} {b Working with JSON APIs:} {[ let response = Requests.post req "https://api.example.com/data" ~body:(Requests.Body.json {|{"key": "value"}|}) in let body_text = Requests.Response.body response |> Eio.Flow.read_all in print_endline body_text (* Response auto-closes with switch *) ]} {b File uploads:} {[ let body = Requests.Body.multipart [ { name = "file"; filename = Some "document.pdf"; content_type = Requests.Mime.pdf; content = `File (Eio.Path.(fs / "document.pdf")) }; { name = "description"; filename = None; content_type = Requests.Mime.text_plain; content = `String "Important document" } ] in let response = Requests.post req "https://example.com/upload" ~body (* Response auto-closes with switch *) ]} {b Streaming downloads:} {[ Requests.One.download ~sw ~client "https://example.com/large-file.zip" ~sink:(Eio.Path.(fs / "download.zip" |> sink)) ]} {2 Choosing Between Main API and One} Use the {b main API (Requests.t)} when you need: - Cookie persistence across requests - Automatic retry handling - Shared authentication across requests - Request/response history tracking - Configuration persistence to disk Use {b One} when you need: - One-off stateless requests - Fine-grained control over connections - Minimal overhead - Custom connection pooling - Direct streaming without cookies *) (** {1 Main API} The main Requests API provides stateful HTTP clients with automatic cookie management and persistent configuration. Requests execute synchronously by default. Use Eio.Fiber.both or Eio.Fiber.all for concurrent execution. *) type ('clock, 'net) t (** A stateful HTTP client that maintains cookies, auth, configuration, and connection pools across requests. *) (** {2 Creation and Configuration} *) val create : sw:Eio.Switch.t -> ?http_pool:('clock Eio.Time.clock, 'net Eio.Net.t) Conpool.t -> ?https_pool:('clock Eio.Time.clock, 'net Eio.Net.t) Conpool.t -> ?cookie_jar:Cookeio.jar -> ?default_headers:Headers.t -> ?auth:Auth.t -> ?timeout:Timeout.t -> ?follow_redirects:bool -> ?max_redirects:int -> ?verify_tls:bool -> ?tls_config:Tls.Config.client -> ?max_connections_per_host:int -> ?connection_idle_timeout:float -> ?connection_lifetime:float -> ?retry:Retry.config -> ?persist_cookies:bool -> ?xdg:Xdge.t -> < clock: 'clock Eio.Resource.t; net: 'net Eio.Resource.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> ('clock Eio.Resource.t, 'net Eio.Resource.t) t (** Create a new requests instance with persistent state and connection pooling. All resources are bound to the provided switch and will be cleaned up automatically. @param sw Switch for resource management @param http_pool Optional pre-configured HTTP connection pool (creates new if not provided) @param https_pool Optional pre-configured HTTPS connection pool (creates new if not provided) @param cookie_jar Cookie storage (default: empty in-memory jar) @param default_headers Headers included in every request @param auth Default authentication @param timeout Default timeout configuration @param follow_redirects Whether to follow HTTP redirects (default: true) @param max_redirects Maximum redirects to follow (default: 10) @param verify_tls Whether to verify TLS certificates (default: true) @param tls_config Custom TLS configuration for HTTPS pool (default: system CA certs) @param max_connections_per_host Maximum pooled connections per host:port (default: 10) @param connection_idle_timeout Max idle time before closing pooled connection (default: 60s) @param connection_lifetime Max lifetime of any pooled connection (default: 300s) @param retry Retry configuration for failed requests @param persist_cookies Whether to persist cookies to disk (default: false) @param xdg XDG directory context for cookies (required if persist_cookies=true) {b Note:} HTTP caching has been disabled for simplicity. See CACHEIO.md for integration notes if you need to restore caching functionality in the future. *) (** {2 Configuration Management} *) val set_default_header : ('clock, 'net) t -> string -> string -> ('clock, 'net) t (** Add or update a default header. Returns a new session with the updated header. The original session's connection pools are shared. *) val remove_default_header : ('clock, 'net) t -> string -> ('clock, 'net) t (** Remove a default header. Returns a new session without the header. *) val set_auth : ('clock, 'net) t -> Auth.t -> ('clock, 'net) t (** Set default authentication. Returns a new session with auth configured. *) val clear_auth : ('clock, 'net) t -> ('clock, 'net) t (** Clear authentication. Returns a new session without auth. *) val set_timeout : ('clock, 'net) t -> Timeout.t -> ('clock, 'net) t (** Set default timeout. Returns a new session with the timeout configured. *) val set_retry : ('clock, 'net) t -> Retry.config -> ('clock, 'net) t (** Set retry configuration. Returns a new session with retry configured. *) (** {2 Request Methods} All request methods execute synchronously. To make concurrent requests, you must explicitly use Eio.Fiber.both or Eio.Fiber.all. The response will auto-close when the parent switch closes. Example of concurrent requests using Fiber.both: {[ let req = Requests.create ~sw env in (* Use Fiber.both for two concurrent requests *) let (r1, r2) = Eio.Fiber.both (fun () -> Requests.get req "https://api1.example.com") (fun () -> Requests.post req "https://api2.example.com" ~body) in (* Process responses *) let body1 = Response.body r1 |> Eio.Flow.read_all in let body2 = Response.body r2 |> Eio.Flow.read_all in ]} Example using Fiber.all for multiple requests: {[ let req = Requests.create ~sw env in (* Use Fiber.all for multiple concurrent requests *) let urls = [ "https://api1.example.com"; "https://api2.example.com"; "https://api3.example.com"; ] in let responses = ref [] in Eio.Fiber.all [ (fun () -> responses := Requests.get req (List.nth urls 0) :: !responses); (fun () -> responses := Requests.get req (List.nth urls 1) :: !responses); (fun () -> responses := Requests.get req (List.nth urls 2) :: !responses); ]; (* Process all responses *) List.iter (fun r -> let body = Response.body r |> Eio.Flow.read_all in print_endline body ) !responses ]} Example using Promise for concurrent requests with individual control: {[ let req = Requests.create ~sw env in (* Start requests in parallel using promises *) let p1, r1 = Eio.Promise.create () in let p2, r2 = Eio.Promise.create () in let p3, r3 = Eio.Promise.create () in Eio.Fiber.fork ~sw (fun () -> Eio.Promise.resolve r1 (Requests.get req "https://api1.example.com") ); Eio.Fiber.fork ~sw (fun () -> Eio.Promise.resolve r2 (Requests.post req "https://api2.example.com" ~body) ); Eio.Fiber.fork ~sw (fun () -> Eio.Promise.resolve r3 (Requests.get req "https://api3.example.com") ); (* Wait for all promises and process *) let resp1 = Eio.Promise.await p1 in let resp2 = Eio.Promise.await p2 in let resp3 = Eio.Promise.await p3 in (* Process responses *) let body1 = Response.body resp1 |> Eio.Flow.read_all in let body2 = Response.body resp2 |> Eio.Flow.read_all in let body3 = Response.body resp3 |> Eio.Flow.read_all in ]} *) val request : (_ Eio.Time.clock, _ Eio.Net.t) t -> ?headers:Headers.t -> ?body:Body.t -> ?auth:Auth.t -> ?timeout:Timeout.t -> ?follow_redirects:bool -> ?max_redirects:int -> method_:Method.t -> string -> Response.t (** Make a concurrent HTTP request *) val get : (_ Eio.Time.clock, _ Eio.Net.t) t -> ?headers:Headers.t -> ?auth:Auth.t -> ?timeout:Timeout.t -> ?params:(string * string) list -> string -> Response.t (** Concurrent GET request *) val post : (_ Eio.Time.clock, _ Eio.Net.t) t -> ?headers:Headers.t -> ?body:Body.t -> ?auth:Auth.t -> ?timeout:Timeout.t -> string -> Response.t (** Concurrent POST request *) val put : (_ Eio.Time.clock, _ Eio.Net.t) t -> ?headers:Headers.t -> ?body:Body.t -> ?auth:Auth.t -> ?timeout:Timeout.t -> string -> Response.t (** Concurrent PUT request *) val patch : (_ Eio.Time.clock, _ Eio.Net.t) t -> ?headers:Headers.t -> ?body:Body.t -> ?auth:Auth.t -> ?timeout:Timeout.t -> string -> Response.t (** Concurrent PATCH request *) val delete : (_ Eio.Time.clock, _ Eio.Net.t) t -> ?headers:Headers.t -> ?auth:Auth.t -> ?timeout:Timeout.t -> string -> Response.t (** Concurrent DELETE request *) val head : (_ Eio.Time.clock, _ Eio.Net.t) t -> ?headers:Headers.t -> ?auth:Auth.t -> ?timeout:Timeout.t -> string -> Response.t (** Concurrent HEAD request *) val options : (_ Eio.Time.clock, _ Eio.Net.t) t -> ?headers:Headers.t -> ?auth:Auth.t -> ?timeout:Timeout.t -> string -> Response.t (** Concurrent OPTIONS request *) (** {2 Cookie Management} *) val cookies : ('clock, 'net) t -> Cookeio.jar (** Get the cookie jar for direct manipulation *) val clear_cookies : ('clock, 'net) t -> unit (** Clear all cookies *) (** {1 Cmdliner Integration} *) module Cmd : sig (** Cmdliner integration for Requests configuration. This module provides command-line argument handling for configuring HTTP requests, including XDG directory paths, timeouts, retries, and other parameters. *) (** Configuration from command line and environment *) type config = { xdg : Xdge.t * Xdge.Cmd.t; (** XDG paths and their sources *) persist_cookies : bool; (** Whether to persist cookies *) verify_tls : bool; (** Whether to verify TLS certificates *) timeout : float option; (** Request timeout in seconds *) max_retries : int; (** Maximum number of retries *) retry_backoff : float; (** Retry backoff factor *) follow_redirects : bool; (** Whether to follow redirects *) max_redirects : int; (** Maximum number of redirects *) user_agent : string option; (** User-Agent header *) } 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 (** [create config env sw] creates a requests instance from command-line configuration *) (** {2 Individual Terms} *) val persist_cookies_term : string -> bool Cmdliner.Term.t (** Term for [--persist-cookies] flag with app-specific env var *) val verify_tls_term : string -> bool Cmdliner.Term.t (** Term for [--no-verify-tls] flag with app-specific env var *) val timeout_term : string -> float option Cmdliner.Term.t (** Term for [--timeout SECONDS] option with app-specific env var *) val retries_term : string -> int Cmdliner.Term.t (** Term for [--max-retries N] option with app-specific env var *) val retry_backoff_term : string -> float Cmdliner.Term.t (** Term for [--retry-backoff FACTOR] option with app-specific env var *) val follow_redirects_term : string -> bool Cmdliner.Term.t (** Term for [--no-follow-redirects] flag with app-specific env var *) val max_redirects_term : string -> int Cmdliner.Term.t (** Term for [--max-redirects N] option with app-specific env var *) val user_agent_term : string -> string option Cmdliner.Term.t (** Term for [--user-agent STRING] option with app-specific env var *) (** {2 Combined Terms} *) val config_term : string -> Eio.Fs.dir_ty Eio.Path.t -> config Cmdliner.Term.t (** [config_term app_name fs] creates a complete configuration term. This combines all individual terms plus XDG configuration into a single term that can be used to configure requests. {b Generated Flags:} - [--config-dir DIR]: Configuration directory - [--data-dir DIR]: Data directory - [--cache-dir DIR]: Cache directory - [--persist-cookies]: Enable cookie persistence - [--no-verify-tls]: Disable TLS verification - [--timeout SECONDS]: Request timeout - [--max-retries N]: Maximum retries - [--retry-backoff FACTOR]: Retry backoff multiplier - [--no-follow-redirects]: Disable redirect following - [--max-redirects N]: Maximum redirects to follow - [--user-agent STRING]: User-Agent header {b Example:} {[ let open Cmdliner in let config_t = Requests.Cmd.config_term "myapp" env#fs in let main config = Eio.Switch.run @@ fun sw -> let req = Requests.Cmd.create config env sw in (* Use requests *) in let cmd = Cmd.v info Term.(const main $ config_t) in Cmd.eval cmd ]} *) 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 (** [requests_term app_name env sw] creates a term that directly produces a requests instance. This is a convenience function that combines configuration parsing with requests creation. {b Example:} {[ let open Cmdliner in let main req = (* Use requests directly *) let resp = Requests.get req "https://example.com" in (* ... *) in Eio.Switch.run @@ fun sw -> let req_t = Requests.Cmd.requests_term "myapp" env sw in let cmd = Cmd.v info Term.(const main $ req_t) in Cmd.eval cmd ]} *) val minimal_term : string -> Eio.Fs.dir_ty Eio.Path.t -> (Xdge.t * bool) Cmdliner.Term.t (** [minimal_term app_name fs] creates a minimal configuration term. This only provides: - [--cache-dir DIR]: Cache directory for responses - [--persist-cookies]: Cookie persistence flag Returns the XDG context and persist_cookies boolean. {b Example:} {[ let open Cmdliner in let minimal_t = Requests.Cmd.minimal_term "myapp" env#fs in let main (xdg, persist) = Eio.Switch.run @@ fun sw -> let req = Requests.create ~sw ~xdg ~persist_cookies:persist env in (* Use requests *) in let cmd = Cmd.v info Term.(const main $ minimal_t) in Cmd.eval cmd ]} *) (** {2 Documentation} *) val env_docs : string -> string (** [env_docs app_name] generates environment variable documentation. Returns formatted documentation for all environment variables that affect requests configuration, including XDG variables. {b Included Variables:} - [${APP_NAME}_CONFIG_DIR]: Configuration directory - [${APP_NAME}_DATA_DIR]: Data directory - [${APP_NAME}_CACHE_DIR]: Cache directory - [${APP_NAME}_STATE_DIR]: State directory - [XDG_CONFIG_HOME], [XDG_DATA_HOME], [XDG_CACHE_HOME], [XDG_STATE_HOME] - [HTTP_PROXY], [HTTPS_PROXY], [NO_PROXY] (when proxy support is added) {b Example:} {[ let env_info = Cmdliner.Cmd.Env.info ~docs:Cmdliner.Manpage.s_environment ~doc:(Requests.Cmd.env_docs "myapp") () ]} *) val pp_config : Format.formatter -> config -> unit (** Pretty print configuration for debugging *) end (** Retry policies and backoff strategies *) module Retry = Retry (** {1 One-Shot API} The One module provides direct control over HTTP requests without session state. Use this for stateless operations or when you need fine-grained control. *) (** One-shot HTTP client for stateless requests *) module One = One (** {1 Core Types} These modules define the fundamental types used throughout the library. *) (** HTTP response handling *) module Response = Response (** Request body construction and encoding *) module Body = Body (** HTTP headers manipulation *) module Headers = Headers (** Authentication schemes (Basic, Bearer, OAuth, etc.) *) module Auth = Auth (** Error types and exception handling *) module Error = Error (** {1 Supporting Types} *) (** HTTP status codes and reason phrases *) module Status = Status (** HTTP request methods (GET, POST, etc.) *) module Method = Method (** MIME types for content negotiation *) module Mime = Mime (** Timeout configuration for requests *) module Timeout = Timeout (** {2 Logging} *) (** Log source for the requests library. Use [Logs.Src.set_level src] to control logging verbosity. Example: [Logs.Src.set_level Requests.src (Some Logs.Debug)] *) val src : Logs.Src.t