Skip to content
Browse files

Merge branch 'master' of git://github.com/davide/ewgi

  • Loading branch information...
2 parents 827106e + df5e128 commit 95741fd44d57c1052799402d1663ad6e479ceefd @filippo filippo committed
Showing with 274 additions and 178 deletions.
  1. +2 −1 Emakefile
  2. +4 −0 Makefile
  3. +1 −1 README.rst
  4. +17 −0 include/ewgi.hrl
  5. +2 −0 src/ewgi.app
  6. +9 −4 src/ewgi_api.erl
  7. +15 −15 src/ewgi_application.erl
  8. +24 −28 src/ewgi_inets.erl
  9. +23 −13 src/ewgi_mochiweb.erl
  10. +25 −83 src/ewgi_test.erl
  11. +90 −0 src/ewgi_testapp.erl
  12. +62 −33 src/ewgi_yaws.erl
View
3 Emakefile
@@ -3,5 +3,6 @@
{["src/*"],
[{i, "include"},
{outdir, "ebin"},
- debug_info]
+ debug_info,
+ {d, debug}]
}.
View
4 Makefile
@@ -27,3 +27,7 @@ lib:
dialyzer: erl
@dialyzer -c ebin
+
+test: erl
+ @$(ERL) -pa $(EBIN_DIRS) -pa ebin -noinput +B \
+ -eval 'case lists:member(error, ewgi_test:test()) of true -> halt(1); _ -> halt(0) end.'
View
2 README.rst
@@ -81,7 +81,7 @@ The even shorter inets example
$ git clone git://github.com/skarab/ewgi.git && (cd ewgi/ && make \
&& erl -pa ebin/ -eval 'application:start(inets)' \
- -eval 'application:set_env(ewgi, app_module, ewgi_test)' \
+ -eval 'application:set_env(ewgi, app_module, ewgi_testapp)' \
-eval 'application:set_env(ewgi, app_function, testapp)' \
-eval 'inets:start(httpd, [{port, 8889},
{server_name, "ewgi"},
View
17 include/ewgi.hrl
@@ -218,4 +218,21 @@
%% @type ewgi_app() = function()
-type ewgi_app() :: fun((ewgi_context()) -> ewgi_context()).
+-ifndef(debug).
+-define(INSPECT_EWGI_RESPONSE(Ctx), Ctx).
+-else.
+-define(INSPECT_EWGI_RESPONSE(Ctx),
+ begin
+ error_logger:info_msg("Inpecting the final ewgi_response()...~n"
+ "Status: ~p~n"
+ "Headers: ~p~n"
+ "Body: ~p~n",
+ [ewgi_api:response_status(Ctx),
+ ewgi_api:response_headers(Ctx),
+ ewgi_api:response_message_body(Ctx)]),
+ Ctx
+ end
+ ).
+-endif.
+
-endif.
View
2 src/ewgi.app
@@ -5,7 +5,9 @@
,{env, []}
,{modules, [ewgi_api
,ewgi_application
+ ,ewgi_inets
,ewgi_mochiweb
+ ,ewgi_testapp
,ewgi_yaws
]}
]}.
View
13 src/ewgi_api.erl
@@ -232,13 +232,18 @@ get_header1("x-http-method-override", Ctx) when ?IS_EWGI_CONTEXT(Ctx) ->
?GET_HTTP_X_HTTP_METHOD_OVERRIDE(headers(Ctx));
get_header1(Hdr, Ctx) when ?IS_EWGI_CONTEXT(Ctx) ->
case gb_trees:lookup(Hdr, ?GET_HTTP_OTHER(headers(Ctx))) of
- {value, V} when is_list(V) ->
- {_, V1} = lists:unzip(V),
- string:join(V1, ", ");
+ {value, V} ->
+ unzip_header_value(V);
none ->
undefined
end.
+unzip_header_value([{_,_}|_]=V) ->
+ {_, V1} = lists:unzip(V),
+ string:join(V1, ", ");
+unzip_header_value(V) ->
+ V.
+
insert_header(K0, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) ->
K = string:to_lower(K0),
insert_header1(K, K0, V, Ctx).
@@ -350,7 +355,7 @@ server_software(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) ->
get_all_headers(Ctx) when ?IS_EWGI_CONTEXT(Ctx) ->
H = headers(Ctx),
Other = gb_trees:to_list(?GET_HTTP_OTHER(H)),
- Acc = [{K, string:join(V, ", ")} || {K, {_, V}} <- [{K0, lists:unzip(V0)} || {K0, V0} <- Other, is_list(V0)]],
+ Acc = [{K, unzip_header_value(V)} || {K, V} <- Other],
L = [{"accept", get_header_value("accept", Ctx)},
{"cookie", get_header_value("cookie", Ctx)},
{"host", get_header_value("host", Ctx)},
View
30 src/ewgi_application.erl
@@ -29,7 +29,7 @@
-export([run/2]).
%% Useful middleware
--export([module_mw/1, rpc_mw/3, mfa_mw/2]).
+-export([module_mw/2, rpc_mw/4, mfa_mw/3]).
-include_lib("ewgi.hrl").
@@ -39,29 +39,29 @@
run(Application, Context) when is_function(Application, 1) ->
Application(Context).
-%% @spec module_mw(Module::term()) -> ewgi_app()
-%% @doc Produces a middleware application which calls the handle/1 function exported by Module.
--spec module_mw(any()) -> ewgi_app().
-module_mw(Module) ->
+%% @spec module_mw(Module::term(), Args:any()) -> ewgi_app()
+%% @doc Produces a middleware application which calls the run/1 function exported by Module.
+-spec module_mw(any(), any()) -> ewgi_app().
+module_mw(Module, Args) ->
F = fun(Context) ->
- Module:handle(Context)
+ Module:run(Context, Args)
end,
F.
-%% @spec rpc_mw(Node::atom(), Module::atom(), Fun::atom()) -> ewgi_app()
+%% @spec rpc_mw(Node::atom(), Module::atom(), Fun::atom(), Args:any()) -> ewgi_app()
%% @doc Produces a middleware application which calls the remote function Module:Fun on Node.
--spec rpc_mw(atom(), atom(), atom()) -> ewgi_app().
-rpc_mw(Node, Module, Fun) ->
+-spec rpc_mw(atom(), atom(), atom(), any()) -> ewgi_app().
+rpc_mw(Node, Module, Fun, Args) ->
F = fun(Context) ->
- rpc:call(Node, Module, Fun, [Context])
+ rpc:call(Node, Module, Fun, [Context, Args])
end,
F.
-%% @spec mfa_mw(Module::atom(), Fun::atom()) -> ewgi_app()
-%% @doc Produces a middleware application which calls the function Module:Fun(Args)
--spec mfa_mw(atom(), atom()) -> ewgi_app().
-mfa_mw(Module, Fun) when is_atom(Module), is_atom(Fun) ->
+%% @spec mfa_mw(Module::atom(), Fun::atom(), Args:any()) -> ewgi_app()
+%% @doc Produces a middleware application which calls the function Module:Fun(Context, Args)
+-spec mfa_mw(atom(), atom(), any()) -> ewgi_app().
+mfa_mw(Module, Fun, Args) when is_atom(Module), is_atom(Fun) ->
F = fun(Context) ->
- apply(Module, Fun, [Context])
+ apply(Module, Fun, [Context, Args])
end,
F.
View
52 src/ewgi_inets.erl
@@ -36,7 +36,7 @@ do(A) ->
not_found ->
{proceed, [{response, {404, []}}]};
Ctx when ?IS_EWGI_CONTEXT(Ctx) ->
- handle_result(A, Ctx)
+ handle_result(A, ?INSPECT_EWGI_RESPONSE(Ctx))
catch
_:Reason ->
error_logger:error_report(io_lib:format("Responding with 500 INTERNAL SERVER ERROR.~nReason: ~p~nStack: ~p~n", [Reason, erlang:get_stacktrace()])),
@@ -244,36 +244,30 @@ handle_result_wrap_stream(#mod{http_version=Ver}, ChunkedAllowed, Code, Headers,
Length = {content_length, integer_to_list(erlang:iolist_size(Body))},
{proceed, [{response, {response, [{code, Code}, Length] ++ Headers, Body}}]};
handle_result_wrap_stream(A, true, Code, Headers, Body) ->
- process_flag(trap_exit, true),
- Self = self(),
ExtraHeaders = httpd_response:cache_headers(A),
httpd_response:send_header(A, Code, ExtraHeaders ++ [{transfer_encoding, "chunked"}|Headers]),
- %% Spawn worker process for chunks to allow for timeouts and avoid socket deadlock
- Pid = spawn_link(fun() -> send_body(A, Self, Body) end),
- Size = handle_stream_body(A, Pid, 0),
- process_flag(trap_exit, false),
- {proceed, [{response, {already_sent, Code, Size}}]}.
-
-send_body(A, Self, Body) ->
- case Body() of
- {H, T} ->
- httpd_response:send_chunk(A, [H], false),
- Self ! {ok, erlang:iolist_size([H])},
- send_body(A, Self, T);
+ handle_stream(A, Code, Body, 0).
+
+handle_stream(A, Code, Generator, Size) when is_function(Generator, 0) ->
+ case (catch Generator()) of
+ {H, T} when is_function(T, 0) ->
+ case H of
+ <<>> -> ok;
+ [] -> ok;
+ _ ->
+ httpd_response:send_chunk(A, [H], false)
+ end,
+ handle_stream(A, Code, T, Size + erlang:iolist_size([H]));
{} ->
httpd_response:send_final_chunk(A, false),
- exit(normal)
- end.
-
-handle_stream_body(A, Pid, Size) ->
- receive
- {ok, Len} ->
- handle_stream_body(A, Pid, Size + Len);
- {'EXIT', Pid, normal} ->
- Size;
- {'EXIT', Pid, Reason} ->
- exit({chunking_process_died, Pid, Reason})
- end.
+ {proceed, [{response, {already_sent, Code, Size}}]};
+ Error ->
+ error_logger:error_report(io_lib:format("Unexpected stream ouput (~p): ~p~n", [Generator, Error])),
+ httpd_response:send_final_chunk(A, false)
+ end;
+handle_stream(A, _Code, Generator, _Size) ->
+ error_logger:error_report(io_lib:format("Invalid stream generator: ~p~n", [Generator])),
+ httpd_response:send_final_chunk(A, false).
stream_to_list(S) when is_function(S, 0) ->
case S() of
@@ -322,7 +316,9 @@ fold_header({"trailer", V}, Acc) ->
fold_header({"transfer-encoding", V}, Acc) ->
[{transfer_encoding, V}|Acc];
fold_header({"set-cookie", V}, Acc) ->
- [{set_cookie, V}|Acc];
+ [{"set-cookie", V}|Acc];
+fold_header({"www-authenticate", V}, Acc) ->
+ [{"www-authenticate", V}|Acc];
fold_header(_, Acc) ->
%% Ignore unrecognised headers
Acc.
View
36 src/ewgi_mochiweb.erl
@@ -42,7 +42,7 @@ run(MochiReq) ->
not_found ->
MochiReq:not_found();
Ctx when ?IS_EWGI_CONTEXT(Ctx) ->
- handle_result(Ctx, MochiReq)
+ handle_result(?INSPECT_EWGI_RESPONSE(Ctx), MochiReq)
catch
_:Reason ->
error_logger:error_report(Reason),
@@ -63,22 +63,32 @@ handle_result(Ctx, Req) ->
handle_result1(Code, Headers, F, Req) when is_function(F, 0) ->
MochiResp = Req:respond({Code, Headers, chunked}),
- handle_stream_result(MochiResp, F());
+ %handle_stream_result(MochiResp, (catch F()));
+ handle_stream(MochiResp, F);
handle_result1(Code, Headers, L, Req) ->
Req:respond({Code, Headers, L}).
%% Treat a stream with chunked transfer encoding
-handle_stream_result(R, {}) ->
- R:write_chunk([]);
-handle_stream_result(R, {[], T}) when is_function(T, 0) ->
- %% Don't prematurely end the stream
- handle_stream_result(R, T());
-handle_stream_result(R, {<<>>, T}) when is_function(T, 0) ->
- %% Don't prematurely end the stream
- handle_stream_result(R, T());
-handle_stream_result(R, {H, T}) when is_function(T, 0) ->
- R:write_chunk(H),
- handle_stream_result(R, T()).
+handle_stream(R, Generator) when is_function(Generator, 0) ->
+ case (catch Generator()) of
+ {H, T} when is_function(T, 0) ->
+ %% Prevent finishing the chunked response
+ case H of
+ <<>> -> ok;
+ [] -> ok;
+ _ ->
+ R:write_chunk(H)
+ end,
+ handle_stream(R, T);
+ {} ->
+ R:write_chunk([]);
+ Error ->
+ error_logger:error_report(io_lib:format("Unexpected stream ouput (~p): ~p~n", [Generator, Error])),
+ R:write_chunk([])
+ end;
+handle_stream(R, Generator) ->
+ error_logger:error_report(io_lib:format("Invalid stream generator: ~p~n", [Generator])),
+ R:write_chunk([]).
process_application(Ctx) when is_list(Appl) ->
Path = ewgi_api:path_info(Ctx),
View
108 src/ewgi_test.erl
@@ -1,90 +1,32 @@
-%%%-------------------------------------------------------------------
-%%% File : ewgi_application.erl
-%%% Authors : Hunter Morris <huntermorris@gmail.com>
-%%% License :
-%%% The contents of this file are subject to the Mozilla Public
-%%% License Version 1.1 (the "License"); you may not use this file
-%%% except in compliance with the License. You may obtain a copy of
-%%% the License at http://www.mozilla.org/MPL/
-%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and
-%%% limitations under the License.
-%%% The Initial Developer of the Original Code is S.G. Consulting
-%%% srl. Portions created by S.G. Consulting s.r.l. are Copyright (C)
-%%% 2007 S.G. Consulting srl. All Rights Reserved.
-%%%
-%%% @doc
-%%% <p>ewgi test applications</p>
-%%%
-%%% @end
-%%%
-%%% Created : 05 July 2009 by Hunter Morris <huntermorris@gmail.com>
-%%%-------------------------------------------------------------------
-module(ewgi_test).
-%% Test EWGI applications
--export([testapp/1, testapp_chunked/1]).
+-export([test/0]).
--include_lib("ewgi.hrl").
+test() ->
+ lists:map(fun test_file/1,
+ [filename:rootname(filename:basename(F))
+ || F <- filelib:wildcard("ebin/*.beam")]).
-testapp(C) ->
- Body = io_lib:format("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\"><html><body><h1>EWGI Context</h1><h2>Request</h2>~s</body></html>", [htmlise(C)]),
- Stat = {200, "OK"},
- H = [{"content-type", "text/html"}],
- ewgi_api:response_message_body(
- Body, ewgi_api:response_headers(
- H, ewgi_api:response_status(Stat, C))).
-
-testapp_chunked(C0) ->
- C = testapp(C0),
- B = ewgi_api:response_message_body(C),
- ewgi_api:response_message_body(list_to_stream(B), C).
-
-htmlise(C) ->
- iolist_to_binary(
- ["<dl class=\"request\">",
- io_lib:format("<dt>url_scheme</dt><dd><pre>~s</pre></dd>", [ewgi_api:url_scheme(C)]),
- io_lib:format("<dt>request_method</dt><dd><pre>~s</pre></dd>", [ewgi_api:request_method(C)]),
- io_lib:format("<dt>path_info</dt><dd><pre>~s</pre></dd>", [ewgi_api:path_info(C)]),
- io_lib:format("<dt>query_string</dt><dd><pre>~s</pre></dd>", [ewgi_api:query_string(C)]),
- io_lib:format("<dt>remote_addr</dt><dd><pre>~s</pre></dd>", [ewgi_api:remote_addr(C)]),
- io_lib:format("<dt>auth_type</dt><dd><pre>~s</pre></dd>", [ewgi_api:auth_type(C)]),
- io_lib:format("<dt>content_length</dt><dd><pre>~p</pre></dd>", [ewgi_api:content_length(C)]),
- io_lib:format("<dt>content_type</dt><dd><pre>~s</pre></dd>", [ewgi_api:content_type(C)]),
- io_lib:format("<dt>gateway_interface</dt><dd><pre>~s</pre></dd>", [ewgi_api:gateway_interface(C)]),
- io_lib:format("<dt>path_translated</dt><dd><pre>~s</pre></dd>", [ewgi_api:path_translated(C)]),
- io_lib:format("<dt>remote_host</dt><dd><pre>~s</pre></dd>", [ewgi_api:remote_host(C)]),
- io_lib:format("<dt>remote_ident</dt><dd><pre>~s</pre></dd>", [ewgi_api:remote_ident(C)]),
- io_lib:format("<dt>remote_user</dt><dd><pre>~s</pre></dd>", [ewgi_api:remote_user(C)]),
- io_lib:format("<dt>remote_user_data</dt><dd><pre>~s</pre></dd>", [ewgi_api:remote_user_data(C)]),
- io_lib:format("<dt>script_name</dt><dd><pre>~s</pre></dd>", [ewgi_api:script_name(C)]),
- io_lib:format("<dt>server_name</dt><dd><pre>~s</pre></dd>", [ewgi_api:server_name(C)]),
- io_lib:format("<dt>server_port</dt><dd><pre>~s</pre></dd>", [ewgi_api:server_port(C)]),
- io_lib:format("<dt>other http headers</dt><dd>~s</dd>", [htmlise_data("http_headers", ewgi_api:get_all_headers(C))]),
- io_lib:format("<dt>server_protocol</dt><dd><pre>~s</pre></dd>", [ewgi_api:server_protocol(C)]),
- io_lib:format("<dt>server_software</dt><dd><pre>~s</pre></dd>", [ewgi_api:server_software(C)]),
- io_lib:format("<dt>ewgi version</dt><dd><pre>~p</pre></dd>", [ewgi_api:version(C)]),
- io_lib:format("<dt>ewgi extra data</dt><dd>~s</dd>", [htmlise_data("request_data", ewgi_api:get_all_data(C))]),
- "</dl>"]).
-
-htmlise_data(Name, L) when is_list(L) ->
- ["<dl class=\"", Name, "\">",
- [io_lib:format("<dt>~s</dt><dd><pre>~p</pre><dd>", [K, V]) || {K, V} <- L],
- "</dl>"];
-htmlise_data(Name, T) ->
- case gb_trees:to_list(T) of
- [] -> [];
- L -> htmlise_data(Name, L)
+test_file("ewgi_test") ->
+ skip;
+test_file(F) ->
+ case code:ensure_loaded(list_to_atom(F)) of
+ {module, M} ->
+ test_mod(M);
+ _ ->
+ skip
end.
-list_to_stream(L) when is_list(L) ->
- fun() ->
- case L of
- [H|T] ->
- {H, list_to_stream(T)};
- [] ->
- {}
- end
+test_mod(M) ->
+ case M:module_info() of
+ L when is_list(L) ->
+ Exports = proplists:get_value(exports, L, []),
+ case lists:member({test,0}, Exports) of
+ true ->
+ M:test();
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
end.
View
90 src/ewgi_testapp.erl
@@ -0,0 +1,90 @@
+%%%-------------------------------------------------------------------
+%%% File : ewgi_testapp.erl
+%%% Authors : Hunter Morris <huntermorris@gmail.com>
+%%% License :
+%%% The contents of this file are subject to the Mozilla Public
+%%% License Version 1.1 (the "License"); you may not use this file
+%%% except in compliance with the License. You may obtain a copy of
+%%% the License at http://www.mozilla.org/MPL/
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and
+%%% limitations under the License.
+%%% The Initial Developer of the Original Code is S.G. Consulting
+%%% srl. Portions created by S.G. Consulting s.r.l. are Copyright (C)
+%%% 2007 S.G. Consulting srl. All Rights Reserved.
+%%%
+%%% @doc
+%%% <p>ewgi test applications</p>
+%%%
+%%% @end
+%%%
+%%% Created : 05 July 2009 by Hunter Morris <huntermorris@gmail.com>
+%%%-------------------------------------------------------------------
+-module(ewgi_testapp).
+
+%% Test EWGI applications
+-export([testapp/1, testapp_chunked/1]).
+
+-include_lib("ewgi.hrl").
+
+testapp(C) ->
+ Body = io_lib:format("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\"><html><body><h1>EWGI Context</h1><h2>Request</h2>~s</body></html>", [htmlise(C)]),
+ Stat = {200, "OK"},
+ H = [{"content-type", "text/html"}],
+ ewgi_api:response_message_body(
+ Body, ewgi_api:response_headers(
+ H, ewgi_api:response_status(Stat, C))).
+
+testapp_chunked(C0) ->
+ C = testapp(C0),
+ B = ewgi_api:response_message_body(C),
+ ewgi_api:response_message_body(list_to_stream(B), C).
+
+htmlise(C) ->
+ iolist_to_binary(
+ ["<dl class=\"request\">",
+ io_lib:format("<dt>url_scheme</dt><dd><pre>~s</pre></dd>", [ewgi_api:url_scheme(C)]),
+ io_lib:format("<dt>request_method</dt><dd><pre>~s</pre></dd>", [ewgi_api:request_method(C)]),
+ io_lib:format("<dt>path_info</dt><dd><pre>~s</pre></dd>", [ewgi_api:path_info(C)]),
+ io_lib:format("<dt>query_string</dt><dd><pre>~s</pre></dd>", [ewgi_api:query_string(C)]),
+ io_lib:format("<dt>remote_addr</dt><dd><pre>~s</pre></dd>", [ewgi_api:remote_addr(C)]),
+ io_lib:format("<dt>auth_type</dt><dd><pre>~s</pre></dd>", [ewgi_api:auth_type(C)]),
+ io_lib:format("<dt>content_length</dt><dd><pre>~p</pre></dd>", [ewgi_api:content_length(C)]),
+ io_lib:format("<dt>content_type</dt><dd><pre>~s</pre></dd>", [ewgi_api:content_type(C)]),
+ io_lib:format("<dt>gateway_interface</dt><dd><pre>~s</pre></dd>", [ewgi_api:gateway_interface(C)]),
+ io_lib:format("<dt>path_translated</dt><dd><pre>~s</pre></dd>", [ewgi_api:path_translated(C)]),
+ io_lib:format("<dt>remote_host</dt><dd><pre>~s</pre></dd>", [ewgi_api:remote_host(C)]),
+ io_lib:format("<dt>remote_ident</dt><dd><pre>~s</pre></dd>", [ewgi_api:remote_ident(C)]),
+ io_lib:format("<dt>remote_user</dt><dd><pre>~s</pre></dd>", [ewgi_api:remote_user(C)]),
+ io_lib:format("<dt>remote_user_data</dt><dd><pre>~s</pre></dd>", [ewgi_api:remote_user_data(C)]),
+ io_lib:format("<dt>script_name</dt><dd><pre>~s</pre></dd>", [ewgi_api:script_name(C)]),
+ io_lib:format("<dt>server_name</dt><dd><pre>~s</pre></dd>", [ewgi_api:server_name(C)]),
+ io_lib:format("<dt>server_port</dt><dd><pre>~s</pre></dd>", [ewgi_api:server_port(C)]),
+ io_lib:format("<dt>other http headers</dt><dd>~s</dd>", [htmlise_data("http_headers", ewgi_api:get_all_headers(C))]),
+ io_lib:format("<dt>server_protocol</dt><dd><pre>~s</pre></dd>", [ewgi_api:server_protocol(C)]),
+ io_lib:format("<dt>server_software</dt><dd><pre>~s</pre></dd>", [ewgi_api:server_software(C)]),
+ io_lib:format("<dt>ewgi version</dt><dd><pre>~p</pre></dd>", [ewgi_api:version(C)]),
+ io_lib:format("<dt>ewgi extra data</dt><dd>~s</dd>", [htmlise_data("request_data", ewgi_api:get_all_data(C))]),
+ "</dl>"]).
+
+htmlise_data(Name, L) when is_list(L) ->
+ ["<dl class=\"", Name, "\">",
+ [io_lib:format("<dt>~s</dt><dd><pre>~p</pre><dd>", [K, V]) || {K, V} <- L],
+ "</dl>"];
+htmlise_data(Name, T) ->
+ case gb_trees:to_list(T) of
+ [] -> [];
+ L -> htmlise_data(Name, L)
+ end.
+
+list_to_stream(L) when is_list(L) ->
+ fun() ->
+ case L of
+ [H|T] ->
+ {H, list_to_stream(T)};
+ [] ->
+ {}
+ end
+ end.
View
95 src/ewgi_yaws.erl
@@ -42,7 +42,7 @@ run(Arg) ->
Ctx0 = ewgi_api:context(Req, ewgi_api:empty_response()),
try Appl(Ctx0) of
Ctx when ?IS_EWGI_CONTEXT(Ctx) ->
- handle_result(Ctx)
+ handle_result(?INSPECT_EWGI_RESPONSE(Ctx))
catch
_:Reason ->
error_logger:error_report(Reason),
@@ -55,17 +55,18 @@ run(Arg) ->
end.
handle_result(Ctx) ->
- Body = case ewgi_api:response_message_body(Ctx) of
- F when is_function(F, 0) ->
- handle_stream_result(F);
- B ->
- B
- end,
{Code, _} = ewgi_api:response_status(Ctx),
H = ewgi_api:response_headers(Ctx),
ContentType = get_content_type(H),
Acc = get_yaws_headers(H),
- [{status, Code}, {content, ContentType, Body}|Acc].
+ case ewgi_api:response_message_body(Ctx) of
+ Generator when is_function(Generator, 0) ->
+ YawsPid = self(),
+ spawn(fun() -> handle_stream(Generator, YawsPid) end),
+ {streamcontent_with_timeout, ContentType, <<>>, infinity};
+ Body ->
+ [{status, Code}, {content, ContentType, Body}|Acc]
+ end.
get_yaws_headers(H) ->
lists:foldl(fun({K0, V}, Acc) ->
@@ -87,13 +88,26 @@ get_content_type(H) ->
end
end, "text/plain", H).
-handle_stream_result(F) when is_function(F, 0) ->
- handle_stream_result(F(), []).
+handle_stream(Generator, YawsPid) when is_function(Generator, 0) ->
+ case (catch Generator()) of
+ {H, T} when is_function(T, 0) ->
+ case H of
+ <<>> -> ok;
+ [] -> ok;
+ _ ->
+ yaws_api:stream_chunk_deliver(YawsPid, [H])
+ end,
+ handle_stream(T, YawsPid);
+ {} ->
+ yaws_api:stream_chunk_end(YawsPid);
+ Error ->
+ error_logger:error_report(io_lib:format("Unexpected stream ouput (~p): ~p~n", [Generator, Error])),
+ yaws_api:stream_chunk_end(YawsPid)
+ end;
+handle_stream(Generator, YawsPid) ->
+ error_logger:error_report(io_lib:format("Invalid stream generator: ~p~n", [Generator])),
+ yaws_api:stream_chunk_end(YawsPid).
-handle_stream_result({}, Acc) ->
- lists:reverse(Acc);
-handle_stream_result({H, T}, Acc) ->
- handle_stream_result(T(), [H|Acc]).
%%--------------------------------------------------------------------
%%% Internal functions
@@ -105,7 +119,10 @@ parse_element(auth_type, #arg{headers=#headers{authorization=V}}) ->
V;
parse_element(content_length, #arg{headers=#headers{content_length=V}}) ->
- V;
+ case V of
+ undefined -> 0;
+ _ -> list_to_integer(V)
+ end;
parse_element(content_type, #arg{headers=#headers{content_type=V}}) ->
V;
@@ -196,8 +213,10 @@ parse_ewgi_element(_, _) ->
parse_http_header_element(http_accept, #arg{headers=#headers{accept=V}}) ->
V;
-parse_http_header_element(http_cookie, #arg{headers=#headers{cookie=V}}) ->
+parse_http_header_element(http_cookie, #arg{headers=#headers{cookie=[V]}}) ->
V;
+parse_http_header_element(http_cookie, #arg{headers=#headers{cookie=_}}) ->
+ undefined;
parse_http_header_element(http_host, #arg{headers=#headers{host=V}}) ->
V;
@@ -238,23 +257,33 @@ parse_http_header_element(other, #arg{headers=#headers{other=HOther}=H}) ->
end,
gb_trees:insert(K, lists:reverse([{K0, V}|lists:reverse(Ex)]), DAcc)
end, gb_trees:empty(), HOther),
- lists:foldl(fun({K, V}, DAcc) ->
- gb_trees:insert(K, V, DAcc)
- end, Dict0, [{"connection", H#headers.connection},
- {"if-match", H#headers.if_match},
- {"if-none-match", H#headers.if_none_match},
- {"if-range", H#headers.if_range},
- {"if-unmodified-since", H#headers.if_unmodified_since},
- {"range", H#headers.range},
- {"referer", H#headers.referer},
- {"accept-ranges", H#headers.accept_ranges},
- {"keep-alive", H#headers.keep_alive},
- {"location", H#headers.location},
- {"content-length", H#headers.content_length},
- {"content-type", H#headers.content_type},
- {"content-encoding", H#headers.content_encoding},
- {"authorization", H#headers.authorization},
- {"transfer-encoding", H#headers.transfer_encoding}]).
+ FixedAuth =
+ case H#headers.authorization of
+ undefined ->
+ undefined;
+ {_Username, _Password, Auth} ->
+ Auth
+ end,
+ Headers = [{"Connection", H#headers.connection},
+ {"If-Match", H#headers.if_match},
+ {"If-None-match", H#headers.if_none_match},
+ {"If-Range", H#headers.if_range},
+ {"If-Unmodified-Since", H#headers.if_unmodified_since},
+ {"Range", H#headers.range},
+ {"Referer", H#headers.referer},
+ {"Accept-Ranges", H#headers.accept_ranges},
+ {"Keep-Alive", H#headers.keep_alive},
+ {"Location", H#headers.location},
+ {"Content-Length", H#headers.content_length},
+ {"Content-Type", H#headers.content_type},
+ {"Content-Encoding", H#headers.content_encoding},
+ {"Authorization", FixedAuth},
+ {"Transfer-Encoding", H#headers.transfer_encoding}],
+ DefinedHeaders = [{K,V} || {K,V} <- Headers, V /= undefined],
+ lists:foldl(fun({K0, V0}, DAcc) ->
+ {K, V} = ewgi_api:normalize_header({K0, V0}),
+ gb_trees:insert(K, [{K0, V}], DAcc)
+ end, Dict0, DefinedHeaders).
%% Final callback after entire input has been read
read_input(Callback, {Length, _ChunkSz}, _Left) when is_function(Callback), Length =< 0 ->

0 comments on commit 95741fd

Please sign in to comment.
Something went wrong with that request. Please try again.