···9999 | Bearer { token } ->
100100 Cohttp.Header.add headers "Authorization" (Printf.sprintf "Bearer %s" token)
101101 | OAuth1 { consumer_key; consumer_secret; token; token_secret; signature_method } ->
102102- (* TODO FIXME use mirage-random properly here instead of timestamp/nonce *)
103102 let timestamp = Printf.sprintf "%.0f" (Unix.gettimeofday ()) in
104104- let nonce = Printf.sprintf "%016x%016x" (Random.bits ()) (Random.bits ()) in
103103+ (* Generate cryptographically secure nonce using mirage-crypto-rng *)
104104+ let nonce_bytes = Mirage_crypto_rng.generate 16 in
105105+ let nonce = Base64.encode_string (Cstruct.to_string nonce_bytes) in
105106106107 let signature_method_str = match signature_method with
107108 | `HMAC_SHA1 -> "HMAC-SHA1"
···324325 let jittered = base_time +. Random.float t.backoff.jitter in
325326 min jittered t.backoff.max
326327327327- let sleep ~sw:_ t response =
328328+ let sleep ~clock t response =
328329 let backoff_time =
329330 match t.respect_retry_after, response with
330331 | true, Some resp ->
···335336 | _ -> get_backoff_time t
336337 in
337338 if backoff_time > 0.0 then
338338- Unix.sleepf backoff_time
339339+ Eio.Time.sleep clock backoff_time
339340end
340341341342module Config = struct
···406407 Tls.Config.client ~authenticator ()
407408end
408409410410+type clock = Clock : _ Eio.Time.clock -> clock
411411+409412type 'a t = {
410413 net : 'a Net.t;
414414+ clock : clock;
411415 tls_config : Tls.config;
412416 default_headers : Cohttp.Header.t;
413417} constraint 'a = [> `Generic] Net.ty
414418415415-let create ?(tls_config=Tls.default ()) ?(default_headers=Cohttp.Header.init ()) net =
416416- { net; tls_config; default_headers }
419419+let create ?(tls_config=Tls.default ()) ?(default_headers=Cohttp.Header.init ()) ~clock net =
420420+ { net; clock = Clock clock; tls_config; default_headers }
417421418422419423let make_client net tls_config =
···491495 Uri.pp uri (retry_state.Retry.retry_count + 1) retry_state.Retry.total);
492496 let retry_state = Retry.increment retry_state ~method_:meth ~url:uri
493497 ~response:result () in
494494- Retry.sleep ~sw retry_state (Some result);
498498+ (match t.clock with Clock c -> Retry.sleep ~clock:c retry_state (Some result));
495499 request_with_retries ~sw t ~config ?body ~meth uri retry_state
496500 end else
497501 raise (Request_error (Http_error {
···512516 (retry_state.Retry.retry_count + 1) retry_state.Retry.total);
513517 let retry_state = Retry.increment retry_state ~method_:meth ~url:uri
514518 ~error:(Request_error (Connection_error (Printexc.to_string e))) () in
515515- Retry.sleep ~sw retry_state None;
519519+ (match t.clock with Clock c -> Retry.sleep ~clock:c retry_state None);
516520 request_with_retries ~sw t ~config ?body ~meth uri retry_state
517521 end else
518522 raise (Request_error (Connection_error (Printexc.to_string e)))
···603607604608 type 'a t = 'a session constraint 'a = [> `Generic] Net.ty
605609606606- let create ?tls_config ?default_headers net =
607607- { client = create ?tls_config ?default_headers net;
610610+ let create ?tls_config ?default_headers ~clock net =
611611+ { client = create ?tls_config ?default_headers ~clock net;
608612 cookies = ref [] }
609613610614 let parse_cookie_header cookie_str =
···10261030 type 'a pool_manager = {
10271031 sw : Switch.t;
10281032 net : 'a Net.t;
10331033+ clock : clock;
10291034 pools : (string, ConnectionPool.t) Hashtbl.t;
10301035 num_pools : int;
10311036 headers : Cohttp.Header.t;
···1038104310391044 type 'a t = 'a pool_manager constraint 'a = [> `Generic] Net.ty
1040104510411041- let create ~sw ?(num_pools=10) ?(headers=Cohttp.Header.init ())
10461046+ let create ~sw ~clock ?(num_pools=10) ?(headers=Cohttp.Header.init ())
10421047 ?(retries=Retry.default) ?(timeout=Timeout.default)
10431048 ?(pool_config=ConnectionPool.default_config) ?tls_config ?cache net =
10441049 let cache = Option.map Cache.create cache in
10451045- { sw; net; pools = Hashtbl.create num_pools; num_pools;
10501050+ { sw; net; clock = Clock clock; pools = Hashtbl.create num_pools; num_pools;
10461051 headers; retries; timeout; pool_config; tls_config; cache }
1047105210481053 let get_pool t ~scheme ~host ~port =
···10961101 | None -> headers
10971102 in
1098110310991099- let create_client = fun ?tls_config ?default_headers net ->
11001100- { net; tls_config = Option.value ~default:(Tls.default ()) tls_config;
11041104+ let create_client = fun ?tls_config ?default_headers ~clock net ->
11051105+ { net; clock; tls_config = Option.value ~default:(Tls.default ()) tls_config;
11011106 default_headers = Option.value ~default:(Cohttp.Header.init ()) default_headers }
11021107 in
11031103- let req_client = create_client ?tls_config:t.tls_config ~default_headers:t.headers t.net in
11081108+ let req_client = create_client ?tls_config:t.tls_config ~default_headers:t.headers ~clock:t.clock t.net in
11041109 let config = Config.create ~headers ~follow_redirects:redirect () in
1105111011061111 (* Wrap request with timeout if specified *)
···11491154 | Result.Error `Timeout ->
11501155 if attempt < retries.Retry.total then (
11511156 Log.info (fun m -> m "Request timeout, retry %d/%d" (attempt + 1) retries.Retry.total);
11521152- Unix.sleepf 1.0;
11571157+ (match t.clock with Clock c -> Eio.Time.sleep c 1.0);
11531158 execute_with_retries (attempt + 1)
11541159 ) else (
11551160 ConnectionPool.put_connection pool conn;
+4-1
requests/lib/requests.mli
···138138val create :
139139 ?tls_config:Tls.config ->
140140 ?default_headers:Cohttp.Header.t ->
141141+ clock:_ Eio.Time.clock ->
141142 'a Net.t ->
142143 'a t
143144···246247 val create :
247248 ?tls_config:Tls.config ->
248249 ?default_headers:Cohttp.Header.t ->
250250+ clock:_ Eio.Time.clock ->
249251 'a Net.t ->
250252 'a t
251253···361363 val increment : t -> method_:meth -> url:Uri.t -> ?response:Response.t -> ?error:exn -> unit -> t
362364 val is_retry : t -> method_:meth -> status_code:int -> bool
363365 val get_backoff_time : t -> float
364364- val sleep : sw:Switch.t -> t -> Response.t option -> unit
366366+ val sleep : clock:_ Eio.Time.clock -> t -> Response.t option -> unit
365367end
366368367369(** Advanced timeout configuration *)
···425427426428 val create :
427429 sw:Switch.t ->
430430+ clock:_ Eio.Time.clock ->
428431 ?num_pools:int ->
429432 ?headers:Cohttp.Header.t ->
430433 ?retries:Retry.t ->
+8-8
requests/test/test_requests.ml
···2233let test_basic_get env =
44 Switch.run @@ fun sw ->
55- let client = Requests.create env#net in
55+ let client = Requests.create ~clock:env#clock env#net in
6677 (* Test simple GET request *)
88 let uri = Uri.of_string "https://api.github.com" in
···16161717let test_json_api env =
1818 Switch.run @@ fun sw ->
1919- let client = Requests.create env#net in
1919+ let client = Requests.create ~clock:env#clock env#net in
20202121 (* Test JSON API *)
2222 let uri = Uri.of_string "https://api.github.com/users/ocaml" in
···29293030let test_custom_headers env =
3131 Switch.run @@ fun sw ->
3232- let client = Requests.create env#net in
3232+ let client = Requests.create ~clock:env#clock env#net in
33333434 (* Test with custom headers *)
3535 let uri = Uri.of_string "https://api.github.com" in
···44444545let test_post_json env =
4646 Switch.run @@ fun sw ->
4747- let client = Requests.create env#net in
4747+ let client = Requests.create ~clock:env#clock env#net in
48484949 (* Test POST with JSON (to httpbin echo service) *)
5050 let uri = Uri.of_string "https://httpbin.org/post" in
···64646565let test_session_cookies env =
6666 Switch.run @@ fun sw ->
6767- let session = Requests.Session.create env#net in
6767+ let session = Requests.Session.create ~clock:env#clock env#net in
68686969 (* Test session with cookies *)
7070 let uri = Uri.of_string "https://httpbin.org/cookies/set?test=value" in
···84848585let test_error_handling env =
8686 Switch.run @@ fun sw ->
8787- let client = Requests.create env#net in
8787+ let client = Requests.create ~clock:env#clock env#net in
88888989 (* Test 404 error *)
9090 let uri = Uri.of_string "https://api.github.com/users/this-user-definitely-does-not-exist-12345" in
···101101 Switch.run @@ fun sw ->
102102103103 (* Test with default TLS config *)
104104- let client1 = Requests.create ~tls_config:(Requests.Tls.default ()) env#net in
104104+ let client1 = Requests.create ~clock:env#clock ~tls_config:(Requests.Tls.default ()) env#net in
105105 let uri = Uri.of_string "https://api.github.com" in
106106 let response1 = Requests.get ~sw client1 uri in
107107 assert (Requests.Response.is_success response1);
108108109109 (* Test with custom CA certs *)
110110 let auth = Result.get_ok (Ca_certs.authenticator ()) in
111111- let client2 = Requests.create ~tls_config:(Requests.Tls.with_ca_certs auth) env#net in
111111+ let client2 = Requests.create ~clock:env#clock ~tls_config:(Requests.Tls.with_ca_certs auth) env#net in
112112 let response2 = Requests.get ~sw client2 uri in
113113 assert (Requests.Response.is_success response2)
114114