Permalink
Browse files

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

  • Loading branch information...
filippo committed Sep 27, 2009
2 parents 827106e + df5e128 commit 95741fd44d57c1052799402d1663ad6e479ceefd
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,5 +3,6 @@
{["src/*"],
[{i, "include"},
{outdir, "ebin"},
- debug_info]
+ debug_info,
+ {d, debug}]
}.
View
@@ -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
@@ -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
@@ -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
@@ -5,7 +5,9 @@
,{env, []}
,{modules, [ewgi_api
,ewgi_application
+ ,ewgi_inets
,ewgi_mochiweb
+ ,ewgi_testapp
,ewgi_yaws
]}
]}.
View
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
Oops, something went wrong.

0 comments on commit 95741fd

Please sign in to comment.