this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

more

+643 -170
+1 -2
stack/requests/bin/ocurl.ml
··· 216 216 else 217 217 print_string body_str; 218 218 219 - (* Close response to free resources *) 220 - Requests.Response.close response; 219 + (* Response auto-closes with switch *) 221 220 222 221 if not quiet && Requests.Response.ok response then 223 222 Logs.app (fun m -> m "✓ Success")
+118 -19
stack/requests/lib/body.ml
··· 83 83 (* Complex to calculate, handled during sending *) 84 84 None 85 85 86 - let to_cohttp_body = function 86 + (* Strings_source - A flow source that streams from a doubly-linked list of strings/flows *) 87 + module Strings_source = struct 88 + type element = 89 + | String of string 90 + | Flow of Eio.Flow.source_ty Eio.Resource.t 91 + 92 + type t = { 93 + dllist : element Lwt_dllist.t; 94 + mutable current_element : element option; 95 + mutable string_offset : int; 96 + } 97 + 98 + let rec single_read t dst = 99 + match t.current_element with 100 + | None -> 101 + (* Try to get the first element from the list *) 102 + if Lwt_dllist.is_empty t.dllist then 103 + raise End_of_file 104 + else begin 105 + t.current_element <- Some (Lwt_dllist.take_l t.dllist); 106 + single_read t dst 107 + end 108 + | Some (String s) when t.string_offset >= String.length s -> 109 + (* Current string exhausted, move to next element *) 110 + t.current_element <- None; 111 + t.string_offset <- 0; 112 + single_read t dst 113 + | Some (String s) -> 114 + (* Read from current string *) 115 + let available = String.length s - t.string_offset in 116 + let to_read = min (Cstruct.length dst) available in 117 + Cstruct.blit_from_string s t.string_offset dst 0 to_read; 118 + t.string_offset <- t.string_offset + to_read; 119 + to_read 120 + | Some (Flow flow) -> 121 + (* Read from flow *) 122 + (try 123 + let n = Eio.Flow.single_read flow dst in 124 + if n = 0 then begin 125 + (* Flow exhausted, move to next element *) 126 + t.current_element <- None; 127 + single_read t dst 128 + end else n 129 + with End_of_file -> 130 + t.current_element <- None; 131 + single_read t dst) 132 + 133 + let read_methods = [] (* No special read methods *) 134 + 135 + let create () = { 136 + dllist = Lwt_dllist.create (); 137 + current_element = None; 138 + string_offset = 0; 139 + } 140 + 141 + let add_string t s = 142 + ignore (Lwt_dllist.add_r (String s) t.dllist) 143 + 144 + let add_flow t flow = 145 + ignore (Lwt_dllist.add_r (Flow flow) t.dllist) 146 + end 147 + 148 + let strings_source_create () = 149 + let t = Strings_source.create () in 150 + let ops = Eio.Flow.Pi.source (module Strings_source) in 151 + (t, Eio.Resource.T (t, ops)) 152 + 153 + let to_cohttp_body ~sw = function 87 154 | Empty -> None 88 155 | String { content; _ } -> Some (Cohttp_eio.Body.of_string content) 89 156 | Stream { source; _ } -> Some source 90 157 | File { file; _ } -> 91 - (* Read file content *) 92 - let content = Eio.Path.load file in 93 - Some (Cohttp_eio.Body.of_string content) 158 + (* Open file and stream it directly without loading into memory *) 159 + let flow = Eio.Path.open_in ~sw file in 160 + Some (flow :> Eio.Flow.source_ty Eio.Resource.t) 94 161 | Multipart { parts; boundary } -> 95 - (* Create multipart body *) 96 - let buffer = Buffer.create 1024 in 162 + (* Create a single strings_source with dllist for streaming *) 163 + let source, flow = strings_source_create () in 164 + 97 165 List.iter (fun part -> 98 - Buffer.add_string buffer (Printf.sprintf "--%s\r\n" boundary); 99 - Buffer.add_string buffer (Printf.sprintf "Content-Disposition: form-data; name=\"%s\"" part.name); 166 + (* Add boundary *) 167 + Strings_source.add_string source "--"; 168 + Strings_source.add_string source boundary; 169 + Strings_source.add_string source "\r\n"; 170 + 171 + (* Add Content-Disposition header *) 172 + Strings_source.add_string source "Content-Disposition: form-data; name=\""; 173 + Strings_source.add_string source part.name; 174 + Strings_source.add_string source "\""; 100 175 (match part.filename with 101 - | Some f -> Buffer.add_string buffer (Printf.sprintf "; filename=\"%s\"" f) 176 + | Some f -> 177 + Strings_source.add_string source "; filename=\""; 178 + Strings_source.add_string source f; 179 + Strings_source.add_string source "\"" 102 180 | None -> ()); 103 - Buffer.add_string buffer "\r\n"; 104 - Buffer.add_string buffer (Printf.sprintf "Content-Type: %s\r\n\r\n" (Mime.to_string part.content_type)); 181 + Strings_source.add_string source "\r\n"; 182 + 183 + (* Add Content-Type header *) 184 + Strings_source.add_string source "Content-Type: "; 185 + Strings_source.add_string source (Mime.to_string part.content_type); 186 + Strings_source.add_string source "\r\n\r\n"; 187 + 188 + (* Add content *) 105 189 (match part.content with 106 - | `String s -> Buffer.add_string buffer s 190 + | `String s -> 191 + Strings_source.add_string source s 107 192 | `File file -> 108 - (* Read file content for multipart *) 109 - let content = Eio.Path.load file in 110 - Buffer.add_string buffer content 111 - | `Stream _ -> ()); (* TODO: read stream *) 112 - Buffer.add_string buffer "\r\n" 193 + (* Open file and add as flow *) 194 + let file_flow = Eio.Path.open_in ~sw file in 195 + Strings_source.add_flow source (file_flow :> Eio.Flow.source_ty Eio.Resource.t) 196 + | `Stream stream -> 197 + (* Add stream directly *) 198 + Strings_source.add_flow source stream); 199 + 200 + (* Add trailing newline *) 201 + Strings_source.add_string source "\r\n" 113 202 ) parts; 114 - Buffer.add_string buffer (Printf.sprintf "--%s--\r\n" boundary); 115 - Some (Cohttp_eio.Body.of_string (Buffer.contents buffer)) 203 + 204 + (* Add final boundary *) 205 + Strings_source.add_string source "--"; 206 + Strings_source.add_string source boundary; 207 + Strings_source.add_string source "--\r\n"; 208 + 209 + Some flow 210 + 211 + (* Private module *) 212 + module Private = struct 213 + let to_cohttp_body = to_cohttp_body 214 + end
+88 -22
stack/requests/lib/body.mli
··· 1 - (** Request body construction *) 1 + (** HTTP request body construction 2 + 3 + This module provides various ways to construct HTTP request bodies, 4 + including strings, files, streams, forms, and multipart data. 5 + 6 + {2 Examples} 7 + 8 + {[ 9 + (* Simple text body *) 10 + let body = Body.text "Hello, World!" 11 + 12 + (* JSON body *) 13 + let body = Body.json {|{"name": "Alice", "age": 30}|} 14 + 15 + (* Form data *) 16 + let body = Body.form [ 17 + ("username", "alice"); 18 + ("password", "secret") 19 + ] 20 + 21 + (* File upload *) 22 + let body = Body.of_file ~mime:Mime.pdf (Eio.Path.(fs / "document.pdf")) 23 + 24 + (* Multipart form with file *) 25 + let body = Body.multipart [ 26 + { name = "field"; filename = None; 27 + content_type = Mime.text_plain; 28 + content = `String "value" }; 29 + { name = "file"; filename = Some "photo.jpg"; 30 + content_type = Mime.jpeg; 31 + content = `File (Eio.Path.(fs / "photo.jpg")) } 32 + ] 33 + ]} 34 + *) 2 35 3 36 type t 4 - (** Abstract body type *) 37 + (** Abstract body type representing HTTP request body content. *) 38 + 39 + (** {1 Basic Constructors} *) 5 40 6 41 val empty : t 7 - (** Empty body *) 42 + (** [empty] creates an empty body (no content). *) 8 43 9 44 val of_string : Mime.t -> string -> t 10 - (** Create body from string with MIME type *) 45 + (** [of_string mime content] creates a body from a string with the specified MIME type. 46 + Example: [of_string Mime.json {|{"key": "value"}|}] *) 11 47 12 48 val of_stream : ?length:int64 -> Mime.t -> Eio.Flow.source_ty Eio.Resource.t -> t 13 - (** Create body from stream with optional content length *) 49 + (** [of_stream ?length mime stream] creates a streaming body. If [length] is provided, 50 + it will be used for the Content-Length header, otherwise chunked encoding is used. *) 14 51 15 52 val of_file : ?mime:Mime.t -> _ Eio.Path.t -> t 16 - (** Create body from file capability *) 53 + (** [of_file ?mime path] creates a body from a file. The MIME type is inferred from 54 + the file extension if not provided. *) 17 55 18 - (** Convenience constructors *) 56 + (** {1 Convenience Constructors} *) 19 57 20 58 val json : string -> t 21 - (** Create JSON body from JSON string *) 59 + (** [json str] creates a JSON body with Content-Type: application/json. *) 22 60 23 61 val text : string -> t 24 - (** Create plain text body *) 62 + (** [text str] creates a plain text body with Content-Type: text/plain. *) 25 63 26 64 val form : (string * string) list -> t 27 - (** Create URL-encoded form body *) 65 + (** [form fields] creates a URL-encoded form body with Content-Type: application/x-www-form-urlencoded. 66 + Example: [form [("username", "alice"); ("password", "secret")]] *) 28 67 29 - (** Multipart support *) 68 + (** {1 Multipart Support} *) 30 69 31 70 type 'a part = { 32 - name : string; 33 - filename : string option; 34 - content_type : Mime.t; 35 - content : [`String of string | `Stream of Eio.Flow.source_ty Eio.Resource.t | `File of 'a Eio.Path.t]; 71 + name : string; (** Form field name *) 72 + filename : string option; (** Optional filename for file uploads *) 73 + content_type : Mime.t; (** MIME type of this part *) 74 + content : [ 75 + | `String of string (** String content *) 76 + | `Stream of Eio.Flow.source_ty Eio.Resource.t (** Streaming content *) 77 + | `File of 'a Eio.Path.t (** File content *) 78 + ]; 36 79 } 80 + (** A single part in a multipart body. *) 37 81 38 82 val multipart : _ part list -> t 39 - (** Create multipart body *) 83 + (** [multipart parts] creates a multipart/form-data body from a list of parts. 84 + This is commonly used for file uploads and complex form submissions. 85 + 86 + Example: 87 + {[ 88 + let body = Body.multipart [ 89 + { name = "username"; filename = None; 90 + content_type = Mime.text_plain; 91 + content = `String "alice" }; 92 + { name = "avatar"; filename = Some "photo.jpg"; 93 + content_type = Mime.jpeg; 94 + content = `File (Eio.Path.(fs / "photo.jpg")) } 95 + ] 96 + ]} 97 + *) 40 98 41 - (** Properties *) 99 + (** {1 Properties} *) 42 100 43 101 val content_type : t -> Mime.t option 44 - (** Get content type *) 102 + (** [content_type body] returns the MIME type of the body, if set. *) 45 103 46 104 val content_length : t -> int64 option 47 - (** Get content length if known *) 105 + (** [content_length body] returns the content length in bytes, if known. 106 + Returns [None] for streaming bodies without a predetermined length. *) 107 + 108 + (** {1 Private API} *) 48 109 49 - (** Internal conversion for cohttp-eio integration *) 50 - val to_cohttp_body : t -> Cohttp_eio.Body.t option 51 - (** Convert body to cohttp-eio body format *) 110 + (** Internal functions exposed for use by other modules in the library. 111 + These are not part of the public API and may change between versions. *) 112 + module Private : sig 113 + val to_cohttp_body : sw:Eio.Switch.t -> t -> Cohttp_eio.Body.t option 114 + (** [to_cohttp_body ~sw body] converts the body to cohttp-eio format. 115 + Uses the switch to manage resources like file handles. 116 + This function is used internally by the Client module. *) 117 + end
+6 -4
stack/requests/lib/client.ml
··· 129 129 130 130 let cohttp_headers = headers_to_cohttp headers in 131 131 let cohttp_body = match body with 132 - | Some b -> Body.to_cohttp_body b 132 + | Some b -> Body.Private.to_cohttp_body ~sw b 133 133 | None -> None 134 134 in 135 135 ··· 226 226 let elapsed = Unix.gettimeofday () -. start_time in 227 227 Log.info (fun m -> m "Request completed in %.3f seconds" elapsed); 228 228 229 - Response.make 229 + Response.Private.make 230 + ~sw 230 231 ~status 231 232 ~headers:final_headers 232 233 ~body:final_body ··· 294 295 Eio.Flow.copy body sink; 295 296 progress_fn ~received:(Option.value total ~default:0L) ~total; 296 297 297 - Response.close response 298 + (* Response auto-closes with switch *) 299 + () 298 300 with e -> 299 - Response.close response; 301 + (* Response auto-closes with switch *) 300 302 raise e
+71 -6
stack/requests/lib/client.mli
··· 1 - (** Global client configuration *) 1 + (** Low-level HTTP client with streaming support 2 + 3 + The Client module provides a stateless HTTP client with connection pooling, 4 + TLS support, and streaming capabilities. For stateful requests with automatic 5 + cookie handling and persistent configuration, use the {!Session} module instead. 6 + 7 + {2 Examples} 8 + 9 + {[ 10 + open Eio_main 11 + 12 + let () = run @@ fun env -> 13 + Switch.run @@ fun sw -> 14 + 15 + (* Create a client *) 16 + let client = Client.create ~clock:env#clock ~net:env#net () in 17 + 18 + (* Simple GET request *) 19 + let response = Client.get ~sw ~client "https://example.com" in 20 + Printf.printf "Status: %d\n" (Response.status_code response); 21 + Response.close response; 22 + 23 + (* POST with JSON body *) 24 + let response = Client.post ~sw ~client 25 + ~body:(Body.json {|{"key": "value"}|}) 26 + ~headers:(Headers.empty |> Headers.content_type Mime.json) 27 + "https://api.example.com/data" in 28 + Response.close response; 29 + 30 + (* Download file with streaming *) 31 + Client.download ~sw ~client 32 + "https://example.com/large-file.zip" 33 + ~sink:(Eio.Path.(fs / "download.zip" |> sink)) 34 + ]} 35 + *) 2 36 3 37 type ('a,'b) t 4 - (** Client configuration *) 38 + (** Client configuration with clock and network types. 39 + The type parameters track the Eio environment capabilities. *) 40 + 41 + (** {1 Client Creation} *) 5 42 6 43 val create : 7 44 ?default_headers:Headers.t -> ··· 13 50 clock:'a Eio.Time.clock -> 14 51 net:'b Eio.Net.t -> 15 52 unit -> ('a Eio.Time.clock, 'b Eio.Net.t) t 16 - (** Create a client with custom configuration *) 53 + (** [create ?default_headers ?timeout ?max_retries ?retry_backoff ?verify_tls ?tls_config ~clock ~net ()] 54 + creates a new HTTP client with the specified configuration. 55 + 56 + @param default_headers Headers to include in every request (default: empty) 57 + @param timeout Default timeout configuration (default: 30s connect, 60s read) 58 + @param max_retries Maximum number of retries for failed requests (default: 3) 59 + @param retry_backoff Exponential backoff factor for retries (default: 2.0) 60 + @param verify_tls Whether to verify TLS certificates (default: true) 61 + @param tls_config Custom TLS configuration (default: uses system CA certificates) 62 + @param clock Eio clock for timeouts and scheduling 63 + @param net Eio network capability for making connections 64 + *) 17 65 18 66 val default : clock:'a Eio.Time.clock -> net:'b Eio.Net.t -> ('a Eio.Time.clock, 'b Eio.Net.t) t 19 - (** Create a client with default configuration *) 67 + (** [default ~clock ~net] creates a client with default configuration. 68 + Equivalent to [create ~clock ~net ()]. *) 20 69 21 - (** Internal accessors *) 70 + (** {1 Configuration Access} *) 71 + 22 72 val clock : ('a,'b) t -> 'a 73 + (** [clock client] returns the clock capability. *) 74 + 23 75 val net : ('a,'b) t -> 'b 76 + (** [net client] returns the network capability. *) 77 + 24 78 val default_headers : ('a,'b) t -> Headers.t 79 + (** [default_headers client] returns the default headers. *) 80 + 25 81 val timeout : ('a,'b) t -> Timeout.t 82 + (** [timeout client] returns the timeout configuration. *) 83 + 26 84 val max_retries : ('a,'b) t -> int 85 + (** [max_retries client] returns the maximum retry count. *) 86 + 27 87 val retry_backoff : ('a,'b) t -> float 88 + (** [retry_backoff client] returns the retry backoff factor. *) 89 + 28 90 val verify_tls : ('a,'b) t -> bool 91 + (** [verify_tls client] returns whether TLS verification is enabled. *) 92 + 29 93 val tls_config : ('a,'b) t -> Tls.Config.client option 94 + (** [tls_config client] returns the TLS configuration if set. *) 30 95 31 - (** {2 HTTP Request Methods} *) 96 + (** {1 HTTP Request Methods} *) 32 97 33 98 val request : 34 99 sw:Eio.Switch.t ->
+69 -14
stack/requests/lib/headers.mli
··· 1 - (** HTTP headers management with case-insensitive keys *) 1 + (** HTTP headers management with case-insensitive keys 2 + 3 + This module provides an efficient implementation of HTTP headers with 4 + case-insensitive header names as per RFC 7230. Headers can have multiple 5 + values for the same key (e.g., multiple Set-Cookie headers). 6 + 7 + {2 Examples} 8 + 9 + {[ 10 + let headers = 11 + Headers.empty 12 + |> Headers.content_type Mime.json 13 + |> Headers.bearer "token123" 14 + |> Headers.set "X-Custom" "value" 15 + ]} 16 + *) 2 17 3 18 type t 4 - (** Abstract header collection type *) 19 + (** Abstract header collection type. Headers are stored with case-insensitive 20 + keys and maintain insertion order. *) 21 + 22 + (** {1 Creation and Conversion} *) 5 23 6 24 val empty : t 7 - (** Empty header collection *) 25 + (** [empty] creates an empty header collection. *) 8 26 9 27 val of_list : (string * string) list -> t 10 - (** Create headers from association list *) 28 + (** [of_list pairs] creates headers from an association list. 29 + Later entries override earlier ones for the same key. *) 11 30 12 31 val to_list : t -> (string * string) list 13 - (** Convert to association list *) 32 + (** [to_list headers] converts headers to an association list. 33 + The order of headers is preserved. *) 34 + 35 + (** {1 Manipulation} *) 14 36 15 37 val add : string -> string -> t -> t 16 - (** Add a header (allows multiple values for same key) *) 38 + (** [add name value headers] adds a header value. Multiple values 39 + for the same header name are allowed (e.g., for Set-Cookie). *) 17 40 18 41 val set : string -> string -> t -> t 19 - (** Set a header (replaces existing values) *) 42 + (** [set name value headers] sets a header value, replacing any 43 + existing values for that header name. *) 20 44 21 45 val get : string -> t -> string option 22 - (** Get first value for a header *) 46 + (** [get name headers] returns the first value for a header name, 47 + or [None] if the header doesn't exist. *) 23 48 24 49 val get_all : string -> t -> string list 25 - (** Get all values for a header *) 50 + (** [get_all name headers] returns all values for a header name. 51 + Returns an empty list if the header doesn't exist. *) 26 52 27 53 val remove : string -> t -> t 28 - (** Remove all values for a header *) 54 + (** [remove name headers] removes all values for a header name. *) 29 55 30 56 val mem : string -> t -> bool 31 - (** Check if header exists *) 57 + (** [mem name headers] checks if a header name exists. *) 32 58 33 59 val merge : t -> t -> t 34 - (** Merge two header collections (right overrides left) *) 60 + (** [merge base override] merges two header collections. 61 + Headers from [override] replace those in [base]. *) 62 + 63 + (** {1 Common Header Builders} 35 64 36 - (** Common header builders *) 65 + Convenience functions for setting common HTTP headers. 66 + *) 37 67 38 68 val content_type : Mime.t -> t -> t 69 + (** [content_type mime headers] sets the Content-Type header. *) 70 + 39 71 val content_length : int64 -> t -> t 72 + (** [content_length length headers] sets the Content-Length header. *) 73 + 40 74 val accept : Mime.t -> t -> t 75 + (** [accept mime headers] sets the Accept header. *) 76 + 41 77 val authorization : string -> t -> t 78 + (** [authorization value headers] sets the Authorization header with a raw value. *) 79 + 42 80 val bearer : string -> t -> t 81 + (** [bearer token headers] sets the Authorization header with a Bearer token. 82 + Example: [bearer "abc123"] sets ["Authorization: Bearer abc123"] *) 83 + 43 84 val basic : username:string -> password:string -> t -> t 85 + (** [basic ~username ~password headers] sets the Authorization header with 86 + HTTP Basic authentication (base64-encoded username:password). *) 87 + 44 88 val user_agent : string -> t -> t 89 + (** [user_agent ua headers] sets the User-Agent header. *) 90 + 45 91 val host : string -> t -> t 92 + (** [host hostname headers] sets the Host header. *) 93 + 46 94 val cookie : string -> string -> t -> t 95 + (** [cookie name value headers] adds a cookie to the Cookie header. 96 + Multiple cookies can be added by calling this function multiple times. *) 97 + 47 98 val range : start:int64 -> ?end_:int64 -> unit -> t -> t 99 + (** [range ~start ?end_ () headers] sets the Range header for partial content. 100 + Example: [range ~start:0L ~end_:999L ()] requests the first 1000 bytes. *) 101 + 102 + (** {1 Aliases} *) 48 103 49 - (** Get multiple values for a header (alias for get_all) *) 50 104 val get_multi : string -> t -> string list 105 + (** [get_multi] is an alias for {!get_all}. *) 51 106 52 107 (** Pretty printer for headers *) 53 108 val pp : Format.formatter -> t -> unit
+178 -13
stack/requests/lib/requests.mli
··· 1 - (** OCaml HTTP client library with streaming support *) 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. Session-based requests} (Recommended for most use cases) 13 + 14 + Sessions maintain state across requests, handle cookies automatically, 15 + and provide a simple interface for common tasks: 16 + 17 + {[ 18 + open Eio_main 19 + 20 + let () = run @@ fun env -> 21 + Switch.run @@ fun sw -> 22 + 23 + (* Create a session *) 24 + let session = Requests.Session.create ~sw env in 25 + 26 + (* Configure authentication once *) 27 + Requests.Session.set_auth session (Requests.Auth.bearer "your-token"); 28 + 29 + (* Make requests - cookies and auth are handled automatically *) 30 + let user = Requests.Session.get session "https://api.github.com/user" in 31 + let repos = Requests.Session.get session "https://api.github.com/user/repos" in 32 + 33 + (* Session automatically manages cookies *) 34 + let _ = Requests.Session.post session "https://example.com/login" 35 + ~body:(Requests.Body.form ["username", "alice"; "password", "secret"]) in 36 + let dashboard = Requests.Session.get session "https://example.com/dashboard" 37 + 38 + (* No cleanup needed - responses auto-close with the switch *) 39 + ]} 40 + 41 + {b 2. Client-based requests} (For fine-grained control) 42 + 43 + The Client module provides lower-level control when you don't need 44 + session state or want to manage connections manually: 45 + 46 + {[ 47 + (* Create a client *) 48 + let client = Requests.Client.create ~clock:env#clock ~net:env#net () in 49 + 50 + (* Make a simple GET request *) 51 + let response = Requests.Client.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.Client.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 Sessions}: Maintain state (cookies, auth, headers) across requests 69 + - {b Authentication}: Built-in support for Basic, Bearer, Digest, and OAuth 70 + - {b Streaming}: Upload and download large files efficiently 71 + - {b Retries}: Automatic retry with exponential backoff 72 + - {b Timeouts}: Configurable connection and read timeouts 73 + - {b Cookie Management}: Automatic cookie handling with persistence 74 + - {b TLS/SSL}: Secure connections with certificate verification 75 + - {b Error Handling}: Comprehensive error types and recovery 76 + 77 + {2 Common Use Cases} 78 + 79 + {b Working with JSON APIs:} 80 + {[ 81 + let response = Requests.Session.post session "https://api.example.com/data" 82 + ~body:(Requests.Body.json {|{"key": "value"}|}) in 83 + let body_text = 84 + Requests.Response.body response 85 + |> Eio.Flow.read_all in 86 + print_endline body_text 87 + (* Response auto-closes with switch *) 88 + ]} 89 + 90 + {b File uploads:} 91 + {[ 92 + let body = Requests.Body.multipart [ 93 + { name = "file"; filename = Some "document.pdf"; 94 + content_type = Requests.Mime.pdf; 95 + content = `File (Eio.Path.(fs / "document.pdf")) }; 96 + { name = "description"; filename = None; 97 + content_type = Requests.Mime.text_plain; 98 + content = `String "Important document" } 99 + ] in 100 + let response = Requests.Session.post session "https://example.com/upload" 101 + ~body 102 + (* Response auto-closes with switch *) 103 + ]} 104 + 105 + {b Streaming downloads:} 106 + {[ 107 + Requests.Client.download ~sw ~client 108 + "https://example.com/large-file.zip" 109 + ~sink:(Eio.Path.(fs / "download.zip" |> sink)) 110 + ]} 111 + 112 + {2 Choosing Between Session and Client} 113 + 114 + Use {b Session} when you need: 115 + - Cookie persistence across requests 116 + - Automatic retry handling 117 + - Shared authentication across requests 118 + - Request/response history tracking 119 + - Configuration persistence to disk 120 + 121 + Use {b Client} when you need: 122 + - One-off stateless requests 123 + - Fine-grained control over connections 124 + - Minimal overhead 125 + - Custom connection pooling 126 + - Direct streaming without cookies 127 + *) 128 + 129 + (** {1 High-Level Session API} 130 + 131 + Sessions provide stateful HTTP clients with automatic cookie management, 132 + persistent configuration, and convenient methods for common operations. 133 + *) 134 + 135 + (** Stateful HTTP sessions with cookies and configuration persistence *) 136 + module Session = Session 137 + 138 + (** Cookie storage and management *) 139 + module Cookie_jar = Cookie_jar 140 + 141 + (** Retry policies and backoff strategies *) 142 + module Retry = Retry 143 + 144 + (** {1 Low-Level Client API} 2 145 3 - (** {1 Core Types} *) 146 + The Client module provides direct control over HTTP requests without 147 + session state. Use this for stateless operations or when you need 148 + fine-grained control. 149 + *) 4 150 5 - module Status = Status 6 - module Method = Method 7 - module Mime = Mime 151 + (** Low-level HTTP client with connection pooling *) 152 + module Client = Client 153 + 154 + (** {1 Core Types} 155 + 156 + These modules define the fundamental types used throughout the library. 157 + *) 158 + 159 + (** HTTP response handling *) 160 + module Response = Response 161 + 162 + (** Request body construction and encoding *) 163 + module Body = Body 164 + 165 + (** HTTP headers manipulation *) 8 166 module Headers = Headers 167 + 168 + (** Authentication schemes (Basic, Bearer, OAuth, etc.) *) 9 169 module Auth = Auth 10 - module Timeout = Timeout 11 - module Body = Body 12 - module Response = Response 13 - module Client = Client 170 + 171 + (** Error types and exception handling *) 14 172 module Error = Error 15 173 174 + (** {1 Supporting Types} *) 175 + 176 + (** HTTP status codes and reason phrases *) 177 + module Status = Status 16 178 17 - (** {1 Session Interface} *) 179 + (** HTTP request methods (GET, POST, etc.) *) 180 + module Method = Method 181 + 182 + (** MIME types for content negotiation *) 183 + module Mime = Mime 18 184 19 - module Session = Session 20 - module Cookie_jar = Cookie_jar 21 - module Retry = Retry 185 + (** Timeout configuration for requests *) 186 + module Timeout = Timeout
+28 -14
stack/requests/lib/response.ml
··· 10 10 mutable closed : bool; 11 11 } 12 12 13 - let make ~status ~headers ~body ~url ~elapsed = 13 + let make ~sw ~status ~headers ~body ~url ~elapsed = 14 14 Log.debug (fun m -> m "Creating response: status=%d url=%s elapsed=%.3fs" status url elapsed); 15 - { status; headers; body; url; elapsed; closed = false } 15 + let response = { status; headers; body; url; elapsed; closed = false } in 16 + 17 + (* Register cleanup with switch *) 18 + Eio.Switch.on_release sw (fun () -> 19 + if not response.closed then begin 20 + Log.debug (fun m -> m "Auto-closing response for %s via switch" url); 21 + try 22 + (* Read and discard remaining data *) 23 + let rec drain () = 24 + let buf = Cstruct.create 8192 in 25 + match Eio.Flow.single_read body buf with 26 + | 0 -> () (* EOF *) 27 + | _ -> drain () 28 + in 29 + drain (); 30 + response.closed <- true 31 + with _ -> 32 + response.closed <- true 33 + end 34 + ); 35 + 36 + response 16 37 17 38 let status t = Status.of_int t.status 18 39 ··· 48 69 else 49 70 t.body 50 71 51 - let close t = 52 - if not t.closed then begin 53 - Log.debug (fun m -> m "Closing response for %s" t.url); 54 - (* Consume remaining body if any *) 55 - try 56 - (* Read and discard remaining data by copying to a buffer *) 57 - (* TODO make this a more efficient null sink *) 58 - let buf = Buffer.create 4096 in 59 - Eio.Flow.copy t.body (Eio.Flow.buffer_sink buf) 60 - with _ -> (); 61 - t.closed <- true 62 - end 63 72 64 73 (* Pretty printers *) 65 74 let pp ppf t = ··· 79 88 @[%a@]@]" 80 89 Status.pp_hum (Status.of_int t.status) t.url t.elapsed 81 90 Headers.pp t.headers 91 + 92 + (* Private module *) 93 + module Private = struct 94 + let make = make 95 + end
+83 -30
stack/requests/lib/response.mli
··· 1 - (** HTTP response handling *) 1 + (** HTTP response handling 2 + 3 + This module represents HTTP responses and provides functions to access 4 + status codes, headers, and response bodies. Responses support streaming 5 + to efficiently handle large payloads. 6 + 7 + {2 Examples} 8 + 9 + {[ 10 + (* Check response status *) 11 + if Response.ok response then 12 + Printf.printf "Success!\n" 13 + else 14 + Printf.printf "Error: %d\n" (Response.status_code response); 15 + 16 + (* Access headers *) 17 + match Response.content_type response with 18 + | Some mime -> Printf.printf "Type: %s\n" (Mime.to_string mime) 19 + | None -> () 20 + 21 + (* Stream response body *) 22 + let body = Response.body response in 23 + Eio.Flow.copy body (Eio.Flow.buffer_sink buffer) 24 + 25 + (* Response automatically closes when the switch is released *) 26 + ]} 27 + 28 + {b Note}: Responses are automatically closed when the switch they were 29 + created with is released. Manual cleanup is not necessary. 30 + *) 2 31 3 32 open Eio 4 33 5 34 type t 6 - (** Abstract response type *) 35 + (** Abstract response type representing an HTTP response. *) 7 36 8 - (** Status *) 37 + (** {1 Status Information} *) 9 38 10 39 val status : t -> Status.t 11 - (** Get HTTP status as Status.t *) 40 + (** [status response] returns the HTTP status as a {!Status.t} value. *) 12 41 13 42 val status_code : t -> int 14 - (** Get HTTP status code as integer *) 43 + (** [status_code response] returns the HTTP status code as an integer (e.g., 200, 404). *) 15 44 16 45 val ok : t -> bool 17 - (** Returns true if status is 200-299 (alias for Status.is_success) *) 46 + (** [ok response] returns [true] if the status code is in the 2xx success range. 47 + This is an alias for {!Status.is_success}. *) 18 48 19 - (** Headers *) 49 + (** {1 Header Access} *) 20 50 21 51 val headers : t -> Headers.t 22 - (** Get all response headers *) 52 + (** [headers response] returns all response headers. *) 23 53 24 54 val header : string -> t -> string option 25 - (** Get a specific header value *) 55 + (** [header name response] returns the value of a specific header, or [None] if not present. 56 + Header names are case-insensitive. *) 26 57 27 58 val content_type : t -> Mime.t option 28 - (** Get content type if present *) 59 + (** [content_type response] returns the parsed Content-Type header as a MIME type, 60 + or [None] if the header is not present or cannot be parsed. *) 29 61 30 62 val content_length : t -> int64 option 31 - (** Get content length if present *) 63 + (** [content_length response] returns the Content-Length in bytes, 64 + or [None] if not specified or chunked encoding is used. *) 32 65 33 66 val location : t -> string option 34 - (** Get Location header for redirects *) 67 + (** [location response] returns the Location header value, typically used in redirects. 68 + Returns [None] if the header is not present. *) 35 69 36 - (** Metadata *) 70 + (** {1 Response Metadata} *) 37 71 38 72 val url : t -> string 39 - (** Final URL after any redirects *) 73 + (** [url response] returns the final URL after following any redirects. 74 + This may differ from the originally requested URL. *) 40 75 41 76 val elapsed : t -> float 42 - (** Time taken for the request in seconds *) 77 + (** [elapsed response] returns the time taken for the request in seconds, 78 + including connection establishment, sending the request, and receiving headers. *) 43 79 44 - (** Body access - streaming *) 80 + (** {1 Response Body} *) 45 81 46 82 val body : t -> Flow.source_ty Resource.t 47 - (** Get response body as a flow for streaming *) 83 + (** [body response] returns the response body as an Eio flow for streaming. 84 + This allows efficient processing of large responses without loading them 85 + entirely into memory. 48 86 49 - val close : t -> unit 50 - (** Close the response and free resources *) 87 + Example: 88 + {[ 89 + let body = Response.body response in 90 + let buffer = Buffer.create 4096 in 91 + Eio.Flow.copy body (Eio.Flow.buffer_sink buffer); 92 + Buffer.contents buffer 93 + ]} 94 + *) 51 95 52 - (** Internal construction - not exposed in public API *) 53 96 54 - val make : 55 - status:int -> 56 - headers:Headers.t -> 57 - body:Flow.source_ty Resource.t -> 58 - url:string -> 59 - elapsed:float -> 60 - t 61 - 62 - (** Pretty printers *) 97 + (** {1 Pretty Printing} *) 63 98 64 99 val pp : Format.formatter -> t -> unit 65 100 (** Pretty print a response summary *) 66 101 67 102 val pp_detailed : Format.formatter -> t -> unit 68 - (** Pretty print a response with full headers *) 103 + (** Pretty print a response with full headers *) 104 + 105 + (** {1 Private API} *) 106 + 107 + (** Internal functions exposed for use by other modules in the library. 108 + These are not part of the public API and may change between versions. *) 109 + module Private : sig 110 + val make : 111 + sw:Eio.Switch.t -> 112 + status:int -> 113 + headers:Headers.t -> 114 + body:Flow.source_ty Resource.t -> 115 + url:string -> 116 + elapsed:float -> 117 + t 118 + (** [make ~sw ~status ~headers ~body ~url ~elapsed] constructs a response. 119 + The response will be automatically closed when the switch is released. 120 + This function is used internally by the Client module. *) 121 + end
+1 -24
stack/requests/lib/session.ml
··· 117 117 118 118 (* Register cleanup on switch *) 119 119 Eio.Switch.on_release sw (fun () -> 120 + Log.info (fun m -> m "Closing session after %d requests" session.requests_made); 120 121 if persist_cookies && Option.is_some xdg then begin 121 122 Log.info (fun m -> m "Saving cookies on session close"); 122 123 Cookie_jar.save ?xdg session.cookie_jar ··· 124 125 ); 125 126 126 127 session 127 - 128 - let close t = 129 - Log.info (fun m -> m "Closing session after %d requests" t.requests_made); 130 - if t.persist_cookies && Option.is_some t.xdg then 131 - Cookie_jar.save ?xdg:t.xdg t.cookie_jar 132 - 133 - let with_session ~sw ?client ?cookie_jar ?default_headers ?auth ?timeout 134 - ?follow_redirects ?max_redirects ?verify_tls ?retry ?persist_cookies 135 - ?xdg env f = 136 - let session = create ~sw ?client ?cookie_jar ?default_headers ?auth 137 - ?timeout ?follow_redirects ?max_redirects ?verify_tls ?retry 138 - ?persist_cookies ?xdg env in 139 - try 140 - let result = f session in 141 - close session; 142 - result 143 - with exn -> 144 - close session; 145 - raise exn 146 128 147 129 let save_cookies : ('a, 'b) t -> unit = fun t -> 148 130 if t.persist_cookies && Option.is_some t.xdg then ··· 361 343 max_redirects : int; 362 344 user_agent : string option; 363 345 } 364 - 365 - (* default_config requires Xdge.Cmd.t which can only come from cmdliner parsing. 366 - Users should use config_term to get a properly configured session. *) 367 - let default_config _app_name _xdg = 368 - failwith "Session.Cmd.default_config: Use config_term instead to get configuration from cmdliner" 369 346 370 347 let create config env sw = 371 348 let xdg, _xdg_cmd = config.xdg in
-22
stack/requests/lib/session.mli
··· 88 88 @param xdg XDG directory configuration (creates default "requests" if not provided) 89 89 *) 90 90 91 - val with_session : 92 - sw:Eio.Switch.t -> 93 - ?client:('clock Eio.Time.clock,'net Eio.Net.t) Client.t -> 94 - ?cookie_jar:Cookie_jar.t -> 95 - ?default_headers:Headers.t -> 96 - ?auth:Auth.t -> 97 - ?timeout:Timeout.t -> 98 - ?follow_redirects:bool -> 99 - ?max_redirects:int -> 100 - ?verify_tls:bool -> 101 - ?retry:Retry.config -> 102 - ?persist_cookies:bool -> 103 - ?xdg:Xdge.t -> 104 - < clock: 'clock Eio.Resource.t; net: 'net Eio.Resource.t; fs: Eio.Fs.dir_ty Eio.Path.t; .. > -> 105 - (('clock Eio.Resource.t, 'net Eio.Resource.t) t -> 'a) -> 106 - 'a 107 - (** Create a session and run a function with it, ensuring cleanup. 108 - The session is automatically closed when the function returns. *) 109 - 110 91 (** {1 Configuration Management} *) 111 92 112 93 val set_default_header : ('clock, 'net) t -> string -> string -> unit ··· 344 325 max_redirects : int; (** Maximum number of redirects *) 345 326 user_agent : string option; (** User-Agent header *) 346 327 } 347 - 348 - val default_config : string -> Xdge.t -> config 349 - (** [default_config app_name xdg] creates a default configuration *) 350 328 351 329 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 352 330 (** [create config env sw] creates a session from command-line configuration *)