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
open Sexplib0.Sexp_conv
type = {
encoding: Transfer.encoding;
: Header.t;
version: Code.version;
status: Code.status_code;
flush: bool;
} [@@deriving fields, sexp]
let make ?(version=`HTTP_1_1) ?(status=`OK) ?(flush=false) ?(encoding=Transfer.Chunked) ?(=Header.init ()) () =
let encoding =
match Header.get_transfer_encoding headers with
| Transfer.(Chunked | Fixed _) as enc -> enc
| Unknown -> encoding
in
{ encoding; headers; version; flush; status }
let pp_hum ppf r =
Format.fprintf ppf "%s" (r |> sexp_of_t |> Sexplib0.Sexp.to_string_hum)
type tt = t
module Make(IO : S.IO) = struct
type t = tt
module IO = IO
module Transfer_IO = Transfer_io.Make(IO)
type reader = Transfer_IO.reader
type writer = Transfer_IO.writer
open IO
let parse_response_fst_line ic =
let open Code in
read_line ic >>= function
| Some response_line -> begin
match Stringext.split response_line ~on:' ' with
| version_raw :: code_raw :: _ -> begin
match version_of_string version_raw with
| `HTTP_1_0 | `HTTP_1_1 as v -> return (`Ok (v, (status_of_code (int_of_string code_raw))))
| `Other _ -> return (`Invalid ("Malformed response version: " ^ version_raw))
end
| _ -> return (`Invalid ("Malformed response first line: " ^ response_line))
end
| None -> return `Eof
let read ic =
parse_response_fst_line ic >>= function
| `Eof -> return `Eof
| `Invalid _reason as r -> return r
| `Ok (version, status) ->
Header_IO.parse ic >>= fun ->
let encoding = Header.get_transfer_encoding headers in
let flush = false in
return (`Ok { encoding; headers; version; status; flush })
let allowed_body response =
match status response with
| #Code.informational_status | `No_content | `Not_modified -> false
| #Code.status_code -> true
let has_body response =
if allowed_body response
then Transfer.has_body (encoding response)
else `No
let make_body_reader {encoding; _} ic = Transfer_IO.make_reader encoding ic
let read_body_chunk = Transfer_IO.read
let res oc =
write oc (Printf.sprintf "%s %s\r\n" (Code.string_of_version res.version)
(Code.string_of_status res.status)) >>= fun () ->
let =
if allowed_body res
then Header.add_transfer_encoding res.headers res.encoding
else res.headers in
Header_IO.write headers oc
let make_body_writer ?flush {encoding; _} oc =
Transfer_IO.make_writer ?flush encoding oc
let write_body = Transfer_IO.write
let {encoding; _} oc =
match encoding with
|Transfer.Chunked ->
IO.write oc "0\r\n\r\n"
|Transfer.Fixed _ | Transfer.Unknown -> return ()
let write ?flush fn req oc =
write_header req oc >>= fun () ->
let writer = make_body_writer ?flush req oc in
fn writer >>= fun () ->
write_footer req oc
end