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.
- data/erlang/lib/capricorn/ebin/capricorn.app +2 -1
- data/erlang/lib/capricorn/include/capricorn.hrl +1 -0
- data/erlang/lib/capricorn/src/cap_cluster_gems.erl +6 -1
- data/erlang/lib/capricorn/src/cap_console_dispatcher.erl +214 -0
- data/erlang/lib/capricorn/src/cap_machine_apps.erl +25 -1
- data/erlang/lib/capricorn/src/cap_sup.erl +14 -0
- data/erlang/lib/ejson/Makefile +24 -0
- data/erlang/lib/ejson/ebin/ejson.app +9 -0
- data/erlang/lib/ejson/include/ejson.hrl +40 -0
- data/erlang/lib/ejson/rebar.config +3 -0
- data/erlang/lib/ejson/src/ejson.erl +22 -0
- data/erlang/lib/ejson/src/ejson_decode.erl +337 -0
- data/erlang/lib/ejson/src/ejson_encode.erl +124 -0
- data/erlang/lib/ejson/test/arrays.escript +47 -0
- data/erlang/lib/ejson/test/compound.escript +56 -0
- data/erlang/lib/ejson/test/literals.escript +30 -0
- data/erlang/lib/ejson/test/numbers.escript +70 -0
- data/erlang/lib/ejson/test/objects.escript +51 -0
- data/erlang/lib/ejson/test/strings.escript +49 -0
- data/erlang/lib/ejson/test/timing.escript +43 -0
- data/erlang/lib/ejson/test/timing.json +382 -0
- data/erlang/lib/ejson/vendor/mochijson2.erl +621 -0
- data/erlang/lib/ejson/vendor/rfc4627.erl +625 -0
- data/erlang/lib/misultin/LICENSE.txt +41 -0
- data/erlang/lib/misultin/Makefile +26 -0
- data/erlang/lib/misultin/README.txt +120 -0
- data/erlang/lib/misultin/ebin/misultin.app +9 -0
- data/erlang/lib/misultin/examples/misultin_compress.erl +43 -0
- data/erlang/lib/misultin/examples/misultin_echo.erl +58 -0
- data/erlang/lib/misultin/examples/misultin_file.erl +43 -0
- data/erlang/lib/misultin/examples/misultin_gen_server.erl +158 -0
- data/erlang/lib/misultin/examples/misultin_get_variable.erl +55 -0
- data/erlang/lib/misultin/examples/misultin_hello_world.erl +43 -0
- data/erlang/lib/misultin/examples/misultin_rest.erl +68 -0
- data/erlang/lib/misultin/examples/misultin_ssl.erl +51 -0
- data/erlang/lib/misultin/examples/misultin_stream.erl +55 -0
- data/erlang/lib/misultin/examples/misultin_websocket_event_example.erl +103 -0
- data/erlang/lib/misultin/examples/misultin_websocket_example.erl +95 -0
- data/erlang/lib/misultin/include/misultin.hrl +95 -0
- data/erlang/lib/misultin/make.bat +55 -0
- data/erlang/lib/misultin/priv/README.txt +12 -0
- data/erlang/lib/misultin/priv/test_certificate.pem +21 -0
- data/erlang/lib/misultin/priv/test_privkey.pem +18 -0
- data/erlang/lib/misultin/rebar.config +3 -0
- data/erlang/lib/misultin/src/misultin.app.src +9 -0
- data/erlang/lib/misultin/src/misultin.erl +338 -0
- data/erlang/lib/misultin/src/misultin_http.erl +488 -0
- data/erlang/lib/misultin/src/misultin_req.erl +280 -0
- data/erlang/lib/misultin/src/misultin_socket.erl +193 -0
- data/erlang/lib/misultin/src/misultin_utility.erl +357 -0
- data/erlang/lib/misultin/src/misultin_websocket.erl +252 -0
- data/erlang/lib/misultin/src/misultin_ws.erl +78 -0
- data/erlang/rebar.config +2 -0
- data/erlang/rel/overlay/etc/capricorn/app.config +4 -0
- data/erlang/rel/reltool.config +5 -0
- data/lib/capricorn/recipes/apache-debian.rb +1 -1
- data/lib/capricorn/recipes/centos-plesk.rb +1 -1
- data/lib/capricorn/recipes/debian-plesk95.rb +1 -2
- data/lib/capricorn/recipes/macports.rb +1 -1
- data/lib/capricorn/version.rb +1 -1
- 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 =======================================================
|