PageRenderTime 79ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/server_gateways/ewgi_inets.erl

http://github.com/skarab/ewgi
Erlang | 473 lines | 377 code | 58 blank | 38 comment | 3 complexity | e7e8fd51e0b508e096479106a4ae3c84 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. %%%-------------------------------------------------------------------
  2. %%% File : ewgi_inets.erl
  3. %%% Author : Hunter Morris <huntermorris@gmail.com>
  4. %%% License :
  5. %%% The contents of this file are subject to the Mozilla Public
  6. %%% License Version 1.1 (the "License"); you may not use this file
  7. %%% except in compliance with the License. You may obtain a copy of
  8. %%% the License at http://www.mozilla.org/MPL/
  9. %%%
  10. %%% Software distributed under the License is distributed on an "AS IS"
  11. %%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  12. %%% the License for the specific language governing rights and
  13. %%% limitations under the License.
  14. %%% The Initial Developer of the Original Code is S.G. Consulting
  15. %%% srl. Portions created by S.G. Consulting s.r.l. are Copyright (C)
  16. %%% 2007 S.G. Consulting srl. All Rights Reserved.
  17. %%%
  18. %%% @doc
  19. %%% <p>Reference implementation of a inets EWGI server gateway.</p>
  20. %%% <p>Requires that environment variables 'app_module' and
  21. %%% 'app_function' are set for application 'ewgi'</p>
  22. %%%
  23. %%% @end
  24. %%%-------------------------------------------------------------------
  25. -module(ewgi_inets).
  26. -export([do/1]).
  27. -export([
  28. stream_process_deliver/2,
  29. stream_process_deliver_chunk/2,
  30. stream_process_deliver_final_chunk/2,
  31. stream_process_end/2
  32. ]).
  33. -ifdef(HAS_R14).
  34. -include_lib("inets/src/http_server/httpd.hrl").
  35. -else.
  36. -include_lib("inets/src/httpd.hrl").
  37. -endif.
  38. -include_lib("ewgi.hrl").
  39. do(A) ->
  40. try parse_arg(A) of
  41. Req when ?IS_EWGI_REQUEST(Req) ->
  42. try process_application(ewgi_api:context(Req, ewgi_api:empty_response())) of
  43. not_found ->
  44. {proceed, [{response, {404, []}}]};
  45. Ctx when ?IS_EWGI_CONTEXT(Ctx) ->
  46. handle_result(A, ?INSPECT_EWGI_RESPONSE(Ctx))
  47. catch
  48. _:Reason ->
  49. error_logger:error_report(io_lib:format("Responding with 500 INTERNAL SERVER ERROR.~nReason: ~p~nStack: ~p~n", [Reason, erlang:get_stacktrace()])),
  50. {break, [{response, {500, []}}]}
  51. end
  52. catch
  53. _:Reason ->
  54. error_logger:error_report(io_lib:format("Responding with 400 BAD REQUEST.~nReason: ~p~nStack: ~p~n", [Reason, erlang:get_stacktrace()])),
  55. {break, [{response, {400, []}}]}
  56. end.
  57. process_application(Ctx) ->
  58. M = case application:get_env(ewgi, app_module) of
  59. {ok, Mod} -> Mod;
  60. _ -> throw({error, "ewgi app_module environment variable not set"})
  61. end,
  62. F = case application:get_env(ewgi, app_function) of
  63. {ok, Fun} -> Fun;
  64. _ -> throw({error, "ewgi app_function environment variable not set"})
  65. end,
  66. Appl = fun(A) -> apply(M, F, [A]) end,
  67. ewgi_application:run(Appl, Ctx).
  68. parse_arg(A) when is_record(A, mod) ->
  69. ewgi_api:server_request_foldl(A, fun parse_element/2, fun parse_ewgi_element/2, fun parse_http_header_element/2).
  70. parse_element(auth_type, _Req) ->
  71. undefined;
  72. parse_element(content_length, #mod{parsed_header=H}) ->
  73. case proplists:get_value("content-length", H) of
  74. undefined -> undefined;
  75. Length when is_integer(Length) ->
  76. Length;
  77. Length when is_list(Length) ->
  78. list_to_integer(Length)
  79. end;
  80. parse_element(content_type, #mod{parsed_header=H}) ->
  81. proplists:get_value("content-type", H);
  82. parse_element(gateway_interface, _Req) ->
  83. "EWGI/1.0";
  84. parse_element(path_info, #mod{request_uri=U}) ->
  85. {_, _, Path, _, _} = ewgi_api:urlsplit(U),
  86. ewgi_api:unquote_path(Path);
  87. parse_element(path_translated, _Req) ->
  88. undefined;
  89. parse_element(query_string, #mod{request_uri=U}) ->
  90. {_, _, _, QueryString, _} = ewgi_api:urlsplit(U),
  91. QueryString;
  92. parse_element(remote_addr, #mod{socket_type={ssl, _}, socket=S}) ->
  93. get_ip(ssl:peername(S));
  94. parse_element(remote_addr, #mod{socket=S}) ->
  95. get_ip(inet:peername(S));
  96. parse_element(remote_host, _Req) ->
  97. undefined;
  98. parse_element(remote_ident, _Req) ->
  99. undefined;
  100. parse_element(remote_user, _Req) ->
  101. undefined;
  102. parse_element(request_method, #mod{method=M}) ->
  103. get_method(M);
  104. parse_element(script_name, _Req) ->
  105. [];
  106. parse_element(server_name, #mod{parsed_header=H}) ->
  107. case proplists:get_value("host", H) of
  108. HostPort when is_list(HostPort) ->
  109. hd(string:tokens(HostPort, ":"));
  110. HostPort ->
  111. HostPort
  112. end;
  113. parse_element(server_port, #mod{parsed_header=H}) ->
  114. case proplists:get_value("host", H) of
  115. Host when is_list(Host) ->
  116. case string:tokens(Host, ":") of
  117. [_, Port] ->
  118. Port;
  119. _ ->
  120. undefined
  121. end;
  122. _ ->
  123. undefined
  124. end;
  125. parse_element(server_protocol, #mod{http_version=V}) ->
  126. V;
  127. parse_element(server_software, _Req) ->
  128. "inets";
  129. %% All other elements are undefined
  130. parse_element(_, _) ->
  131. undefined.
  132. parse_ewgi_element(read_input, #mod{entity_body=Buf}) ->
  133. F = fun(Callback, Length) when is_integer(Length) -> % No chunk size specified, so use default
  134. read_input(Callback, {Length, ?DEFAULT_CHUNKSIZE}, list_to_binary(Buf));
  135. (Callback, {Length, ChunkSz}) ->
  136. read_input(Callback, {Length, ChunkSz}, list_to_binary(Buf))
  137. end,
  138. F;
  139. parse_ewgi_element(write_error, Req) ->
  140. F = fun(Msg) ->
  141. error_logger:error_report([{message, Msg}, {request, Req}])
  142. end,
  143. F;
  144. parse_ewgi_element(url_scheme, #mod{socket_type={ssl, _}}) ->
  145. "https";
  146. parse_ewgi_element(url_scheme, _) ->
  147. "http";
  148. parse_ewgi_element(version, _) ->
  149. {1, 0};
  150. parse_ewgi_element(data, A) ->
  151. gb_trees:from_orddict([{"inets.mod", A}]);
  152. parse_ewgi_element(_, _) ->
  153. undefined.
  154. get_header(K, #mod{parsed_header=H}) ->
  155. proplists:get_value(K, H).
  156. parse_http_header_element(http_accept, A) ->
  157. get_header("accept", A);
  158. parse_http_header_element(http_cookie, A) ->
  159. get_header("cookie", A);
  160. parse_http_header_element(http_host, A) ->
  161. get_header("host", A);
  162. parse_http_header_element(http_if_modified_since, A) ->
  163. get_header("if-modified-since", A);
  164. parse_http_header_element(http_user_agent, A) ->
  165. get_header("user-agent", A);
  166. parse_http_header_element(http_x_http_method_override, A) ->
  167. get_header("x-http-method-override", A);
  168. parse_http_header_element(other, #mod{parsed_header=H}) ->
  169. lists:foldl(fun parse_other_header/2, gb_trees:empty(), H);
  170. parse_http_header_element(_, _) ->
  171. undefined.
  172. parse_other_header({K0, _}=Pair, Acc) ->
  173. parse_other_header1(K0, ewgi_api:normalize_header(Pair), Acc).
  174. parse_other_header1(_, {"content-length", _}, Acc) ->
  175. Acc;
  176. parse_other_header1(_, {"content-type", _}, Acc) ->
  177. Acc;
  178. parse_other_header1(_, {"accept", _}, Acc) ->
  179. Acc;
  180. parse_other_header1(_, {"cookie", _}, Acc) ->
  181. Acc;
  182. parse_other_header1(_, {"host", _}, Acc) ->
  183. Acc;
  184. parse_other_header1(_, {"if-modified-since", _}, Acc) ->
  185. Acc;
  186. parse_other_header1(_, {"user-agent", _}, Acc) ->
  187. Acc;
  188. parse_other_header1(_, {"x-http-method-override", _}, Acc) ->
  189. Acc;
  190. parse_other_header1(K0, {K, V}, Acc) ->
  191. Ex = case gb_trees:lookup(K, Acc) of
  192. {value, L} -> L;
  193. none -> []
  194. end,
  195. gb_trees:enter(K, [{K0, V}|Ex], Acc).
  196. handle_result(#mod{config_db=Db}=A, Ctx) ->
  197. case ewgi_api:response_message_body(Ctx) of
  198. {push_stream, GeneratorPid, Timeout} when is_pid(GeneratorPid) ->
  199. ChunkedAllowed = not httpd_response:is_disable_chunked_send(Db),
  200. handle_push_stream(A, Ctx, ChunkedAllowed, GeneratorPid, Timeout);
  201. Body0 ->
  202. {Code, _} = ewgi_api:response_status(Ctx),
  203. Headers0 = [{string:to_lower(H), binary_to_list(iolist_to_binary(V))} || {H, V} <- ewgi_api:response_headers(Ctx)],
  204. Headers = lists:foldl(fun fold_header/2, [], Headers0),
  205. case Body0 of
  206. Body when is_function(Body, 0) ->
  207. ChunkedAllowed = not httpd_response:is_disable_chunked_send(Db),
  208. handle_result_wrap_stream(A, ChunkedAllowed, Code, Headers, Body);
  209. _ ->
  210. Body = [Body0],
  211. Length = {content_length, integer_to_list(erlang:iolist_size(Body))},
  212. {proceed, [{response, {response, [{code, Code}, Length] ++ Headers, Body}}]}
  213. end
  214. end.
  215. handle_result_wrap_stream(#mod{http_version=Ver}, ChunkedAllowed, Code, Headers, Body0)
  216. when (Ver =/= "HTTP/1.1") or (not ChunkedAllowed) ->
  217. Body = stream_to_list(Body0),
  218. Length = {content_length, integer_to_list(erlang:iolist_size(Body))},
  219. {proceed, [{response, {response, [{code, Code}, Length] ++ Headers, Body}}]};
  220. handle_result_wrap_stream(A, true, Code, Headers, Body) ->
  221. ExtraHeaders = httpd_response:cache_headers(A),
  222. httpd_response:send_header(A, Code, ExtraHeaders ++ [{transfer_encoding, "chunked"}|Headers]),
  223. handle_stream(A, Code, Body, 0).
  224. handle_stream(A, Code, Generator, Size) when is_function(Generator, 0) ->
  225. case (catch Generator()) of
  226. {H, T} when is_function(T, 0) ->
  227. case H of
  228. <<>> -> ok;
  229. [] -> ok;
  230. _ ->
  231. httpd_response:send_chunk(A, [H], false)
  232. end,
  233. handle_stream(A, Code, T, Size + erlang:iolist_size([H]));
  234. {} ->
  235. httpd_response:send_final_chunk(A, false),
  236. {proceed, [{response, {already_sent, Code, Size}}]};
  237. Error ->
  238. error_logger:error_report(io_lib:format("Unexpected stream ouput (~p): ~p~n", [Generator, Error])),
  239. httpd_response:send_final_chunk(A, false)
  240. end;
  241. handle_stream(A, _Code, Generator, _Size) ->
  242. error_logger:error_report(io_lib:format("Invalid stream generator: ~p~n", [Generator])),
  243. httpd_response:send_final_chunk(A, false).
  244. stream_to_list(S) when is_function(S, 0) ->
  245. case S() of
  246. {H, T} -> [H|stream_to_list(T)];
  247. {} -> []
  248. end.
  249. handle_push_stream(#mod{http_version=Ver}, _Ctx, ChunkedAllowed, GeneratorPid, _Timeout)
  250. when (Ver =/= "HTTP/1.1") or (not ChunkedAllowed) ->
  251. GeneratorPid ! {discard, self()},
  252. Body = "HTTP/1.1 required for streaming live data!",
  253. Length = {content_length, integer_to_list(erlang:iolist_size(Body))},
  254. NotSupported = 505,
  255. {proceed, [{response, {response, [{code, NotSupported}, Length], Body}}]};
  256. handle_push_stream(A, Ctx, true, GeneratorPid, Timeout) ->
  257. Socket = A#mod.socket,
  258. GeneratorPid ! {push_stream_init, ?MODULE, self(), Socket},
  259. receive
  260. {push_stream_init, GeneratorPid, Code, Headers0, TransferEncoding} ->
  261. Headers1 = [{string:to_lower(H), binary_to_list(iolist_to_binary(V))} || {H, V} <- Headers0],
  262. Headers = lists:foldl(fun fold_header/2, [], Headers1),
  263. ExtraHeaders = httpd_response:cache_headers(A),
  264. httpd_response:send_header(A, Code, ExtraHeaders ++ Headers),
  265. case TransferEncoding of
  266. chunked ->
  267. GeneratorPid ! {ok, self()}
  268. ;_ ->
  269. %% WARNING: we're depending on the original ewgi_context here!!!!
  270. case ewgi_api:request_method(Ctx) of
  271. 'HEAD' ->
  272. GeneratorPid ! {discard, self()};
  273. _ ->
  274. GeneratorPid ! {ok, self()}
  275. end
  276. end,
  277. Socket = A#mod.socket,
  278. wait_for_streamcontent_pid(Socket, GeneratorPid)
  279. after Timeout ->
  280. Body = "Gateway Timeout",
  281. Length = {content_length, integer_to_list(erlang:iolist_size(Body))},
  282. GatewayTimeout = 504,
  283. {proceed, [{response, {response, [{code, GatewayTimeout}, Length], Body}}]}
  284. end.
  285. %% Copied/adapted from yaws_server
  286. wait_for_streamcontent_pid(CliSock, ContentPid) ->
  287. Ref = erlang:monitor(process, ContentPid),
  288. gen_tcp:controlling_process(CliSock, ContentPid),
  289. ContentPid ! {ok, self()},
  290. receive
  291. endofstreamcontent ->
  292. ok = gen_tcp:close(CliSock),
  293. erlang:demonitor(Ref),
  294. %% should just use demonitor [flush] option instead?
  295. receive
  296. {'DOWN', Ref, _, _, _} ->
  297. ok
  298. after 0 ->
  299. ok
  300. end;
  301. {'DOWN', Ref, _, _, _} ->
  302. ok
  303. end,
  304. done.
  305. %%--------------------------------------------------------------------
  306. %% Push Streams API - copied from yaws_api
  307. %%--------------------------------------------------------------------
  308. %% This won't work for SSL for now
  309. stream_process_deliver(Sock, IoList) ->
  310. gen_tcp:send(Sock, IoList).
  311. %% This won't work for SSL for now either
  312. stream_process_deliver_chunk(Sock, IoList) ->
  313. Chunk = case erlang:iolist_size(IoList) of
  314. 0 ->
  315. stream_process_deliver_final_chunk(Sock, IoList);
  316. S ->
  317. [http_util:integer_to_hexlist(S), "\r\n", IoList, "\r\n"]
  318. end,
  319. gen_tcp:send(Sock, Chunk).
  320. stream_process_deliver_final_chunk(Sock, IoList) ->
  321. Chunk = case erlang:iolist_size(IoList) of
  322. 0 ->
  323. <<"0\r\n\r\n">>;
  324. S ->
  325. [http_util:integer_to_hexlist(S), "\r\n", IoList, "\r\n0\r\n\r\n"]
  326. end,
  327. gen_tcp:send(Sock, Chunk).
  328. stream_process_end(Sock, ServerPid) ->
  329. gen_tcp:controlling_process(Sock, ServerPid),
  330. ServerPid ! endofstreamcontent.
  331. %%--------------------------------------------------------------------
  332. fold_header({"accept-ranges", V}, Acc) ->
  333. [{accept_ranges, V}|Acc];
  334. fold_header({"allow", V}, Acc) ->
  335. [{allow, V}|Acc];
  336. fold_header({"cache-control", V}, Acc) ->
  337. [{cache_control, V}|Acc];
  338. fold_header({"content-md5", V}, Acc) ->
  339. [{content_MD5, V}|Acc];
  340. fold_header({"content-encoding", V}, Acc) ->
  341. [{content_encoding, V}|Acc];
  342. fold_header({"content-language", V}, Acc) ->
  343. [{content_language, V}|Acc];
  344. fold_header({"content-length", V}, Acc) ->
  345. [{content_length, V}|Acc];
  346. fold_header({"content-location", V}, Acc) ->
  347. [{content_location, V}|Acc];
  348. fold_header({"content-range", V}, Acc) ->
  349. [{content_range, V}|Acc];
  350. fold_header({"content-type", V}, Acc) ->
  351. [{content_type, V}|Acc];
  352. fold_header({"date", V}, Acc) ->
  353. [{date, V}|Acc];
  354. fold_header({"etag", V}, Acc) ->
  355. [{etag, V}|Acc];
  356. fold_header({"expires", V}, Acc) ->
  357. [{expires, V}|Acc];
  358. fold_header({"last-modified", V}, Acc) ->
  359. [{last_modified, V}|Acc];
  360. fold_header({"location", V}, Acc) ->
  361. [{location, V}|Acc];
  362. fold_header({"pragma", V}, Acc) ->
  363. [{pragma, V}|Acc];
  364. fold_header({"retry-after", V}, Acc) ->
  365. [{retry_after, V}|Acc];
  366. fold_header({"server", V}, Acc) ->
  367. [{server, V}|Acc];
  368. fold_header({"trailer", V}, Acc) ->
  369. [{trailer, V}|Acc];
  370. fold_header({"transfer-encoding", V}, Acc) ->
  371. [{transfer_encoding, V}|Acc];
  372. fold_header({"set-cookie", V}, Acc) ->
  373. [{"set-cookie", V}|Acc];
  374. fold_header({"www-authenticate", V}, Acc) ->
  375. [{"www-authenticate", V}|Acc];
  376. fold_header(_, Acc) ->
  377. %% Ignore unrecognised headers
  378. Acc.
  379. get_ip({ok, {Ip, _Port}}) ->
  380. inet_parse:ntoa(Ip);
  381. get_ip(_) ->
  382. undefined.
  383. get_method("OPTIONS") ->
  384. 'OPTIONS';
  385. get_method("GET") ->
  386. 'GET';
  387. get_method("HEAD") ->
  388. 'HEAD';
  389. get_method("POST") ->
  390. 'POST';
  391. get_method("PUT") ->
  392. 'PUT';
  393. get_method("DELETE") ->
  394. 'DELETE';
  395. get_method("TRACE") ->
  396. 'TRACE';
  397. get_method("CONNECT") ->
  398. 'CONNECT';
  399. get_method(Other) ->
  400. Other.
  401. %% Final callback after entire input has been read
  402. read_input(Callback, {Length, _ChunkSz}, _Left) when is_function(Callback), Length =< 0 ->
  403. Callback(eof);
  404. %% Continue reading and calling back with each chunk of data
  405. read_input(Callback, {Length, ChunkSz}, Left) when is_function(Callback), is_binary(Left) ->
  406. L = recv_size(Length, ChunkSz),
  407. <<Bin:L/bytes,Rest/bits>> = Left,
  408. Rem = Length - size(Bin),
  409. NewCallback = Callback({data, Bin}),
  410. read_input(NewCallback, {Rem, ChunkSz}, Rest).
  411. %% Read either Length bytes or ChunkSz, whichever is smaller
  412. recv_size(Length, ChunkSz) when Length > 0, Length < ChunkSz ->
  413. Length;
  414. recv_size(_, ChunkSz) ->
  415. ChunkSz.