Source file ocsigen_response.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
open Cohttp
open Lwt.Syntax
module Body = struct
type t = ((string -> unit Lwt.t) -> unit Lwt.t) * Transfer.encoding
let make encoding writer : t = writer, encoding
let empty = make (Fixed 0L) (fun _write -> Lwt.return_unit)
let of_string s =
make
(Transfer.Fixed (Int64.of_int (String.length s)))
(fun write -> write s)
let of_cohttp ~encoding body =
(fun write -> Cohttp_lwt.Body.write_body write body), encoding
let write (w, _) = w
let transfer_encoding = snd
end
type t =
{a_response : Response.t; a_body : Body.t; a_cookies : Ocsigen_cookie_map.t}
let (resp : Response.t) equals_to =
match Header.get resp.headers header with
| Some v when String.equal v equals_to ->
{resp with headers = Header.remove resp.headers header}
| _ -> resp
let make ?(body = Body.empty) ?(cookies = Ocsigen_cookie_map.empty) a_response =
let a_response =
remove_header_if_equal_to a_response "transfer-encoding" "chunked"
in
{a_response; a_body = body; a_cookies = cookies}
let respond ? ~status ?(body = Body.empty) () =
let response = Response.make ~status ?headers () in
make ~body response
let respond_string ? ~status ~body () =
let response = Response.make ~status ?headers () in
let body = Body.of_string body in
make ~body response
let respond_error ? ?(status = `Internal_server_error) ~body () =
respond_string ?headers ~status ~body:("Error: " ^ body) ()
let respond_not_found ?uri () =
let body =
match uri with
| None -> "Not found"
| Some uri -> "Not found: " ^ Uri.to_string uri
in
respond_string ~status:`Not_found ~body ()
let respond_file ? ?(status = `OK) fname =
let exception Isnt_a_file in
Lwt.catch
(fun () ->
let* () =
let* s = Lwt_unix.stat fname in
if Unix.(s.st_kind <> S_REG)
then raise Isnt_a_file
else Lwt.return_unit
in
let count = 16384 in
let* ic =
Lwt_io.open_file ~buffer:(Lwt_bytes.create count) ~mode:Lwt_io.input
fname
in
let* len = Lwt_io.length ic in
let encoding = Http.Transfer.Fixed len in
let stream write =
let rec cat_loop () =
Lwt.bind (Lwt_io.read ~count ic) (function
| "" -> Lwt.return_unit
| buf -> Lwt.bind (write buf) cat_loop)
in
let* () =
Lwt.catch cat_loop (fun exn ->
Logs.warn (fun m ->
m "Error resolving file %s (%s)" fname (Printexc.to_string exn));
Lwt.return_unit)
in
Lwt.catch
(fun () -> Lwt_io.close ic)
(fun e ->
Logs.warn (fun f ->
f "Closing channel failed: %s" (Printexc.to_string e));
Lwt.return_unit)
in
let body = Body.make encoding stream in
let mime_type = Magic_mime.lookup fname in
let =
Http.Header.add_opt_unless_exists headers "content-type" mime_type
in
Lwt.return (respond ~headers ~status ~body ()))
(function
| Unix.Unix_error (Unix.ENOENT, _, _) | Isnt_a_file ->
Lwt.return (respond_not_found ())
| exn -> Lwt.reraise exn)
let update ?response ?body ?cookies {a_response; a_body; a_cookies} =
let a_response =
match response with Some response -> response | None -> a_response
in
let a_body = match body with Some body -> body | None -> a_body
and a_cookies =
match cookies with Some cookies -> cookies | None -> a_cookies
in
{a_response; a_body; a_cookies}
let of_cohttp ?(cookies = Ocsigen_cookie_map.empty) (a_response, body) =
let encoding = Response.encoding a_response in
let a_body = Body.of_cohttp ~encoding body in
{a_response; a_body; a_cookies = cookies}
let path exp name c _secure =
Format.sprintf "%s=%s%s%s" name c
("; path=/" ^ Ocsigen_lib.Url.string_of_url_path ~encode:true path)
""
^
match exp with
| Some s -> "; expires=" ^ Ocsigen_lib.Date.to_string s
| None -> ""
let path t hds =
Ocsigen_cookie_map.Map_inner.fold
(fun name c h ->
let open Ocsigen_cookie_map in
let exp, v, secure =
match c with
| OUnset -> Some 0., "", false
| OSet (t, v, secure) -> t, v, secure
in
Cohttp.Header.add h
Ocsigen_header.Name.(to_string set_cookie)
(make_cookies_header path exp name v secure))
t hds
let to_cohttp_response {a_response; a_cookies; a_body = _, body_encoding} =
let =
let add name value = Header.add_unless_exists headers name value in
let add_transfer_encoding h =
Header.add_transfer_encoding h body_encoding
in
Response.headers a_response
|> Ocsigen_cookie_map.Map_path.fold make_cookies_headers a_cookies
|> add "server" Ocsigen_config.server_name
|> add "date" (Ocsigen_lib.Date.to_string (Unix.time ()))
|> add_transfer_encoding
in
{a_response with Response.headers}
let to_response_expert t =
let module R = Cohttp_lwt_unix.Response in
let encoding oc =
match encoding with
| Transfer.Chunked -> Lwt_io.write oc "0\r\n\r\n"
| Transfer.Fixed _ | Transfer.Unknown -> Lwt.return_unit
in
let res = to_cohttp_response t in
( res
, fun _ic oc ->
let writer = R.make_body_writer ~flush:false res oc in
let body, encoding = t.a_body in
let* () = body (R.write_body writer) in
write_footer encoding oc )
let response t = t.a_response
let body t = t.a_body
let status {a_response = {Cohttp.Response.status; _}; _} =
match status with
| `Code _ -> failwith "FIXME: Cohttp.Code.status_code -> status"
| #Cohttp.Code.status as a -> a
let set_status ({a_response; _} as a) status =
{ a with
a_response =
{a_response with Cohttp.Response.status :> Cohttp.Code.status_code} }
let cookies {a_cookies; _} = a_cookies
let add_cookies ({a_cookies; _} as a) cookies =
if cookies = Ocsigen_cookie_map.empty
then a
else {a with a_cookies = Ocsigen_cookie_map.add_multi a_cookies cookies}
let {a_response; _} id =
let h = Cohttp.Response.headers a_response in
Cohttp.Header.get h (Ocsigen_header.Name.to_string id)
let {a_response; _} id =
let h = Cohttp.Response.headers a_response in
Cohttp.Header.get_multi h (Ocsigen_header.Name.to_string id)
let
({a_response = {; _} as a_response; _} as a)
id
v
=
{ a with
a_response =
{ a_response with
Cohttp.Response.headers =
Cohttp.Header.add headers (Ocsigen_header.Name.to_string id) v } }
let
({a_response = {; _} as a_response; _} as a)
id
l
=
let id = Ocsigen_header.Name.to_string id in
let =
List.fold_left (fun -> Cohttp.Header.add headers id) headers l
in
{a with a_response = {a_response with Cohttp.Response.headers}}
let
({a_response = {; _} as a_response; _} as a)
id
v
=
{ a with
a_response =
{ a_response with
Cohttp.Response.headers =
Cohttp.Header.replace headers (Ocsigen_header.Name.to_string id) v }
}
let ({a_response; _} as a) l =
let =
List.fold_left
(fun (id, content) ->
Cohttp.Header.replace headers
(Ocsigen_header.Name.to_string id)
content)
(Cohttp.Response.headers a_response)
l
in
{a with a_response = {a_response with Cohttp.Response.headers}}
let ({a_response; _} as a) id =
let = Cohttp.Response.headers a_response
and id = Ocsigen_header.Name.to_string id in
let = Cohttp.Header.remove headers id in
{a with a_response = {a_response with Cohttp.Response.headers}}