capricorn 2.0.8 → 2.0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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 =======================================================
|