A batteries included HTTP/1.1 client in OCaml

fix build

+53 -52
+28 -28
lib/requests.ml
··· 23 23 24 24 (* Main API - Session functionality with connection pooling *) 25 25 26 - type ('clock, 'net) t = { 26 + type t = T : { 27 27 sw : Eio.Switch.t; 28 - clock : 'clock; 29 - net : 'net; 30 - http_pool : ('clock, 'net) Conpool.t; 31 - https_pool : ('clock, 'net) Conpool.t; 28 + clock : [> float Eio.Time.clock_ty] Eio.Resource.t; 29 + net : [> [> `Generic] Eio.Net.ty] Eio.Resource.t; 30 + http_pool : Conpool.t; 31 + https_pool : Conpool.t; 32 32 cookie_jar : Cookeio_jar.t; 33 33 cookie_mutex : Eio.Mutex.t; 34 34 default_headers : Headers.t; ··· 49 49 mutable requests_made : int; 50 50 mutable total_time : float; 51 51 mutable retries_count : int; 52 - } 52 + } -> t 53 53 54 54 let create 55 55 ~sw ··· 136 136 Cookeio_jar.create () 137 137 in 138 138 139 - { 139 + T { 140 140 sw; 141 141 clock; 142 142 net; ··· 159 159 retries_count = 0; 160 160 } 161 161 162 - let set_default_header t key value = 163 - { t with default_headers = Headers.set key value t.default_headers } 162 + let set_default_header (T t) key value = 163 + T { t with default_headers = Headers.set key value t.default_headers } 164 164 165 - let remove_default_header t key = 166 - { t with default_headers = Headers.remove key t.default_headers } 165 + let remove_default_header (T t) key = 166 + T { t with default_headers = Headers.remove key t.default_headers } 167 167 168 - let set_auth t auth = 168 + let set_auth (T t) auth = 169 169 Log.debug (fun m -> m "Setting authentication method"); 170 - { t with auth = Some auth } 170 + T { t with auth = Some auth } 171 171 172 - let clear_auth t = 172 + let clear_auth (T t) = 173 173 Log.debug (fun m -> m "Clearing authentication"); 174 - { t with auth = None } 174 + T { t with auth = None } 175 175 176 - let set_timeout t timeout = 176 + let set_timeout (T t) timeout = 177 177 Log.debug (fun m -> m "Setting timeout: %a" Timeout.pp timeout); 178 - { t with timeout } 178 + T { t with timeout } 179 179 180 - let set_retry t config = 180 + let set_retry (T t) config = 181 181 Log.debug (fun m -> m "Setting retry config: max_retries=%d" config.Retry.max_retries); 182 - { t with retry = Some config } 182 + T { t with retry = Some config } 183 183 184 - let cookies t = t.cookie_jar 185 - let clear_cookies t = Cookeio_jar.clear t.cookie_jar 184 + let cookies (T t) = t.cookie_jar 185 + let clear_cookies (T t) = Cookeio_jar.clear t.cookie_jar 186 186 187 187 (* Internal request function using connection pools *) 188 - let make_request_internal t ?headers ?body ?auth ?timeout ?follow_redirects ?max_redirects ~method_ url = 188 + let make_request_internal (T t) ?headers ?body ?auth ?timeout ?follow_redirects ?max_redirects ~method_ url = 189 189 let start_time = Unix.gettimeofday () in 190 190 let method_str = Method.to_string method_ in 191 191 ··· 238 238 List.iter (fun cookie_str -> 239 239 let now = fun () -> Ptime.of_float_s (Eio.Time.now t.clock) |> Option.get in 240 240 match Cookeio.of_set_cookie_header ~now ~domain:cookie_domain ~path:cookie_path cookie_str with 241 - | Some cookie -> 241 + | Ok cookie -> 242 242 Log.debug (fun m -> m "Storing cookie: %s" (Cookeio.name cookie)); 243 243 Cookeio_jar.add_cookie t.cookie_jar cookie 244 - | None -> 245 - Log.warn (fun m -> m "Failed to parse cookie: %s" cookie_str) 244 + | Error msg -> 245 + Log.warn (fun m -> m "Failed to parse cookie: %s (%s)" cookie_str msg) 246 246 ) cookie_headers 247 247 ) 248 248 in ··· 414 414 response 415 415 416 416 (* Public request function - executes synchronously with retry support *) 417 - let request t ?headers ?body ?auth ?timeout ?follow_redirects ?max_redirects ~method_ url = 417 + let request (T t as wrapped_t) ?headers ?body ?auth ?timeout ?follow_redirects ?max_redirects ~method_ url = 418 418 match t.retry with 419 419 | None -> 420 420 (* No retry configured, execute directly *) 421 - make_request_internal t ?headers ?body ?auth ?timeout 421 + make_request_internal wrapped_t ?headers ?body ?auth ?timeout 422 422 ?follow_redirects ?max_redirects ~method_ url 423 423 | Some retry_config -> 424 424 (* Wrap in retry logic *) ··· 436 436 (Method.to_string method_) url); 437 437 438 438 try 439 - let response = make_request_internal t ?headers ?body ?auth ?timeout 439 + let response = make_request_internal wrapped_t ?headers ?body ?auth ?timeout 440 440 ?follow_redirects ?max_redirects ~method_ url in 441 441 let status = Response.status_code response in 442 442
+25 -24
lib/requests.mli
··· 198 198 Use Eio.Fiber.both or Eio.Fiber.all for concurrent execution. 199 199 *) 200 200 201 - type ('clock, 'net) t 201 + type t 202 202 (** A stateful HTTP client that maintains cookies, auth, configuration, and 203 - connection pools across requests. *) 203 + connection pools across requests. The clock and network resources are 204 + existentially quantified and hidden behind this abstract type. *) 204 205 205 206 (** {2 Creation and Configuration} *) 206 207 207 208 val create : 208 209 sw:Eio.Switch.t -> 209 - ?http_pool:('clock Eio.Time.clock, 'net Eio.Net.t) Conpool.t -> 210 - ?https_pool:('clock Eio.Time.clock, 'net Eio.Net.t) Conpool.t -> 210 + ?http_pool:Conpool.t -> 211 + ?https_pool:Conpool.t -> 211 212 ?cookie_jar:Cookeio_jar.t -> 212 213 ?default_headers:Headers.t -> 213 214 ?auth:Auth.t -> ··· 222 223 ?retry:Retry.config -> 223 224 ?persist_cookies:bool -> 224 225 ?xdg:Xdge.t -> 225 - < clock: 'clock Eio.Resource.t; net: 'net Eio.Resource.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> 226 - ('clock Eio.Resource.t, 'net Eio.Resource.t) t 226 + < clock: _ Eio.Time.clock; net: _ Eio.Net.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> 227 + t 227 228 (** Create a new requests instance with persistent state and connection pooling. 228 229 All resources are bound to the provided switch and will be cleaned up automatically. 229 230 ··· 251 252 252 253 (** {2 Configuration Management} *) 253 254 254 - val set_default_header : ('clock, 'net) t -> string -> string -> ('clock, 'net) t 255 + val set_default_header : t -> string -> string -> t 255 256 (** Add or update a default header. Returns a new session with the updated header. 256 257 The original session's connection pools are shared. *) 257 258 258 - val remove_default_header : ('clock, 'net) t -> string -> ('clock, 'net) t 259 + val remove_default_header : t -> string -> t 259 260 (** Remove a default header. Returns a new session without the header. *) 260 261 261 - val set_auth : ('clock, 'net) t -> Auth.t -> ('clock, 'net) t 262 + val set_auth : t -> Auth.t -> t 262 263 (** Set default authentication. Returns a new session with auth configured. *) 263 264 264 - val clear_auth : ('clock, 'net) t -> ('clock, 'net) t 265 + val clear_auth : t -> t 265 266 (** Clear authentication. Returns a new session without auth. *) 266 267 267 - val set_timeout : ('clock, 'net) t -> Timeout.t -> ('clock, 'net) t 268 + val set_timeout : t -> Timeout.t -> t 268 269 (** Set default timeout. Returns a new session with the timeout configured. *) 269 270 270 - val set_retry : ('clock, 'net) t -> Retry.config -> ('clock, 'net) t 271 + val set_retry : t -> Retry.config -> t 271 272 (** Set retry configuration. Returns a new session with retry configured. *) 272 273 273 274 (** {2 Request Methods} ··· 348 349 *) 349 350 350 351 val request : 351 - (_ Eio.Time.clock, _ Eio.Net.t) t -> 352 + t -> 352 353 ?headers:Headers.t -> 353 354 ?body:Body.t -> 354 355 ?auth:Auth.t -> ··· 361 362 (** Make a concurrent HTTP request *) 362 363 363 364 val get : 364 - (_ Eio.Time.clock, _ Eio.Net.t) t -> 365 + t -> 365 366 ?headers:Headers.t -> 366 367 ?auth:Auth.t -> 367 368 ?timeout:Timeout.t -> ··· 371 372 (** Concurrent GET request *) 372 373 373 374 val post : 374 - (_ Eio.Time.clock, _ Eio.Net.t) t -> 375 + t -> 375 376 ?headers:Headers.t -> 376 377 ?body:Body.t -> 377 378 ?auth:Auth.t -> ··· 381 382 (** Concurrent POST request *) 382 383 383 384 val put : 384 - (_ Eio.Time.clock, _ Eio.Net.t) t -> 385 + t -> 385 386 ?headers:Headers.t -> 386 387 ?body:Body.t -> 387 388 ?auth:Auth.t -> ··· 391 392 (** Concurrent PUT request *) 392 393 393 394 val patch : 394 - (_ Eio.Time.clock, _ Eio.Net.t) t -> 395 + t -> 395 396 ?headers:Headers.t -> 396 397 ?body:Body.t -> 397 398 ?auth:Auth.t -> ··· 401 402 (** Concurrent PATCH request *) 402 403 403 404 val delete : 404 - (_ Eio.Time.clock, _ Eio.Net.t) t -> 405 + t -> 405 406 ?headers:Headers.t -> 406 407 ?auth:Auth.t -> 407 408 ?timeout:Timeout.t -> ··· 410 411 (** Concurrent DELETE request *) 411 412 412 413 val head : 413 - (_ Eio.Time.clock, _ Eio.Net.t) t -> 414 + t -> 414 415 ?headers:Headers.t -> 415 416 ?auth:Auth.t -> 416 417 ?timeout:Timeout.t -> ··· 419 420 (** Concurrent HEAD request *) 420 421 421 422 val options : 422 - (_ Eio.Time.clock, _ Eio.Net.t) t -> 423 + t -> 423 424 ?headers:Headers.t -> 424 425 ?auth:Auth.t -> 425 426 ?timeout:Timeout.t -> ··· 429 430 430 431 (** {2 Cookie Management} *) 431 432 432 - val cookies : ('clock, 'net) t -> Cookeio_jar.t 433 + val cookies : t -> Cookeio_jar.t 433 434 (** Get the cookie jar for direct manipulation *) 434 435 435 - val clear_cookies : ('clock, 'net) t -> unit 436 + val clear_cookies : t -> unit 436 437 (** Clear all cookies *) 437 438 438 439 (** {1 Cmdliner Integration} *) ··· 458 459 verbose_http : bool; (** Enable verbose HTTP-level logging *) 459 460 } 460 461 461 - 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 462 + val create : config -> < clock: _ Eio.Time.clock; net: _ Eio.Net.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> Eio.Switch.t -> t 462 463 (** [create config env sw] creates a requests instance from command-line configuration *) 463 464 464 465 (** {2 Individual Terms} *) ··· 529 530 Cmd.eval cmd 530 531 ]} *) 531 532 532 - 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 533 + 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 533 534 (** [requests_term app_name env sw] creates a term that directly produces a requests instance. 534 535 535 536 This is a convenience function that combines configuration parsing