PageRenderTime 145ms CodeModel.GetById 60ms app.highlight 51ms RepoModel.GetById 30ms app.codeStats 0ms

/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
  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
 28-export([run/2]).
 29-export([
 30	 stream_process_deliver/2,
 31	 stream_process_deliver_chunk/2,
 32	 stream_process_deliver_final_chunk/2,
 33	 stream_process_end/2
 34	]).
 35
 36-include_lib("yaws_api.hrl").
 37-include_lib("ewgi.hrl").
 38
 39-define(INTERNAL_ERROR, [{status, 500}, {content, "text/plain", <<"Internal Server Error">>}]).
 40-define(BAD_REQUEST, [{status, 400}, {content, "text/plain", <<"Bad Request">>}]).
 41
 42%%====================================================================
 43%% ewgi_server callbacks
 44%%====================================================================
 45run(Appl, Arg) ->
 46    try parse_arg(Arg) of
 47        Req when ?IS_EWGI_REQUEST(Req) ->
 48            Ctx0 = ewgi_api:context(Req, ewgi_api:empty_response()),
 49            try Appl(Ctx0) of
 50                Ctx when ?IS_EWGI_CONTEXT(Ctx) ->
 51                    handle_result(?INSPECT_EWGI_RESPONSE(Ctx), Arg#arg.clisock)
 52            catch
 53                _:Reason ->
 54                    error_logger:error_report(Reason),
 55                    ?INTERNAL_ERROR
 56            end
 57    catch
 58        _:Reason ->
 59            error_logger:error_report(Reason),
 60            ?BAD_REQUEST
 61    end.
 62
 63handle_result(Ctx, Socket) ->
 64    case ewgi_api:response_message_body(Ctx) of
 65	{push_stream, GeneratorPid, Timeout} when is_pid(GeneratorPid) ->
 66	    GeneratorPid ! {push_stream_init, ?MODULE, self(), Socket},
 67	    receive
 68		{push_stream_init, GeneratorPid, Code, Headers, _TransferEncoding} ->
 69		    ContentType = get_content_type(Headers),
 70		    Acc = get_yaws_headers(Headers),
 71		    [{status, Code},
 72		     {allheaders, Acc},
 73		     {streamcontent_from_pid, ContentType, GeneratorPid}]
 74	    after Timeout ->
 75		    [{status, 504}, {content, "text/plain", <<"Gateway Timeout">>}]
 76	    end;
 77	Body ->
 78	    {Code, _} = ewgi_api:response_status(Ctx),
 79	    H = ewgi_api:response_headers(Ctx),
 80	    ContentType = get_content_type(H),
 81	    Acc = get_yaws_headers(H),
 82	    case Body of
 83		Generator when is_function(Generator, 0) ->
 84		    YawsPid = self(),
 85		    spawn(fun() -> handle_stream(Generator, YawsPid) end),
 86		    [{status, Code},
 87		     {allheaders, Acc},
 88		     {streamcontent_with_timeout, ContentType, <<>>, infinity}];
 89		_ ->
 90		    [{status, Code},
 91		     {allheaders, Acc},
 92		     {content, ContentType, Body}]
 93	    end
 94    end.
 95
 96get_yaws_headers(H) ->
 97    lists:foldl(fun({K0, V}, Acc) ->
 98                        case string:to_lower(K0) of
 99                            "content-type" ->
100                                Acc;
101                            K ->
102                                [{header, [K ++ ": ", V]}|Acc]
103                        end
104                end, [], H).
105
106get_content_type(H) ->
107    lists:foldl(fun({K, V}, Def) ->
108                        case string:to_lower(K) of
109                            "content-type" ->
110                                V;
111                            _ ->
112                                Def
113                        end
114                end, "text/plain", H).
115
116handle_stream(Generator, YawsPid) when is_function(Generator, 0) ->
117    case (catch Generator()) of
118        {H, T} when is_function(T, 0) ->
119	    case H of
120		<<>> -> ok;
121		[] -> ok;
122		_ ->
123		    yaws_api:stream_chunk_deliver(YawsPid, [H])
124	    end,
125	    handle_stream(T, YawsPid);
126	{} ->
127	    yaws_api:stream_chunk_end(YawsPid);
128	Error ->
129	    error_logger:error_report(io_lib:format("Unexpected stream ouput (~p): ~p~n", [Generator, Error])),
130	    yaws_api:stream_chunk_end(YawsPid)
131    end;
132handle_stream(Generator, YawsPid) ->
133    error_logger:error_report(io_lib:format("Invalid stream generator: ~p~n", [Generator])),
134    yaws_api:stream_chunk_end(YawsPid).
135
136%%--------------------------------------------------------------------
137%% Push Streams API
138%%--------------------------------------------------------------------
139stream_process_deliver(Socket, IoList) ->
140	yaws_api:stream_process_deliver(Socket, IoList).
141
142stream_process_deliver_chunk(Socket, IoList) ->
143	yaws_api:stream_process_deliver_chunk(Socket, IoList).
144
145stream_process_deliver_final_chunk(Socket, IoList) ->
146	yaws_api:stream_process_deliver_final_chunk(Socket, IoList).
147
148stream_process_end(Socket, ServerPid) ->
149    yaws_api:stream_process_end(Socket, ServerPid).
150
151%%--------------------------------------------------------------------
152%%% Internal functions
153%%--------------------------------------------------------------------
154parse_arg(Req) ->
155    ewgi_api:server_request_foldl(Req, fun parse_element/2, fun parse_ewgi_element/2, fun parse_http_header_element/2).
156
157parse_element(auth_type, #arg{headers=#headers{authorization=V}}) ->
158    V;
159
160parse_element(content_length, #arg{headers=#headers{content_length=V}}) ->
161	case V of
162		undefined -> 0;
163		_ -> list_to_integer(V)
164	end;
165
166parse_element(content_type, #arg{headers=#headers{content_type=V}}) ->
167    V;
168
169parse_element(gateway_interface, _) ->
170    "EWGI/1.0";
171
172parse_element(path_info, #arg{pathinfo=undefined}) ->
173    "/";
174parse_element(path_info, #arg{pathinfo=V}) ->
175    V;
176
177parse_element(path_translated, #arg{fullpath=V}) ->
178    V;
179
180parse_element(query_string, #arg{querydata=V}) ->
181    V;
182
183parse_element(remote_addr, #arg{client_ip_port=Addr}) ->
184    {{A,B,C,D}, _Port} = Addr,
185    integer_to_list(A) ++ "."
186        ++ integer_to_list(B) ++ "."
187        ++ integer_to_list(C) ++ "."
188        ++ integer_to_list(D);
189
190parse_element(remote_host, _Req) ->
191    undefined;
192
193parse_element(remote_ident, _Req) ->
194    undefined;
195
196parse_element(remote_user, _Req) ->
197    undefined;
198
199parse_element(request_method, #arg{req=#http_request{method=V}}) ->
200    V;
201
202parse_element(script_name, #arg{prepath=V}) ->
203    V;
204
205parse_element(server_name, #arg{headers=#headers{host=HostPort}}) ->
206    hd(string:tokens(HostPort, ":"));
207
208parse_element(server_port, #arg{headers=#headers{host=HostPort0}}) ->
209    HostPort = string:tokens(HostPort0, ":"),
210    case length(HostPort) of
211        2 -> [_H, P] = HostPort, P;
212        _ -> undefined
213    end;
214
215parse_element(server_protocol, #arg{req=#http_request{version={Maj, Min}}}) ->
216    "HTTP/" ++ integer_to_list(Maj) ++ "." ++ integer_to_list(Min);
217
218parse_element(server_software, _) ->
219    "Yaws";
220
221parse_element(_, _) ->
222    undefined.
223
224%% TODO: Handle Expect: 100-Continue
225parse_ewgi_element(read_input, #arg{clidata=Buf}) ->
226    F = fun(Callback, Length) when is_integer(Length) -> % No chunk size specified, so use default
227                read_input(Callback, {Length, ?DEFAULT_CHUNKSIZE}, Buf);
228           (Callback, {Length, ChunkSz}) ->
229                read_input(Callback, {Length, ChunkSz}, Buf)
230        end,
231    F;
232
233parse_ewgi_element(write_error, Req) ->
234    F = fun(Msg) ->
235                error_logger:error_report([{message, Msg}, {request, Req}])
236        end,
237    F;
238
239%% https?
240parse_ewgi_element(url_scheme, _) ->
241    "http";
242
243parse_ewgi_element(version, _) ->
244    {1, 0};
245
246parse_ewgi_element(data, _) ->
247    gb_trees:empty();
248
249parse_ewgi_element(_, _) ->
250    undefined.
251
252parse_http_header_element(http_accept, #arg{headers=#headers{accept=V}}) ->
253    V;
254
255parse_http_header_element(http_cookie, #arg{headers=#headers{cookie=[V]}}) ->
256    V;
257parse_http_header_element(http_cookie, #arg{headers=#headers{cookie=_}}) ->
258    undefined;
259
260parse_http_header_element(http_host, #arg{headers=#headers{host=V}}) ->
261    V;
262
263parse_http_header_element(http_if_modified_since, #arg{headers=#headers{if_modified_since=V}}) ->
264    V;
265
266parse_http_header_element(http_user_agent, #arg{headers=#headers{user_agent=V}}) ->
267    V;
268
269parse_http_header_element(http_x_http_method_override, #arg{headers=#headers{other=L}}) ->
270    lists:foldl(fun({http_header,_,K0,_,V}, undefined) ->
271                        K = case is_atom(K0) of
272                                true ->
273                                    atom_to_list(K0);
274                                false ->
275                                    K0
276                            end,
277                        case string:to_lower(K) of
278                            "x-http-method-override" ->
279                                V;
280                            _ ->
281                                undefined
282                        end;
283                   (_, Acc) ->
284                        Acc
285                end, undefined, L);
286
287parse_http_header_element(other, #arg{headers=#headers{other=HOther}=H}) ->
288    Dict0 = lists:foldl(fun(El, DAcc) ->
289                                K0 = element(3, El),
290                                {K, V} = ewgi_api:normalize_header({K0, element(5, El)}),
291                                Ex = case gb_trees:lookup(K, DAcc) of
292                                         {value, L} ->
293                                             L;
294                                         none ->
295                                             []
296                                     end,
297                                gb_trees:insert(K, lists:reverse([{K0, V}|lists:reverse(Ex)]), DAcc)
298                        end, gb_trees:empty(), HOther),
299    FixedAuth =
300	case H#headers.authorization of
301	    undefined ->
302		undefined;
303	    {_Username, _Password, Auth} ->
304		Auth
305	end,
306    Headers = [{"Connection", H#headers.connection},
307	       {"If-Match", H#headers.if_match},
308	       {"If-None-match", H#headers.if_none_match},
309	       {"If-Range", H#headers.if_range},
310	       {"If-Unmodified-Since", H#headers.if_unmodified_since},
311	       {"Range", H#headers.range},
312	       {"Referer", H#headers.referer},
313	       {"Accept-Ranges", H#headers.accept_ranges},
314	       {"Keep-Alive", H#headers.keep_alive},
315	       {"Location", H#headers.location},
316	       {"Content-Length", H#headers.content_length},
317	       {"Content-Type", H#headers.content_type},
318	       {"Content-Encoding", H#headers.content_encoding},
319	       {"Authorization", FixedAuth},
320	       {"Transfer-Encoding", H#headers.transfer_encoding}],
321    DefinedHeaders = [{K,V} || {K,V} <- Headers, V /= undefined],
322    lists:foldl(fun({K0, V0}, DAcc) ->
323			{K, V} = ewgi_api:normalize_header({K0, V0}),
324                        gb_trees:insert(K, [{K0, V}], DAcc)
325                end, Dict0, DefinedHeaders).
326
327%% Final callback after entire input has been read
328read_input(Callback, {Length, _ChunkSz}, _Left) when is_function(Callback), Length =< 0 ->
329    Callback(eof);
330
331%% Continue reading and calling back with each chunk of data
332read_input(Callback, {Length, ChunkSz}, Left) when is_function(Callback) ->
333    L = recv_size(Length, ChunkSz),
334    <<Bin:L/bytes,Rest/bits>> = Left,
335    Rem = Length - size(Bin),
336    NewCallback = Callback({data, Bin}),
337    read_input(NewCallback, {Rem, ChunkSz}, Rest).
338
339%% Read either Length bytes or ChunkSz, whichever is smaller
340recv_size(Length, ChunkSz) when Length > 0, Length < ChunkSz ->
341    Length;
342recv_size(_, ChunkSz) ->
343    ChunkSz.
344