PageRenderTime 40ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/src/misultin/misultin_http.erl

http://github.com/evanmiller/ChicagoBoss
Erlang | 487 lines | 356 code | 32 blank | 99 comment | 1 complexity | de6d4871fa64d1c5234785be38f84609 MD5 | raw file
  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.0").
  35. % API
  36. -export([handle_data/8]).
  37. % internal
  38. -export([socket_loop/2]).
  39. % macros
  40. -define(MAX_HEADERS_COUNT, 100).
  41. -define(SUPPORTED_ENCODINGS, ["gzip", "deflate"]).
  42. % records
  43. -record(c, {
  44. sock,
  45. socket_mode,
  46. port,
  47. recv_timeout,
  48. compress,
  49. stream_support,
  50. loop,
  51. ws_loop
  52. }).
  53. % includes
  54. -include("misultin.hrl").
  55. % ============================ \/ API ======================================================================
  56. % Callback from misultin_socket
  57. handle_data(Sock, SocketMode, ListenPort, PeerAddr, PeerPort, PeerCert, RecvTimeout, CustomOpts) ->
  58. % build connection & request records
  59. 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},
  60. Req = #req{socket = Sock, socket_mode = SocketMode, peer_addr = PeerAddr, peer_port = PeerPort, peer_cert = PeerCert},
  61. % enter loop
  62. request(C, Req).
  63. % ============================ /\ API ======================================================================
  64. % ============================ \/ INTERNAL FUNCTIONS =======================================================
  65. % REQUEST: wait for a HTTP Request line. Transition to state headers if one is received.
  66. request(#c{sock = Sock, socket_mode = SocketMode, recv_timeout = RecvTimeout} = C, Req) ->
  67. misultin_socket:setopts(Sock, [{active, once}, {packet, http}], SocketMode),
  68. receive
  69. {SocketMode, Sock, {http_request, Method, Path, Version}} ->
  70. ?LOG_DEBUG("received full headers of a new HTTP packet", []),
  71. % change packet type if in ssl mode
  72. case SocketMode of
  73. ssl -> misultin_socket:setopts(Sock, [{packet, httph}], SocketMode);
  74. _ -> ok
  75. end,
  76. % go to headers
  77. headers(C, Req#req{vsn = Version, method = Method, uri = Path, connection = default_connection(Version)}, []);
  78. {SocketMode, Sock, {http_error, "\r\n"}} ->
  79. request(C, Req);
  80. {SocketMode, Sock, {http_error, "\n"}} ->
  81. request(C, Req);
  82. {http, Sock, {http_error, _Other}} ->
  83. ?LOG_WARNING("received a http error, might be a ssl request while socket in http mode: ~p, sending forbidden response and closing socket", [_Other]),
  84. misultin_socket:send(Sock, misultin_utility:get_http_status_code(403), SocketMode),
  85. misultin_socket:close(Sock, SocketMode),
  86. exit(normal);
  87. _Other ->
  88. ?LOG_WARNING("tcp error on incoming request: ~p, closing socket", [_Other]),
  89. misultin_socket:close(Sock, SocketMode),
  90. exit(normal)
  91. after RecvTimeout ->
  92. ?LOG_DEBUG("normal receive timeout, exit", []),
  93. misultin_socket:close(Sock, SocketMode),
  94. exit(normal)
  95. end.
  96. % HEADERS: collect HTTP headers. After the end of header marker transition to body state.
  97. headers(C, Req, H) ->
  98. headers(C, Req, H, 0).
  99. headers(#c{sock = Sock, socket_mode = SocketMode}, _Req, _H, ?MAX_HEADERS_COUNT) ->
  100. ?LOG_DEBUG("too many headers sent, bad request",[]),
  101. misultin_socket:send(Sock, misultin_utility:get_http_status_code(400), SocketMode);
  102. headers(#c{sock = Sock, socket_mode = SocketMode, recv_timeout = RecvTimeout, ws_loop = WsLoop} = C, Req, H, HeaderCount) ->
  103. misultin_socket:setopts(Sock, [{active, once}], SocketMode),
  104. receive
  105. {SocketMode, Sock, {http_header, _, 'Content-Length', _, Val} = _Head} ->
  106. ?LOG_DEBUG("received header: ~p", [_Head]),
  107. headers(C, Req#req{content_length = Val}, [{'Content-Length', Val}|H], HeaderCount + 1);
  108. {SocketMode, Sock, {http_header, _, 'Connection', _, Val} = _Head} ->
  109. ?LOG_DEBUG("received header: ~p", [_Head]),
  110. headers(C, Req#req{connection = keep_alive(Req#req.vsn, Val)}, [{'Connection', Val}|H], HeaderCount + 1);
  111. {SocketMode, Sock, {http_header, _, Header, _, Val} = _Head} ->
  112. ?LOG_DEBUG("received header: ~p", [_Head]),
  113. headers(C, Req, [{Header, Val}|H], HeaderCount + 1);
  114. {SocketMode, Sock, {http_error, "\r\n"} = _Head} ->
  115. ?LOG_DEBUG("received header: ~p", [_Head]),
  116. headers(C, Req, H, HeaderCount);
  117. {SocketMode, Sock, {http_error, "\n"} = _Head} ->
  118. ?LOG_DEBUG("received header: ~p", [_Head]),
  119. headers(C, Req, H, HeaderCount);
  120. {SocketMode, Sock, http_eoh} ->
  121. ?LOG_DEBUG("received EOH header", []),
  122. Headers = lists:reverse(H),
  123. {_PathType, Path} = Req#req.uri,
  124. % check if it's a websocket request
  125. CheckWs = case WsLoop of
  126. none -> false;
  127. _Function -> misultin_websocket:check(Path, Headers)
  128. end,
  129. case CheckWs of
  130. false ->
  131. ?LOG_DEBUG("normal http request received", []),
  132. body(C, Req#req{headers = Headers});
  133. {true, Origin, Host, Path} ->
  134. ?LOG_DEBUG("websocket request received", []),
  135. misultin_websocket:connect(#ws{socket = Sock, socket_mode = SocketMode, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port, origin = Origin, host = Host, path = Path}, WsLoop)
  136. end;
  137. {SocketMode, Sock, _Other} ->
  138. ?LOG_DEBUG("tcp error treating headers: ~p, send bad request error back", [_Other]),
  139. misultin_socket:send(Sock, misultin_utility:get_http_status_code(400), SocketMode);
  140. _Other ->
  141. ?LOG_DEBUG("received unknown message: ~p, ignoring", [_Other]),
  142. ignored
  143. after RecvTimeout ->
  144. ?LOG_DEBUG("headers timeout, sending request timeout error", []),
  145. misultin_socket:send(Sock, misultin_utility:get_http_status_code(408), SocketMode)
  146. end.
  147. % default connection
  148. default_connection({1,1}) -> keep_alive;
  149. default_connection(_) -> close.
  150. % Shall we keep the connection alive? Default case for HTTP/1.1 is yes, default for HTTP/1.0 is no.
  151. keep_alive({1,1}, "close") -> close;
  152. keep_alive({1,1}, "Close") -> close;
  153. % string:to_upper is used only as last resort.
  154. keep_alive({1,1}, Head) ->
  155. case string:to_upper(Head) of
  156. "CLOSE" -> close;
  157. _ -> keep_alive
  158. end;
  159. keep_alive({1,0}, "Keep-Alive") -> keep_alive;
  160. keep_alive({1,0}, Head) ->
  161. case string:to_upper(Head) of
  162. "KEEP-ALIVE" -> keep_alive;
  163. _ -> close
  164. end;
  165. keep_alive({0,9}, _) -> close;
  166. keep_alive(_Vsn, _KA) -> close.
  167. % BODY: collect the body of the HTTP request if there is one, and lookup and call the implementation callback.
  168. % Depending on whether the request is persistent transition back to state request to await the next request or exit.
  169. body(#c{sock = Sock, socket_mode = SocketMode, recv_timeout = RecvTimeout} = C, Req) ->
  170. case Req#req.method of
  171. 'GET' ->
  172. ?LOG_DEBUG("GET request received",[]),
  173. Close = handle_get(C, Req),
  174. case Close of
  175. close ->
  176. % close socket
  177. misultin_socket:close(Sock, SocketMode);
  178. keep_alive ->
  179. 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})
  180. end;
  181. 'POST' ->
  182. ?LOG_DEBUG("POST request received", []),
  183. case catch list_to_integer(Req#req.content_length) of
  184. {'EXIT', _} ->
  185. % TODO: provide a fallback when content length is not or wrongly specified
  186. ?LOG_DEBUG("specified content length is not a valid integer number: ~p", [Req#req.content_length]),
  187. misultin_socket:send(Sock, misultin_utility:get_http_status_code(411), SocketMode),
  188. exit(normal);
  189. 0 ->
  190. ?LOG_DEBUG("zero content-lenght specified, skipping parsing body of request", []),
  191. Close = handle_post(C, Req),
  192. case Close of
  193. close ->
  194. % close socket
  195. misultin_socket:close(Sock, SocketMode);
  196. keep_alive ->
  197. misultin_socket:setopts(Sock, [{packet, http}], SocketMode),
  198. 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})
  199. end;
  200. Len ->
  201. ?LOG_DEBUG("parsing POST content in body of request", []),
  202. misultin_socket:setopts(Sock, [{packet, raw}, {active, false}], SocketMode),
  203. case misultin_socket:recv(Sock, Len, RecvTimeout, SocketMode) of
  204. {ok, Bin} ->
  205. Close = handle_post(C, Req#req{body = Bin}),
  206. case Close of
  207. close ->
  208. % close socket
  209. misultin_socket:close(Sock, SocketMode);
  210. keep_alive ->
  211. misultin_socket:setopts(Sock, [{packet, http}], SocketMode),
  212. 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})
  213. end;
  214. {error, timeout} ->
  215. ?LOG_DEBUG("request timeout, sending error", []),
  216. misultin_socket:send(Sock, misultin_utility:get_http_status_code(408), SocketMode);
  217. _Other ->
  218. ?LOG_DEBUG("tcp error treating post data: ~p, send bad request error back", [_Other]),
  219. misultin_socket:send(Sock, misultin_utility:get_http_status_code(400), SocketMode)
  220. end
  221. end;
  222. _Other ->
  223. ?LOG_DEBUG("method not implemented: ~p", [_Other]),
  224. misultin_socket:send(Sock, misultin_utility:get_http_status_code(501), SocketMode),
  225. exit(normal)
  226. end.
  227. % handle a get request
  228. handle_get(C, #req{socket_mode = SocketMode, connection = Conn} = Req) ->
  229. case Req#req.uri of
  230. {abs_path, Path} ->
  231. {F, Args} = split_at_q_mark(Path, []),
  232. call_mfa(C, Req#req{args = Args, uri = {abs_path, F}}),
  233. Conn;
  234. {absoluteURI, http, _Host, _, Path} ->
  235. {F, Args} = split_at_q_mark(Path, []),
  236. call_mfa(C, Req#req{args = Args, uri = {absoluteURI, F}}),
  237. Conn;
  238. {absoluteURI, _Other_method, _Host, _, _Path} ->
  239. misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(501), SocketMode),
  240. close;
  241. {scheme, _Scheme, _RequestString} ->
  242. misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(510), SocketMode),
  243. close;
  244. _ ->
  245. misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(403), SocketMode),
  246. close
  247. end.
  248. % handle a post request
  249. handle_post(C, #req{socket_mode = SocketMode, connection = Conn} = Req) ->
  250. case Req#req.uri of
  251. {abs_path, _Path} ->
  252. call_mfa(C, Req),
  253. Conn;
  254. {absoluteURI, http, _Host, _, _Path} ->
  255. call_mfa(C, Req),
  256. Conn;
  257. {absoluteURI, _Other_method, _Host, _, _Path} ->
  258. misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(501), SocketMode),
  259. close;
  260. {scheme, _Scheme, _RequestString} ->
  261. misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(501), SocketMode),
  262. close;
  263. _ ->
  264. misultin_socket:send(C#c.sock, misultin_utility:get_http_status_code(403), SocketMode),
  265. close
  266. end.
  267. % Description: Main dispatcher
  268. call_mfa(#c{sock = Sock, socket_mode = SocketMode, compress = Compress, stream_support = StreamSupport, loop = Loop} = C, #req{headers = RequestHeaders} = Request) ->
  269. % spawn listening process for Request messages [only used to support stream requests]
  270. case StreamSupport of
  271. true ->
  272. SocketPid = spawn(?MODULE, socket_loop, [C, Request]);
  273. false ->
  274. SocketPid = no_stream_support_proc
  275. end,
  276. % create request
  277. Req = misultin_req:new(Request, SocketPid),
  278. % call loop
  279. case catch Loop(Req) of
  280. {'EXIT', _Reason} ->
  281. ?LOG_ERROR("worker crash: ~p", [_Reason]),
  282. % kill listening socket
  283. catch SocketPid ! shutdown,
  284. % send response
  285. misultin_socket:send(Sock, misultin_utility:get_http_status_code(500), SocketMode),
  286. % force exit
  287. exit(normal);
  288. {HttpCode, Headers0, Body} ->
  289. % received normal response
  290. ?LOG_DEBUG("sending response", []),
  291. % kill listening socket
  292. catch SocketPid ! shutdown,
  293. % build binary body & compress if necessary
  294. {CompressHeaders, BodyBinary} = compress_body(RequestHeaders, convert_to_binary(Body), Compress),
  295. % build headers
  296. Headers1 = add_output_header('Content-Length', {Headers0, BodyBinary}),
  297. Headers = add_output_header('Connection', {Headers1, Request}),
  298. Enc_headers = enc_headers(lists:flatten([CompressHeaders|Headers])),
  299. % build and send response
  300. Resp = [misultin_utility:get_http_status_code(HttpCode), Enc_headers, <<"\r\n">>, BodyBinary],
  301. misultin_socket:send(Sock, Resp, SocketMode);
  302. {raw, Body} ->
  303. misultin_socket:send(Sock, Body, SocketMode);
  304. _ ->
  305. % loop exited normally, kill listening socket
  306. catch SocketPid ! shutdown
  307. end.
  308. % Description: Ensure Body is binary.
  309. convert_to_binary(Body) when is_list(Body) ->
  310. list_to_binary(lists:flatten(Body));
  311. convert_to_binary(Body) when is_binary(Body) ->
  312. Body;
  313. convert_to_binary(Body) when is_atom(Body) ->
  314. list_to_binary(atom_to_list(Body)).
  315. % Description: Socket loop for stream responses
  316. socket_loop(#c{sock = Sock, socket_mode = SocketMode} = C, Request) ->
  317. receive
  318. {stream_head, HttpCode, Headers0} ->
  319. ?LOG_DEBUG("sending stream head", []),
  320. Headers = add_output_header('Connection', {Headers0, Request}),
  321. Enc_headers = enc_headers(Headers),
  322. Resp = [misultin_utility:get_http_status_code(HttpCode), Enc_headers, <<"\r\n">>],
  323. misultin_socket:send(Sock, Resp, SocketMode),
  324. socket_loop(C, Request);
  325. {stream_data, Body} ->
  326. ?LOG_DEBUG("sending stream data", []),
  327. misultin_socket:send(Sock, Body, SocketMode),
  328. socket_loop(C, Request);
  329. stream_close ->
  330. ?LOG_DEBUG("closing stream", []),
  331. misultin_socket:close(Sock, SocketMode);
  332. shutdown ->
  333. ?LOG_DEBUG("shutting down socket loop", []),
  334. shutdown
  335. end.
  336. % Description: Add necessary Content-Length Header
  337. add_output_header('Content-Length', {Headers, Body}) ->
  338. case misultin_utility:get_key_value('Content-Length', Headers) of
  339. undefined ->
  340. [{'Content-Length', size(Body)}|Headers];
  341. _ExistingContentLength ->
  342. Headers
  343. end;
  344. % Description: Add necessary Connection Header
  345. add_output_header('Connection', {Headers, Req}) ->
  346. case Req#req.connection of
  347. undefined ->
  348. % nothing to echo
  349. Headers;
  350. Connection ->
  351. % echo
  352. case misultin_utility:get_key_value('Connection', Headers) of
  353. undefined ->
  354. [{'Connection', connection_str(Connection)}|Headers];
  355. _ExistingConnectionHeaderValue ->
  356. Headers
  357. end
  358. end.
  359. % Helper to Connection string
  360. connection_str(keep_alive) -> "Keep-Alive";
  361. connection_str(close) -> "Close".
  362. % Description: Encode headers
  363. enc_headers([{Tag, Val}|T]) when is_atom(Tag) ->
  364. [atom_to_list(Tag), ": ", enc_header_val(Val), "\r\n"|enc_headers(T)];
  365. enc_headers([{Tag, Val}|T]) when is_list(Tag) ->
  366. [Tag, ": ", enc_header_val(Val), "\r\n"|enc_headers(T)];
  367. enc_headers([]) ->
  368. [].
  369. enc_header_val(Val) when is_atom(Val) ->
  370. atom_to_list(Val);
  371. enc_header_val(Val) when is_integer(Val) ->
  372. integer_to_list(Val);
  373. enc_header_val(Val) ->
  374. Val.
  375. % Split the path at the ?
  376. split_at_q_mark([$?|T], Acc) ->
  377. {lists:reverse(Acc), T};
  378. split_at_q_mark([H|T], Acc) ->
  379. split_at_q_mark(T, [H|Acc]);
  380. split_at_q_mark([], Acc) ->
  381. {lists:reverse(Acc), []}.
  382. % Function: -> {EncodingHeader, binary()}
  383. % Description: Compress body depending on Request Headers and misultin supported encodings.
  384. compress_body(RequestHeaders, BodyBinary, true) ->
  385. case misultin_utility:get_key_value('Accept-Encoding', RequestHeaders) of
  386. undefined ->
  387. % unkown encoding accepted, return body as is
  388. ?LOG_DEBUG("no accepted encoding specified by request: building binary body without compression", []),
  389. {[], BodyBinary};
  390. AcceptEncoding ->
  391. case set_encoding(AcceptEncoding) of
  392. none ->
  393. % no common encoding accepted
  394. ?LOG_DEBUG("no supported compression: building binary body without compression", []),
  395. {[], BodyBinary};
  396. Encoding ->
  397. ?LOG_DEBUG("building binary body with ~p compression", [Encoding]),
  398. {{'Content-Encoding', Encoding}, encode(Encoding, BodyBinary)}
  399. end
  400. end;
  401. compress_body(_RequestHeaders, BodyBinary, false) ->
  402. ?LOG_DEBUG("building binary body without compression", []),
  403. {[], BodyBinary}.
  404. % Function: -> binary()
  405. % Description: Compress body.
  406. encode(deflate, BodyBinary) ->
  407. zlib:compress(BodyBinary);
  408. encode(gzip, BodyBinary) ->
  409. zlib:gzip(BodyBinary).
  410. % Function: -> atom() | none
  411. % Description: Set encoding name depending on Request Headers and supported misultin encodings.
  412. set_encoding(AcceptEncodingHeader) ->
  413. % get request accepted encodings
  414. RequestEncodings = get_accepted_encodings(AcceptEncodingHeader),
  415. % get a request accepted encoding which is supported by misultin
  416. F = fun({E, _Q}) ->
  417. lists:member(E, ?SUPPORTED_ENCODINGS)
  418. end,
  419. case lists:filter(F, RequestEncodings) of
  420. [] -> none;
  421. [{Enc, _Q}|_T] -> list_to_atom(Enc)
  422. end.
  423. % Function: -> [{Encoding, Q},...]
  424. % Description: Get accepted encodings and quality, sorted by quality.
  425. get_accepted_encodings(AcceptEncodingHeader) ->
  426. % take away empty spaces
  427. Header = lists:filter(fun(E) -> case E of $\s -> false; _ -> true end end, AcceptEncodingHeader),
  428. % get values
  429. F = fun(E, AccIn) ->
  430. case string:tokens(E, ";") of
  431. [Enc] -> [{Enc, 1.0}|AccIn];
  432. [Enc, QStr] ->
  433. [_, Val] = string:tokens(QStr, "="),
  434. case list_to_number(Val) of
  435. not_a_number -> AccIn;
  436. V -> [{Enc, V}|AccIn]
  437. end;
  438. _ -> AccIn
  439. end
  440. end,
  441. Encodings0 = lists:foldl(F, [], string:tokens(Header, ",")),
  442. % sort
  443. lists:sort(fun({_E1, Q1}, {_E2, Q2}) -> Q1 > Q2 end, Encodings0).
  444. % Function: -> number() | not_a_number
  445. % Description: Converts a list to a number.
  446. list_to_number(L) ->
  447. case catch list_to_float(L) of
  448. {'EXIT', _} ->
  449. case catch list_to_integer(L) of
  450. {'EXIT', _} -> not_a_number;
  451. Value -> Value
  452. end;
  453. Value -> Value
  454. end.
  455. % ============================ /\ INTERNAL FUNCTIONS =======================================================