Source file cohttp_curl.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
module Sink = struct
type _ t = String : string t | Discard : unit t
let string = String
let discard = Discard
end
module Error = struct
type t = Curl.curlCode
let create x = x
let is_timeout (t : t) =
match t with Curl.CURLE_OPERATION_TIMEOUTED -> true | _ -> false
let message t = Curl.strerror t
end
module Source = struct
type t = Empty | String of string
let empty = Empty
let string s = String s
let to_curl_callback t =
match t with
| Empty -> fun _ -> ""
| String s ->
let len = String.length s in
let pos = ref 0 in
fun max_asked ->
if !pos >= len then ""
else
let chunk_len = min (len - !pos) max_asked in
let res = String.sub s !pos chunk_len in
pos := !pos + chunk_len;
res
end
module Request = struct
type 'a t = {
curl : Curl.t;
body : 'a Sink.t;
mutable body_buffer : Buffer.t option;
}
let curl t = t.curl
let body (type a) (t : a t) : a =
match t.body with
| Discard ->
assert (t.body_buffer = None);
()
| String ->
let res =
Buffer.contents
(match t.body_buffer with None -> assert false | Some s -> s)
in
t.body_buffer <- None;
res
let create (type a) ?timeout_ms ? method_ ~uri ~(input : Source.t)
~(output : a Sink.t) ~on_response : a t =
let = ref [] in
let response_body = ref None in
let h = Curl.init () in
Curl.setopt h (CURLOPT_URL uri);
Curl.setopt h (CURLOPT_CUSTOMREQUEST (Http.Method.to_string method_));
let () =
match headers with
| None -> ()
| Some ->
let buf = Buffer.create 128 in
let =
Http.Header.fold
(fun key value acc ->
Buffer.clear buf;
Buffer.add_string buf key;
Buffer.add_string buf ": ";
Buffer.add_string buf value;
Buffer.contents buf :: acc)
headers []
|> List.rev
in
Curl.setopt h (CURLOPT_HTTPHEADER headers)
in
Curl.setopt h
(CURLOPT_HEADERFUNCTION
(let status_code_ready = ref false in
let response_http_version = ref None in
fun ->
(match !status_code_ready with
| false ->
(match String.split_on_char ' ' header with
| v :: _ ->
response_http_version := Some (Http.Version.of_string v)
| _ -> invalid_arg "invalid request");
status_code_ready := true
| true -> (
match header with
| "\r\n" ->
let response =
let =
Http.Header.of_list_rev !response_header_acc
in
response_header_acc := [];
let status =
match Curl.getinfo h CURLINFO_HTTP_CODE with
| CURLINFO_Long l -> Http.Status.of_int l
| _ -> assert false
in
let version =
match !response_http_version with
| None -> assert false
| Some v -> v
in
Http.Response.make ~version ~status ~headers ()
in
on_response response
| _ ->
let k, v =
match Stringext.cut header ~on:":" with
| None -> invalid_arg "proper abort needed"
| Some (k, v) -> (String.trim k, String.trim v)
in
response_header_acc := (k, v) :: !response_header_acc));
String.length header));
Curl.setopt h (CURLOPT_READFUNCTION (Source.to_curl_callback input));
Curl.setopt h
(CURLOPT_WRITEFUNCTION
(match output with
| Discard -> fun s -> String.length s
| String ->
let buf = Buffer.create 128 in
response_body := Some buf;
fun s ->
Buffer.add_string buf s;
String.length s));
(match timeout_ms with
| None -> ()
| Some tms -> Curl.setopt h (CURLOPT_TIMEOUTMS tms));
{ curl = h; body = output; body_buffer = !response_body }
end
module Private = struct
module Error = Error
module Sink = Sink
module Source = Source
module Request = Request
end