A batteries included HTTP/1.1 client in OCaml
at main 188 lines 6.8 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6open Eio 7 8let () = 9 Crypto_rng_unix.use_default (); 10 Eio_main.run @@ fun env -> 11 Switch.run @@ fun sw -> 12 (* Example 1: Basic GET request *) 13 Printf.printf "\n=== Example 1: Basic GET Request ===\n%!"; 14 let req = Requests.v ~sw env in 15 let resp1 = Requests.get req "https://httpbin.org/get" in 16 Printf.printf "Status: %d\n%!" (Requests.Response.status_code resp1); 17 let body1 = 18 Requests.Response.body resp1 19 |> Buf_read.of_flow ~max_size:max_int 20 |> Buf_read.take_all 21 in 22 Printf.printf "Body length: %d bytes\n%!" (String.length body1); 23 24 (* Example 2: POST with JSON body *) 25 Printf.printf "\n=== Example 2: POST with JSON ===\n%!"; 26 let json_data = 27 Jsont.Object 28 ( [ 29 ("name", Jsont.String "Alice"); 30 ("email", Jsont.String "alice@example.com"); 31 ("age", Jsont.Number 30.0); 32 ], 33 Jsont.Meta.none ) 34 in 35 let resp2 = 36 Requests.post req 37 ~body:(Requests.Body.json json_data) 38 "https://httpbin.org/post" 39 in 40 Printf.printf "POST status: %d\n%!" (Requests.Response.status_code resp2); 41 42 (* Example 3: Custom headers and authentication *) 43 Printf.printf "\n=== Example 3: Custom Headers and Auth ===\n%!"; 44 let headers = 45 Requests.Headers.empty 46 |> Requests.Headers.set "X-Custom-Header" "MyValue" 47 |> Requests.Headers.user_agent "OCaml-Requests-Example/1.0" 48 in 49 let resp3 = 50 Requests.get req ~headers 51 ~auth:(Requests.Auth.bearer ~token:"demo-token-123") 52 "https://httpbin.org/bearer" 53 in 54 Printf.printf "Auth status: %d\n%!" (Requests.Response.status_code resp3); 55 56 (* Example 4: Session with default headers *) 57 Printf.printf "\n=== Example 4: Session with Default Headers ===\n%!"; 58 let req2 = Requests.v ~sw env in 59 let req2 = 60 Requests.set_default_header req2 "User-Agent" "OCaml-Requests/1.0" 61 in 62 let req2 = Requests.set_default_header req2 "Accept" "application/json" in 63 64 (* All requests with req2 will include these headers *) 65 let resp4 = Requests.get req2 "https://httpbin.org/headers" in 66 Printf.printf "Headers response status: %d\n%!" 67 (Requests.Response.status_code resp4); 68 69 (* Example 5: Query parameters *) 70 Printf.printf "\n=== Example 5: Query Parameters ===\n%!"; 71 let resp5 = 72 Requests.get req 73 ~params:[ ("key1", "value1"); ("key2", "value2") ] 74 "https://httpbin.org/get" 75 in 76 Printf.printf "Query params status: %d\n%!" 77 (Requests.Response.status_code resp5); 78 79 (* Example 6: Form data submission *) 80 Printf.printf "\n=== Example 6: Form Data ===\n%!"; 81 let form_body = 82 Requests.Body.form [ ("username", "demo"); ("password", "secret123") ] 83 in 84 let resp6 = Requests.post req ~body:form_body "https://httpbin.org/post" in 85 Printf.printf "Form POST status: %d\n%!" (Requests.Response.status_code resp6); 86 87 (* Example 7: Retry configuration *) 88 Printf.printf "\n=== Example 7: Retry Configuration ===\n%!"; 89 let retry_config = 90 Requests.Retry.config ~max_retries:3 ~backoff_factor:0.5 () 91 in 92 let req_with_retry = Requests.v ~sw ~retry:retry_config env in 93 let req_with_retry = 94 Requests.set_timeout req_with_retry (Requests.Timeout.v ~total:10.0 ()) 95 in 96 97 (* This will retry on 5xx errors *) 98 (try 99 let resp7 = Requests.get req_with_retry "https://httpbin.org/status/200" in 100 Printf.printf "Retry test status: %d\n%!" 101 (Requests.Response.status_code resp7) 102 with _ -> Printf.printf "Request failed even after retries\n%!"); 103 104 (* Example 8: Concurrent requests using Fiber.both *) 105 Printf.printf "\n=== Example 8: Concurrent Requests ===\n%!"; 106 let start_time = Unix.gettimeofday () in 107 108 let r1, r2 = 109 Fiber.both 110 (fun () -> Requests.get req "https://httpbin.org/delay/1") 111 (fun () -> Requests.get req "https://httpbin.org/delay/1") 112 in 113 114 let elapsed = Unix.gettimeofday () -. start_time in 115 Printf.printf "Two 1-second delays completed in %.2f seconds (concurrent)\n%!" 116 elapsed; 117 Printf.printf "Response 1 status: %d\n%!" (Requests.Response.status_code r1); 118 Printf.printf "Response 2 status: %d\n%!" (Requests.Response.status_code r2); 119 120 (* Example 9: One-shot stateless request *) 121 Printf.printf "\n=== Example 9: One-Shot Stateless Request ===\n%!"; 122 let resp9 = 123 Requests.One.get ~sw ~clock:env#clock ~net:env#net "https://httpbin.org/get" 124 in 125 Printf.printf "One-shot status: %d\n%!" (Requests.Response.status_code resp9); 126 127 (* Example 10: Error handling *) 128 Printf.printf "\n=== Example 10: Error Handling ===\n%!"; 129 (try 130 let resp = Requests.get req "https://httpbin.org/status/404" in 131 (* By default, 4xx/5xx status codes don't raise exceptions *) 132 if Requests.Response.ok resp then Printf.printf "Success\n%!" 133 else 134 Printf.printf "Got %d response (no exception by default)\n%!" 135 (Requests.Response.status_code resp); 136 (* Use raise_for_status to get exception behavior *) 137 let _resp = Requests.Response.raise_for_status resp in 138 () 139 with 140 | Eio.Io (Requests.Error.E (Requests.Error.Http_error_status _), _) -> 141 Printf.printf "HTTP error status raised via raise_for_status\n%!" 142 | exn -> Printf.printf "Other error: %s\n%!" (Printexc.to_string exn)); 143 144 (* Example 11: Timeouts *) 145 Printf.printf "\n=== Example 11: Timeouts ===\n%!"; 146 let req_timeout = Requests.v ~sw env in 147 let req_timeout = 148 Requests.set_timeout req_timeout (Requests.Timeout.v ~total:5.0 ()) 149 in 150 151 (try 152 let resp11 = Requests.get req_timeout "https://httpbin.org/delay/2" in 153 Printf.printf "Timeout test completed: %d\n%!" 154 (Requests.Response.status_code resp11) 155 with 156 | Eio.Time.Timeout -> Printf.printf "Request correctly timed out\n%!" 157 | exn -> Printf.printf "Other timeout error: %s\n%!" (Printexc.to_string exn)); 158 159 (* Example 12: Multiple concurrent requests with Fiber.all *) 160 Printf.printf "\n=== Example 12: Multiple Concurrent Requests ===\n%!"; 161 let urls = 162 [ 163 "https://httpbin.org/delay/1"; 164 "https://httpbin.org/get"; 165 "https://httpbin.org/headers"; 166 ] 167 in 168 169 let start_time = Unix.gettimeofday () in 170 let responses = ref [] in 171 172 Fiber.all 173 (List.map 174 (fun url -> 175 fun () -> 176 let resp = Requests.get req url in 177 responses := resp :: !responses) 178 urls); 179 180 let elapsed = Unix.gettimeofday () -. start_time in 181 Printf.printf "Three requests completed in %.2f seconds (concurrent)\n%!" 182 elapsed; 183 List.iter 184 (fun r -> 185 Printf.printf " Status: %d\n%!" (Requests.Response.status_code r)) 186 !responses; 187 188 Printf.printf "\n=== All examples completed successfully! ===\n%!"