capricorn 2.0.8 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 =======================================================