PageRenderTime 34ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/src/server_gateways/ewgi_yaws.erl

http://github.com/skarab/ewgi
Erlang | 344 lines | 257 code | 48 blank | 39 comment | 1 complexity | a09ee67b87376b964786a7a6999a919a MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. %%%-------------------------------------------------------------------
  2. %%% File : ewgi_yaws.erl
  3. %%% Authors : Filippo Pacini <filippo.pacini@gmail.com>
  4. %%% Hunter Morris <huntermorris@gmail.com>
  5. %%% License :
  6. %%% The contents of this file are subject to the Mozilla Public
  7. %%% License Version 1.1 (the "License"); you may not use this file
  8. %%% except in compliance with the License. You may obtain a copy of
  9. %%% the License at http://www.mozilla.org/MPL/
  10. %%%
  11. %%% Software distributed under the License is distributed on an "AS IS"
  12. %%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  13. %%% the License for the specific language governing rights and
  14. %%% limitations under the License.
  15. %%% The Initial Developer of the Original Code is S.G. Consulting
  16. %%% srl. Portions created by S.G. Consulting s.r.l. are Copyright (C)
  17. %%% 2007 S.G. Consulting srl. All Rights Reserved.
  18. %%%
  19. %%% @doc
  20. %%% <p>Reference implementation of a Yaws EWGI server gateway.</p>
  21. %%%
  22. %%% @end
  23. %%%
  24. %%% Created : 12 Oct 2007 by Filippo Pacini <filippo.pacini@gmail.com>
  25. %%%-------------------------------------------------------------------
  26. -module(ewgi_yaws).
  27. -export([run/2]).
  28. -export([
  29. stream_process_deliver/2,
  30. stream_process_deliver_chunk/2,
  31. stream_process_deliver_final_chunk/2,
  32. stream_process_end/2
  33. ]).
  34. -include_lib("yaws_api.hrl").
  35. -include_lib("ewgi.hrl").
  36. -define(INTERNAL_ERROR, [{status, 500}, {content, "text/plain", <<"Internal Server Error">>}]).
  37. -define(BAD_REQUEST, [{status, 400}, {content, "text/plain", <<"Bad Request">>}]).
  38. %%====================================================================
  39. %% ewgi_server callbacks
  40. %%====================================================================
  41. run(Appl, Arg) ->
  42. try parse_arg(Arg) of
  43. Req when ?IS_EWGI_REQUEST(Req) ->
  44. Ctx0 = ewgi_api:context(Req, ewgi_api:empty_response()),
  45. try Appl(Ctx0) of
  46. Ctx when ?IS_EWGI_CONTEXT(Ctx) ->
  47. handle_result(?INSPECT_EWGI_RESPONSE(Ctx), Arg#arg.clisock)
  48. catch
  49. _:Reason ->
  50. error_logger:error_report(Reason),
  51. ?INTERNAL_ERROR
  52. end
  53. catch
  54. _:Reason ->
  55. error_logger:error_report(Reason),
  56. ?BAD_REQUEST
  57. end.
  58. handle_result(Ctx, Socket) ->
  59. case ewgi_api:response_message_body(Ctx) of
  60. {push_stream, GeneratorPid, Timeout} when is_pid(GeneratorPid) ->
  61. GeneratorPid ! {push_stream_init, ?MODULE, self(), Socket},
  62. receive
  63. {push_stream_init, GeneratorPid, Code, Headers, _TransferEncoding} ->
  64. ContentType = get_content_type(Headers),
  65. Acc = get_yaws_headers(Headers),
  66. [{status, Code},
  67. {allheaders, Acc},
  68. {streamcontent_from_pid, ContentType, GeneratorPid}]
  69. after Timeout ->
  70. [{status, 504}, {content, "text/plain", <<"Gateway Timeout">>}]
  71. end;
  72. Body ->
  73. {Code, _} = ewgi_api:response_status(Ctx),
  74. H = ewgi_api:response_headers(Ctx),
  75. ContentType = get_content_type(H),
  76. Acc = get_yaws_headers(H),
  77. case Body of
  78. Generator when is_function(Generator, 0) ->
  79. YawsPid = self(),
  80. spawn(fun() -> handle_stream(Generator, YawsPid) end),
  81. [{status, Code},
  82. {allheaders, Acc},
  83. {streamcontent_with_timeout, ContentType, <<>>, infinity}];
  84. _ ->
  85. [{status, Code},
  86. {allheaders, Acc},
  87. {content, ContentType, Body}]
  88. end
  89. end.
  90. get_yaws_headers(H) ->
  91. lists:foldl(fun({K0, V}, Acc) ->
  92. case string:to_lower(K0) of
  93. "content-type" ->
  94. Acc;
  95. K ->
  96. [{header, [K ++ ": ", V]}|Acc]
  97. end
  98. end, [], H).
  99. get_content_type(H) ->
  100. lists:foldl(fun({K, V}, Def) ->
  101. case string:to_lower(K) of
  102. "content-type" ->
  103. V;
  104. _ ->
  105. Def
  106. end
  107. end, "text/plain", H).
  108. handle_stream(Generator, YawsPid) when is_function(Generator, 0) ->
  109. case (catch Generator()) of
  110. {H, T} when is_function(T, 0) ->
  111. case H of
  112. <<>> -> ok;
  113. [] -> ok;
  114. _ ->
  115. yaws_api:stream_chunk_deliver(YawsPid, [H])
  116. end,
  117. handle_stream(T, YawsPid);
  118. {} ->
  119. yaws_api:stream_chunk_end(YawsPid);
  120. Error ->
  121. error_logger:error_report(io_lib:format("Unexpected stream ouput (~p): ~p~n", [Generator, Error])),
  122. yaws_api:stream_chunk_end(YawsPid)
  123. end;
  124. handle_stream(Generator, YawsPid) ->
  125. error_logger:error_report(io_lib:format("Invalid stream generator: ~p~n", [Generator])),
  126. yaws_api:stream_chunk_end(YawsPid).
  127. %%--------------------------------------------------------------------
  128. %% Push Streams API
  129. %%--------------------------------------------------------------------
  130. stream_process_deliver(Socket, IoList) ->
  131. yaws_api:stream_process_deliver(Socket, IoList).
  132. stream_process_deliver_chunk(Socket, IoList) ->
  133. yaws_api:stream_process_deliver_chunk(Socket, IoList).
  134. stream_process_deliver_final_chunk(Socket, IoList) ->
  135. yaws_api:stream_process_deliver_final_chunk(Socket, IoList).
  136. stream_process_end(Socket, ServerPid) ->
  137. yaws_api:stream_process_end(Socket, ServerPid).
  138. %%--------------------------------------------------------------------
  139. %%% Internal functions
  140. %%--------------------------------------------------------------------
  141. parse_arg(Req) ->
  142. ewgi_api:server_request_foldl(Req, fun parse_element/2, fun parse_ewgi_element/2, fun parse_http_header_element/2).
  143. parse_element(auth_type, #arg{headers=#headers{authorization=V}}) ->
  144. V;
  145. parse_element(content_length, #arg{headers=#headers{content_length=V}}) ->
  146. case V of
  147. undefined -> 0;
  148. _ -> list_to_integer(V)
  149. end;
  150. parse_element(content_type, #arg{headers=#headers{content_type=V}}) ->
  151. V;
  152. parse_element(gateway_interface, _) ->
  153. "EWGI/1.0";
  154. parse_element(path_info, #arg{pathinfo=undefined}) ->
  155. "/";
  156. parse_element(path_info, #arg{pathinfo=V}) ->
  157. V;
  158. parse_element(path_translated, #arg{fullpath=V}) ->
  159. V;
  160. parse_element(query_string, #arg{querydata=V}) ->
  161. V;
  162. parse_element(remote_addr, #arg{client_ip_port=Addr}) ->
  163. {{A,B,C,D}, _Port} = Addr,
  164. integer_to_list(A) ++ "."
  165. ++ integer_to_list(B) ++ "."
  166. ++ integer_to_list(C) ++ "."
  167. ++ integer_to_list(D);
  168. parse_element(remote_host, _Req) ->
  169. undefined;
  170. parse_element(remote_ident, _Req) ->
  171. undefined;
  172. parse_element(remote_user, _Req) ->
  173. undefined;
  174. parse_element(request_method, #arg{req=#http_request{method=V}}) ->
  175. V;
  176. parse_element(script_name, #arg{prepath=V}) ->
  177. V;
  178. parse_element(server_name, #arg{headers=#headers{host=HostPort}}) ->
  179. hd(string:tokens(HostPort, ":"));
  180. parse_element(server_port, #arg{headers=#headers{host=HostPort0}}) ->
  181. HostPort = string:tokens(HostPort0, ":"),
  182. case length(HostPort) of
  183. 2 -> [_H, P] = HostPort, P;
  184. _ -> undefined
  185. end;
  186. parse_element(server_protocol, #arg{req=#http_request{version={Maj, Min}}}) ->
  187. "HTTP/" ++ integer_to_list(Maj) ++ "." ++ integer_to_list(Min);
  188. parse_element(server_software, _) ->
  189. "Yaws";
  190. parse_element(_, _) ->
  191. undefined.
  192. %% TODO: Handle Expect: 100-Continue
  193. parse_ewgi_element(read_input, #arg{clidata=Buf}) ->
  194. F = fun(Callback, Length) when is_integer(Length) -> % No chunk size specified, so use default
  195. read_input(Callback, {Length, ?DEFAULT_CHUNKSIZE}, Buf);
  196. (Callback, {Length, ChunkSz}) ->
  197. read_input(Callback, {Length, ChunkSz}, Buf)
  198. end,
  199. F;
  200. parse_ewgi_element(write_error, Req) ->
  201. F = fun(Msg) ->
  202. error_logger:error_report([{message, Msg}, {request, Req}])
  203. end,
  204. F;
  205. %% https?
  206. parse_ewgi_element(url_scheme, _) ->
  207. "http";
  208. parse_ewgi_element(version, _) ->
  209. {1, 0};
  210. parse_ewgi_element(data, _) ->
  211. gb_trees:empty();
  212. parse_ewgi_element(_, _) ->
  213. undefined.
  214. parse_http_header_element(http_accept, #arg{headers=#headers{accept=V}}) ->
  215. V;
  216. parse_http_header_element(http_cookie, #arg{headers=#headers{cookie=[V]}}) ->
  217. V;
  218. parse_http_header_element(http_cookie, #arg{headers=#headers{cookie=_}}) ->
  219. undefined;
  220. parse_http_header_element(http_host, #arg{headers=#headers{host=V}}) ->
  221. V;
  222. parse_http_header_element(http_if_modified_since, #arg{headers=#headers{if_modified_since=V}}) ->
  223. V;
  224. parse_http_header_element(http_user_agent, #arg{headers=#headers{user_agent=V}}) ->
  225. V;
  226. parse_http_header_element(http_x_http_method_override, #arg{headers=#headers{other=L}}) ->
  227. lists:foldl(fun({http_header,_,K0,_,V}, undefined) ->
  228. K = case is_atom(K0) of
  229. true ->
  230. atom_to_list(K0);
  231. false ->
  232. K0
  233. end,
  234. case string:to_lower(K) of
  235. "x-http-method-override" ->
  236. V;
  237. _ ->
  238. undefined
  239. end;
  240. (_, Acc) ->
  241. Acc
  242. end, undefined, L);
  243. parse_http_header_element(other, #arg{headers=#headers{other=HOther}=H}) ->
  244. Dict0 = lists:foldl(fun(El, DAcc) ->
  245. K0 = element(3, El),
  246. {K, V} = ewgi_api:normalize_header({K0, element(5, El)}),
  247. Ex = case gb_trees:lookup(K, DAcc) of
  248. {value, L} ->
  249. L;
  250. none ->
  251. []
  252. end,
  253. gb_trees:insert(K, lists:reverse([{K0, V}|lists:reverse(Ex)]), DAcc)
  254. end, gb_trees:empty(), HOther),
  255. FixedAuth =
  256. case H#headers.authorization of
  257. undefined ->
  258. undefined;
  259. {_Username, _Password, Auth} ->
  260. Auth
  261. end,
  262. Headers = [{"Connection", H#headers.connection},
  263. {"If-Match", H#headers.if_match},
  264. {"If-None-match", H#headers.if_none_match},
  265. {"If-Range", H#headers.if_range},
  266. {"If-Unmodified-Since", H#headers.if_unmodified_since},
  267. {"Range", H#headers.range},
  268. {"Referer", H#headers.referer},
  269. {"Accept-Ranges", H#headers.accept_ranges},
  270. {"Keep-Alive", H#headers.keep_alive},
  271. {"Location", H#headers.location},
  272. {"Content-Length", H#headers.content_length},
  273. {"Content-Type", H#headers.content_type},
  274. {"Content-Encoding", H#headers.content_encoding},
  275. {"Authorization", FixedAuth},
  276. {"Transfer-Encoding", H#headers.transfer_encoding}],
  277. DefinedHeaders = [{K,V} || {K,V} <- Headers, V /= undefined],
  278. lists:foldl(fun({K0, V0}, DAcc) ->
  279. {K, V} = ewgi_api:normalize_header({K0, V0}),
  280. gb_trees:insert(K, [{K0, V}], DAcc)
  281. end, Dict0, DefinedHeaders).
  282. %% Final callback after entire input has been read
  283. read_input(Callback, {Length, _ChunkSz}, _Left) when is_function(Callback), Length =< 0 ->
  284. Callback(eof);
  285. %% Continue reading and calling back with each chunk of data
  286. read_input(Callback, {Length, ChunkSz}, Left) when is_function(Callback) ->
  287. L = recv_size(Length, ChunkSz),
  288. <<Bin:L/bytes,Rest/bits>> = Left,
  289. Rem = Length - size(Bin),
  290. NewCallback = Callback({data, Bin}),
  291. read_input(NewCallback, {Rem, ChunkSz}, Rest).
  292. %% Read either Length bytes or ChunkSz, whichever is smaller
  293. recv_size(Length, ChunkSz) when Length > 0, Length < ChunkSz ->
  294. Length;
  295. recv_size(_, ChunkSz) ->
  296. ChunkSz.