capricorn 2.0.8 → 2.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/erlang/lib/capricorn/ebin/capricorn.app +2 -1
  2. data/erlang/lib/capricorn/include/capricorn.hrl +1 -0
  3. data/erlang/lib/capricorn/src/cap_cluster_gems.erl +6 -1
  4. data/erlang/lib/capricorn/src/cap_console_dispatcher.erl +214 -0
  5. data/erlang/lib/capricorn/src/cap_machine_apps.erl +25 -1
  6. data/erlang/lib/capricorn/src/cap_sup.erl +14 -0
  7. data/erlang/lib/ejson/Makefile +24 -0
  8. data/erlang/lib/ejson/ebin/ejson.app +9 -0
  9. data/erlang/lib/ejson/include/ejson.hrl +40 -0
  10. data/erlang/lib/ejson/rebar.config +3 -0
  11. data/erlang/lib/ejson/src/ejson.erl +22 -0
  12. data/erlang/lib/ejson/src/ejson_decode.erl +337 -0
  13. data/erlang/lib/ejson/src/ejson_encode.erl +124 -0
  14. data/erlang/lib/ejson/test/arrays.escript +47 -0
  15. data/erlang/lib/ejson/test/compound.escript +56 -0
  16. data/erlang/lib/ejson/test/literals.escript +30 -0
  17. data/erlang/lib/ejson/test/numbers.escript +70 -0
  18. data/erlang/lib/ejson/test/objects.escript +51 -0
  19. data/erlang/lib/ejson/test/strings.escript +49 -0
  20. data/erlang/lib/ejson/test/timing.escript +43 -0
  21. data/erlang/lib/ejson/test/timing.json +382 -0
  22. data/erlang/lib/ejson/vendor/mochijson2.erl +621 -0
  23. data/erlang/lib/ejson/vendor/rfc4627.erl +625 -0
  24. data/erlang/lib/misultin/LICENSE.txt +41 -0
  25. data/erlang/lib/misultin/Makefile +26 -0
  26. data/erlang/lib/misultin/README.txt +120 -0
  27. data/erlang/lib/misultin/ebin/misultin.app +9 -0
  28. data/erlang/lib/misultin/examples/misultin_compress.erl +43 -0
  29. data/erlang/lib/misultin/examples/misultin_echo.erl +58 -0
  30. data/erlang/lib/misultin/examples/misultin_file.erl +43 -0
  31. data/erlang/lib/misultin/examples/misultin_gen_server.erl +158 -0
  32. data/erlang/lib/misultin/examples/misultin_get_variable.erl +55 -0
  33. data/erlang/lib/misultin/examples/misultin_hello_world.erl +43 -0
  34. data/erlang/lib/misultin/examples/misultin_rest.erl +68 -0
  35. data/erlang/lib/misultin/examples/misultin_ssl.erl +51 -0
  36. data/erlang/lib/misultin/examples/misultin_stream.erl +55 -0
  37. data/erlang/lib/misultin/examples/misultin_websocket_event_example.erl +103 -0
  38. data/erlang/lib/misultin/examples/misultin_websocket_example.erl +95 -0
  39. data/erlang/lib/misultin/include/misultin.hrl +95 -0
  40. data/erlang/lib/misultin/make.bat +55 -0
  41. data/erlang/lib/misultin/priv/README.txt +12 -0
  42. data/erlang/lib/misultin/priv/test_certificate.pem +21 -0
  43. data/erlang/lib/misultin/priv/test_privkey.pem +18 -0
  44. data/erlang/lib/misultin/rebar.config +3 -0
  45. data/erlang/lib/misultin/src/misultin.app.src +9 -0
  46. data/erlang/lib/misultin/src/misultin.erl +338 -0
  47. data/erlang/lib/misultin/src/misultin_http.erl +488 -0
  48. data/erlang/lib/misultin/src/misultin_req.erl +280 -0
  49. data/erlang/lib/misultin/src/misultin_socket.erl +193 -0
  50. data/erlang/lib/misultin/src/misultin_utility.erl +357 -0
  51. data/erlang/lib/misultin/src/misultin_websocket.erl +252 -0
  52. data/erlang/lib/misultin/src/misultin_ws.erl +78 -0
  53. data/erlang/rebar.config +2 -0
  54. data/erlang/rel/overlay/etc/capricorn/app.config +4 -0
  55. data/erlang/rel/reltool.config +5 -0
  56. data/lib/capricorn/recipes/apache-debian.rb +1 -1
  57. data/lib/capricorn/recipes/centos-plesk.rb +1 -1
  58. data/lib/capricorn/recipes/debian-plesk95.rb +1 -2
  59. data/lib/capricorn/recipes/macports.rb +1 -1
  60. data/lib/capricorn/version.rb +1 -1
  61. metadata +51 -4
@@ -0,0 +1,488 @@
1
+ % ==========================================================================================================
2
+ % MISULTIN - Http(s)
3
+ %
4
+ % >-|-|-(°>
5
+ %
6
+ % Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>, Sean Hinde.
7
+ % All rights reserved.
8
+ %
9
+ % Code portions from Sean Hinde have been originally taken under BSD license from Trapexit at the address:
10
+ % <http://www.trapexit.org/A_fast_web_server_demonstrating_some_undocumented_Erlang_features>
11
+ %
12
+ % BSD License
13
+ %
14
+ % Redistribution and use in source and binary forms, with or without modification, are permitted provided
15
+ % that the following conditions are met:
16
+ %
17
+ % * Redistributions of source code must retain the above copyright notice, this list of conditions and the
18
+ % following disclaimer.
19
+ % * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
20
+ % the following disclaimer in the documentation and/or other materials provided with the distribution.
21
+ % * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
22
+ % products derived from this software without specific prior written permission.
23
+ %
24
+ % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
25
+ % WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
26
+ % PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
27
+ % ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
28
+ % TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29
+ % HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30
+ % NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
+ % POSSIBILITY OF SUCH DAMAGE.
32
+ % ==========================================================================================================
33
+ -module(misultin_http).
34
+ -vsn("0.6.1").
35
+
36
+ % API
37
+ -export([handle_data/8]).
38
+
39
+ % internal
40
+ -export([socket_loop/2]).
41
+
42
+ % macros
43
+ -define(MAX_HEADERS_COUNT, 100).
44
+ -define(SUPPORTED_ENCODINGS, ["gzip", "deflate"]).
45
+
46
+ % records
47
+ -record(c, {
48
+ sock,
49
+ socket_mode,
50
+ port,
51
+ recv_timeout,
52
+ compress,
53
+ stream_support,
54
+ loop,
55
+ ws_loop,
56
+ ws_autoexit
57
+ }).
58
+
59
+ % includes
60
+ -include("../include/misultin.hrl").
61
+
62
+
63
+ % ============================ \/ API ======================================================================
64
+
65
+ % Callback from misultin_socket
66
+ handle_data(Sock, SocketMode, ListenPort, PeerAddr, PeerPort, PeerCert, RecvTimeout, CustomOpts) ->
67
+ % build connection & request records
68
+ C = #c{sock = Sock, socket_mode = SocketMode, port = ListenPort, recv_timeout = RecvTimeout, compress = CustomOpts#custom_opts.compress, stream_support = CustomOpts#custom_opts.stream_support, loop = CustomOpts#custom_opts.loop, ws_loop = CustomOpts#custom_opts.ws_loop, ws_autoexit = CustomOpts#custom_opts.ws_autoexit},
69
+ Req = #req{socket = Sock, socket_mode = SocketMode, peer_addr = PeerAddr, peer_port = PeerPort, peer_cert = PeerCert},
70
+ % enter loop
71
+ request(C, Req).
72
+
73
+ % ============================ /\ API ======================================================================
74
+
75
+
76
+ % ============================ \/ INTERNAL FUNCTIONS =======================================================
77
+
78
+ % REQUEST: wait for a HTTP Request line. Transition to state headers if one is received.
79
+ request(#c{sock = Sock, socket_mode = SocketMode, recv_timeout = RecvTimeout} = C, Req) ->
80
+ misultin_socket:setopts(Sock, [{active, once}, {packet, http}], SocketMode),
81
+ receive
82
+ {SocketMode, Sock, {http_request, Method, Path, Version}} ->
83
+ ?LOG_DEBUG("received full headers of a new HTTP packet", []),
84
+ % change packet type if in ssl mode
85
+ case SocketMode of
86
+ ssl -> misultin_socket:setopts(Sock, [{packet, httph}], SocketMode);
87
+ _ -> ok
88
+ end,
89
+ % go to headers
90
+ headers(C, Req#req{vsn = Version, method = Method, uri = Path, connection = default_connection(Version)}, []);
91
+ {SocketMode, Sock, {http_error, "\r\n"}} ->
92
+ request(C, Req);
93
+ {SocketMode, Sock, {http_error, "\n"}} ->
94
+ request(C, Req);
95
+ {http, Sock, {http_error, _Other}} ->
96
+ ?LOG_WARNING("received a http error, might be a ssl request while socket in http mode: ~p, sending forbidden response and closing socket", [_Other]),
97
+ misultin_socket:send(Sock, misultin_utility:get_http_status_code(403), SocketMode),
98
+ misultin_socket:close(Sock, SocketMode),
99
+ exit(normal);
100
+ _Other ->
101
+ ?LOG_WARNING("tcp error on incoming request: ~p, closing socket", [_Other]),
102
+ misultin_socket:close(Sock, SocketMode),
103
+ exit(normal)
104
+ after RecvTimeout ->
105
+ ?LOG_DEBUG("normal receive timeout, exit", []),
106
+ misultin_socket:close(Sock, SocketMode),
107
+ exit(normal)
108
+ end.
109
+
110
+ % HEADERS: collect HTTP headers. After the end of header marker transition to body state.
111
+ headers(C, Req, H) ->
112
+ headers(C, Req, H, 0).
113
+ headers(#c{sock = Sock, socket_mode = SocketMode}, _Req, _H, ?MAX_HEADERS_COUNT) ->
114
+ ?LOG_DEBUG("too many headers sent, bad request",[]),
115
+ misultin_socket:send(Sock, misultin_utility:get_http_status_code(400), SocketMode);
116
+ headers(#c{sock = Sock, socket_mode = SocketMode, recv_timeout = RecvTimeout, ws_loop = WsLoop} = C, Req, H, HeaderCount) ->
117
+ misultin_socket:setopts(Sock, [{active, once}], SocketMode),
118
+ receive
119
+ {SocketMode, Sock, {http_header, _, 'Content-Length', _, Val} = _Head} ->
120
+ ?LOG_DEBUG("received header: ~p", [_Head]),
121
+ headers(C, Req#req{content_length = Val}, [{'Content-Length', Val}|H], HeaderCount + 1);
122
+ {SocketMode, Sock, {http_header, _, 'Connection', _, Val} = _Head} ->
123
+ ?LOG_DEBUG("received header: ~p", [_Head]),
124
+ headers(C, Req#req{connection = keep_alive(Req#req.vsn, Val)}, [{'Connection', Val}|H], HeaderCount + 1);
125
+ {SocketMode, Sock, {http_header, _, Header, _, Val} = _Head} ->
126
+ ?LOG_DEBUG("received header: ~p", [_Head]),
127
+ headers(C, Req, [{Header, Val}|H], HeaderCount + 1);
128
+ {SocketMode, Sock, {http_error, "\r\n"} = _Head} ->
129
+ ?LOG_DEBUG("received header: ~p", [_Head]),
130
+ headers(C, Req, H, HeaderCount);
131
+ {SocketMode, Sock, {http_error, "\n"} = _Head} ->
132
+ ?LOG_DEBUG("received header: ~p", [_Head]),
133
+ headers(C, Req, H, HeaderCount);
134
+ {SocketMode, Sock, http_eoh} ->
135
+ ?LOG_DEBUG("received EOH header", []),
136
+ Headers = lists:reverse(H),
137
+ {_PathType, Path} = Req#req.uri,
138
+ % check if it's a websocket request
139
+ CheckWs = case WsLoop of
140
+ none -> false;
141
+ _Function -> misultin_websocket:check(Path, Headers)
142
+ end,
143
+ case CheckWs of
144
+ false ->
145
+ ?LOG_DEBUG("normal http request received", []),
146
+ body(C, Req#req{headers = Headers});
147
+ {true, Vsn} ->
148
+ ?LOG_DEBUG("websocket request received", []),
149
+ misultin_websocket:connect(Req, #ws{vsn = Vsn, socket = Sock, socket_mode = SocketMode, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port, path = Path, headers = Headers, ws_autoexit = C#c.ws_autoexit}, WsLoop)
150
+ end;
151
+ {SocketMode, Sock, _Other} ->
152
+ ?LOG_DEBUG("tcp error treating headers: ~p, send bad request error back", [_Other]),
153
+ misultin_socket:send(Sock, misultin_utility:get_http_status_code(400), SocketMode);
154
+ _Other ->
155
+ ?LOG_DEBUG("received unknown message: ~p, ignoring", [_Other]),
156
+ ignored
157
+ after RecvTimeout ->
158
+ ?LOG_DEBUG("headers timeout, sending request timeout error", []),
159
+ misultin_socket:send(Sock, misultin_utility:get_http_status_code(408), SocketMode)
160
+ end.
161
+
162
+ % default connection
163
+ default_connection({1,1}) -> keep_alive;
164
+ default_connection(_) -> close.
165
+
166
+ % Shall we keep the connection alive? Default case for HTTP/1.1 is yes, default for HTTP/1.0 is no.
167
+ keep_alive({1,1}, "close") -> close;
168
+ keep_alive({1,1}, "Close") -> close;
169
+ % string:to_upper is used only as last resort.
170
+ keep_alive({1,1}, Head) ->
171
+ case string:to_upper(Head) of
172
+ "CLOSE" -> close;
173
+ _ -> keep_alive
174
+ end;
175
+ keep_alive({1,0}, "Keep-Alive") -> keep_alive;
176
+ keep_alive({1,0}, Head) ->
177
+ case string:to_upper(Head) of
178
+ "KEEP-ALIVE" -> keep_alive;
179
+ _ -> close
180
+ end;
181
+ keep_alive({0,9}, _) -> close;
182
+ keep_alive(_Vsn, _KA) -> close.
183
+
184
+ % BODY: collect the body of the HTTP request if there is one, and lookup and call the implementation callback.
185
+ % Depending on whether the request is persistent transition back to state request to await the next request or exit.
186
+ body(#c{sock = Sock, socket_mode = SocketMode, recv_timeout = RecvTimeout} = C, Req) ->
187
+ case Req#req.method of
188
+ 'GET' ->
189
+ ?LOG_DEBUG("GET request received",[]),
190
+ Close = handle_get(C, Req),
191
+ case Close of
192
+ close ->
193
+ % close socket
194
+ misultin_socket:close(Sock, SocketMode);
195
+ keep_alive ->
196
+ request(C, #req{socket = Sock, socket_mode = SocketMode, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port, peer_cert = Req#req.peer_cert})
197
+ end;
198
+ 'POST' ->
199
+ ?LOG_DEBUG("POST request received", []),
200
+ case catch list_to_integer(Req#req.content_length) of
201
+ {'EXIT', _} ->
202
+ % TODO: provide a fallback when content length is not or wrongly specified
203
+ ?LOG_DEBUG("specified content length is not a valid integer number: ~p", [Req#req.content_length]),
204
+ misultin_socket:send(Sock, misultin_utility:get_http_status_code(411), SocketMode),
205
+ exit(normal);
206
+ 0 ->
207
+ ?LOG_DEBUG("zero content-lenght specified, skipping parsing body of request", []),
208
+ Close = handle_post(C, Req),
209
+ case Close of
210
+ close ->
211
+ % close socket
212
+ misultin_socket:close(Sock, SocketMode);
213
+ keep_alive ->
214
+ misultin_socket:setopts(Sock, [{packet, http}], SocketMode),
215
+ request(C, #req{socket = Sock, socket_mode = SocketMode, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port, peer_cert = Req#req.peer_cert})
216
+ end;
217
+ Len ->
218
+ ?LOG_DEBUG("parsing POST content in body of request", []),
219
+ misultin_socket:setopts(Sock, [{packet, raw}, {active, false}], SocketMode),
220
+ case misultin_socket:recv(Sock, Len, RecvTimeout, SocketMode) of
221
+ {ok, Bin} ->
222
+ Close = handle_post(C, Req#req{body = Bin}),
223
+ case Close of
224
+ close ->
225
+ % close socket
226
+ misultin_socket:close(Sock, SocketMode);
227
+ keep_alive ->
228
+ misultin_socket:setopts(Sock, [{packet, http}], SocketMode),
229
+ request(C, #req{socket = Sock, socket_mode = SocketMode, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port, peer_cert = Req#req.peer_cert})
230
+ end;
231
+ {error, timeout} ->
232
+ ?LOG_WARNING("request timeout, sending error", []),
233
+ misultin_socket:send(Sock, misultin_utility:get_http_status_code(408), SocketMode);
234
+ _Other ->
235
+ ?LOG_ERROR("tcp error treating post data: ~p, send bad request error back", [_Other]),
236
+ misultin_socket:send(Sock, misultin_utility:get_http_status_code(400), SocketMode)
237
+ end
238
+ end;
239
+ _Other ->
240
+ ?LOG_DEBUG("method not implemented: ~p", [_Other]),
241
+ misultin_socket:send(Sock, misultin_utility:get_http_status_code(501), SocketMode),
242
+ exit(normal)
243
+ end.
244
+
245
+ % handle a get request
246
+ handle_get(C, #req{socket_mode = SocketMode, connection = Conn} = Req) ->
247
+ case Req#req.uri of
248
+ {abs_path, Path} ->
249
+ {F, Args} = split_at_q_mark(Path, []),
250
+ call_mfa(C, Req#req{args = Args, uri = {abs_path, F}}),
251
+ Conn;
252
+ {absoluteURI, http, _Host, _, Path} ->
253
+ {F, Args} = split_at_q_mark(Path, []),
254
+ call_mfa(C, Req#req{args = Args, uri = {absoluteURI, F}}),
255
+ Conn;
256
+ {absoluteURI, _Other_method, _Host, _, _Path} ->
257
+ misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(501), SocketMode),
258
+ close;
259
+ {scheme, _Scheme, _RequestString} ->
260
+ misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(510), SocketMode),
261
+ close;
262
+ _ ->
263
+ misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(403), SocketMode),
264
+ close
265
+ end.
266
+
267
+ % handle a post request
268
+ handle_post(C, #req{socket_mode = SocketMode, connection = Conn} = Req) ->
269
+ case Req#req.uri of
270
+ {abs_path, _Path} ->
271
+ call_mfa(C, Req),
272
+ Conn;
273
+ {absoluteURI, http, _Host, _, _Path} ->
274
+ call_mfa(C, Req),
275
+ Conn;
276
+ {absoluteURI, _Other_method, _Host, _, _Path} ->
277
+ misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(501), SocketMode),
278
+ close;
279
+ {scheme, _Scheme, _RequestString} ->
280
+ misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(501), SocketMode),
281
+ close;
282
+ _ ->
283
+ misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(403), SocketMode),
284
+ close
285
+ end.
286
+
287
+ % Description: Main dispatcher
288
+ call_mfa(#c{sock = Sock, socket_mode = SocketMode, compress = Compress, stream_support = StreamSupport, loop = Loop} = C, #req{headers = RequestHeaders} = Request) ->
289
+ % spawn listening process for Request messages [only used to support stream requests]
290
+ case StreamSupport of
291
+ true ->
292
+ SocketPid = spawn(?MODULE, socket_loop, [C, Request]);
293
+ false ->
294
+ SocketPid = no_stream_support_proc
295
+ end,
296
+ % create request
297
+ Req = misultin_req:new(Request, SocketPid),
298
+ % call loop
299
+ case catch Loop(Req) of
300
+ {'EXIT', _Reason} ->
301
+ ?LOG_ERROR("worker crash: ~p", [_Reason]),
302
+ % kill listening socket
303
+ catch SocketPid ! shutdown,
304
+ % send response
305
+ misultin_socket:send(Sock, misultin_utility:get_http_status_code(500), SocketMode),
306
+ % force exit
307
+ exit(normal);
308
+ {HttpCode, Headers0, Body} ->
309
+ % received normal response
310
+ ?LOG_DEBUG("sending response", []),
311
+ % kill listening socket
312
+ catch SocketPid ! shutdown,
313
+ % build binary body & compress if necessary
314
+ {CompressHeaders, BodyBinary} = compress_body(RequestHeaders, convert_to_binary(Body), Compress),
315
+ % build headers
316
+ Headers1 = add_output_header('Content-Length', {Headers0, BodyBinary}),
317
+ Headers = add_output_header('Connection', {Headers1, Request}),
318
+ Enc_headers = enc_headers(lists:flatten([CompressHeaders|Headers])),
319
+ % build and send response
320
+ Resp = [misultin_utility:get_http_status_code(HttpCode), Enc_headers, <<"\r\n">>, BodyBinary],
321
+ misultin_socket:send(Sock, Resp, SocketMode);
322
+ {raw, Body} ->
323
+ misultin_socket:send(Sock, Body, SocketMode);
324
+ _ ->
325
+ % loop exited normally, kill listening socket
326
+ catch SocketPid ! shutdown
327
+ end.
328
+
329
+ % Description: Ensure Body is binary.
330
+ convert_to_binary(Body) when is_list(Body) ->
331
+ list_to_binary(lists:flatten(Body));
332
+ convert_to_binary(Body) when is_binary(Body) ->
333
+ Body;
334
+ convert_to_binary(Body) when is_atom(Body) ->
335
+ list_to_binary(atom_to_list(Body)).
336
+
337
+ % Description: Socket loop for stream responses
338
+ socket_loop(#c{sock = Sock, socket_mode = SocketMode} = C, Request) ->
339
+ receive
340
+ {stream_head, HttpCode, Headers0} ->
341
+ ?LOG_DEBUG("sending stream head", []),
342
+ Headers = add_output_header('Connection', {Headers0, Request}),
343
+ Enc_headers = enc_headers(Headers),
344
+ Resp = [misultin_utility:get_http_status_code(HttpCode), Enc_headers, <<"\r\n">>],
345
+ misultin_socket:send(Sock, Resp, SocketMode),
346
+ socket_loop(C, Request);
347
+ {stream_data, Body} ->
348
+ ?LOG_DEBUG("sending stream data", []),
349
+ misultin_socket:send(Sock, Body, SocketMode),
350
+ socket_loop(C, Request);
351
+ stream_close ->
352
+ ?LOG_DEBUG("closing stream", []),
353
+ misultin_socket:close(Sock, SocketMode);
354
+ shutdown ->
355
+ ?LOG_DEBUG("shutting down socket loop", []),
356
+ shutdown
357
+ end.
358
+
359
+ % Description: Add necessary Content-Length Header
360
+ add_output_header('Content-Length', {Headers, Body}) ->
361
+ case misultin_utility:get_key_value('Content-Length', Headers) of
362
+ undefined ->
363
+ [{'Content-Length', size(Body)}|Headers];
364
+ _ExistingContentLength ->
365
+ Headers
366
+ end;
367
+
368
+ % Description: Add necessary Connection Header
369
+ add_output_header('Connection', {Headers, Req}) ->
370
+ case Req#req.connection of
371
+ undefined ->
372
+ % nothing to echo
373
+ Headers;
374
+ Connection ->
375
+ % echo
376
+ case misultin_utility:get_key_value('Connection', Headers) of
377
+ undefined ->
378
+ [{'Connection', connection_str(Connection)}|Headers];
379
+ _ExistingConnectionHeaderValue ->
380
+ Headers
381
+ end
382
+ end.
383
+
384
+ % Helper to Connection string
385
+ connection_str(keep_alive) -> "Keep-Alive";
386
+ connection_str(close) -> "Close".
387
+
388
+ % Description: Encode headers
389
+ enc_headers([{Tag, Val}|T]) when is_atom(Tag) ->
390
+ [atom_to_list(Tag), ": ", enc_header_val(Val), "\r\n"|enc_headers(T)];
391
+ enc_headers([{Tag, Val}|T]) when is_list(Tag) ->
392
+ [Tag, ": ", enc_header_val(Val), "\r\n"|enc_headers(T)];
393
+ enc_headers([]) ->
394
+ [].
395
+ enc_header_val(Val) when is_atom(Val) ->
396
+ atom_to_list(Val);
397
+ enc_header_val(Val) when is_integer(Val) ->
398
+ integer_to_list(Val);
399
+ enc_header_val(Val) ->
400
+ Val.
401
+
402
+ % Split the path at the ?
403
+ split_at_q_mark([$?|T], Acc) ->
404
+ {lists:reverse(Acc), T};
405
+ split_at_q_mark([H|T], Acc) ->
406
+ split_at_q_mark(T, [H|Acc]);
407
+ split_at_q_mark([], Acc) ->
408
+ {lists:reverse(Acc), []}.
409
+
410
+ % Function: -> {EncodingHeader, binary()}
411
+ % Description: Compress body depending on Request Headers and misultin supported encodings.
412
+ compress_body(RequestHeaders, BodyBinary, true) ->
413
+ case misultin_utility:get_key_value('Accept-Encoding', RequestHeaders) of
414
+ undefined ->
415
+ % unkown encoding accepted, return body as is
416
+ ?LOG_DEBUG("no accepted encoding specified by request: building binary body without compression", []),
417
+ {[], BodyBinary};
418
+ AcceptEncoding ->
419
+ case set_encoding(AcceptEncoding) of
420
+ none ->
421
+ % no common encoding accepted
422
+ ?LOG_DEBUG("no supported compression: building binary body without compression", []),
423
+ {[], BodyBinary};
424
+ Encoding ->
425
+ ?LOG_DEBUG("building binary body with ~p compression", [Encoding]),
426
+ {{'Content-Encoding', Encoding}, encode(Encoding, BodyBinary)}
427
+ end
428
+ end;
429
+ compress_body(_RequestHeaders, BodyBinary, false) ->
430
+ ?LOG_DEBUG("building binary body without compression", []),
431
+ {[], BodyBinary}.
432
+
433
+ % Function: -> binary()
434
+ % Description: Compress body.
435
+ encode(deflate, BodyBinary) ->
436
+ zlib:compress(BodyBinary);
437
+ encode(gzip, BodyBinary) ->
438
+ zlib:gzip(BodyBinary).
439
+
440
+ % Function: -> atom() | none
441
+ % Description: Set encoding name depending on Request Headers and supported misultin encodings.
442
+ set_encoding(AcceptEncodingHeader) ->
443
+ % get request accepted encodings
444
+ RequestEncodings = get_accepted_encodings(AcceptEncodingHeader),
445
+ % get a request accepted encoding which is supported by misultin
446
+ F = fun({E, _Q}) ->
447
+ lists:member(E, ?SUPPORTED_ENCODINGS)
448
+ end,
449
+ case lists:filter(F, RequestEncodings) of
450
+ [] -> none;
451
+ [{Enc, _Q}|_T] -> list_to_atom(Enc)
452
+ end.
453
+
454
+ % Function: -> [{Encoding, Q},...]
455
+ % Description: Get accepted encodings and quality, sorted by quality.
456
+ get_accepted_encodings(AcceptEncodingHeader) ->
457
+ % take away empty spaces
458
+ Header = lists:filter(fun(E) -> case E of $\s -> false; _ -> true end end, AcceptEncodingHeader),
459
+ % get values
460
+ F = fun(E, AccIn) ->
461
+ case string:tokens(E, ";") of
462
+ [Enc] -> [{Enc, 1.0}|AccIn];
463
+ [Enc, QStr] ->
464
+ [_, Val] = string:tokens(QStr, "="),
465
+ case list_to_number(Val) of
466
+ not_a_number -> AccIn;
467
+ V -> [{Enc, V}|AccIn]
468
+ end;
469
+ _ -> AccIn
470
+ end
471
+ end,
472
+ Encodings0 = lists:foldl(F, [], string:tokens(Header, ",")),
473
+ % sort
474
+ lists:sort(fun({_E1, Q1}, {_E2, Q2}) -> Q1 > Q2 end, Encodings0).
475
+
476
+ % Function: -> number() | not_a_number
477
+ % Description: Converts a list to a number.
478
+ list_to_number(L) ->
479
+ case catch list_to_float(L) of
480
+ {'EXIT', _} ->
481
+ case catch list_to_integer(L) of
482
+ {'EXIT', _} -> not_a_number;
483
+ Value -> Value
484
+ end;
485
+ Value -> Value
486
+ end.
487
+
488
+ % ============================ /\ INTERNAL FUNCTIONS =======================================================