Skip to content

Commit

Permalink
Copied the middleware examples from ewgi_examples to src/middleware.
Browse files Browse the repository at this point in the history
  • Loading branch information
davide committed Oct 10, 2009
1 parent 9fee9f7 commit abf8c0a
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Emakefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@
{outdir, "ebin"},
debug_info,
{d, debug}]
}.

{["src/middleware/*/*"],
[{i, "include"},
{outdir, "ebin"},
debug_info,
{d, debug}]
}.
70 changes: 70 additions & 0 deletions src/middleware/ewgi_deflate/ewgi_deflate.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
%% @author Filippo Pacini <filippo.pacini@gmail.com>
%% @copyright 2009 S.G. Consulting.

%% @doc deflate ewgi middleware.

-module(ewgi_deflate).
-author('Filippo Pacini <filippo.pacini@gmail.com>').

-export([run/2]).

-define(ENCODABLE, ["text/plain", "text/html", "text/xml"]).

run(Ctx, []) ->
%% get the accept encoding header
AcceptEnc = ewgi_api:get_header_value("accept-encoding", Ctx),
Hdrs = ewgi_api:response_headers(Ctx),
%% check gzip/deflate
Ctx1 = case can_encode_response(Hdrs) of
true ->
case parse_encoding(AcceptEnc) of
false ->
Ctx;
gzip ->
%% FIXME: does not work if response_message_body streams a file
Body1 = zlib:gzip(ewgi_api:response_message_body(Ctx)),
Hdrs1 = [{"Content-encoding", "gzip"}|Hdrs],
ewgi_api:response_headers(
Hdrs1,
ewgi_api:response_message_body(Body1, Ctx)
);
deflate ->
%% FIXME: does not work if response_message_body streams a file
Body1 = zlib:compress(ewgi_api:response_message_body(Ctx)),
Hdrs1 = [{"Content-encoding", "deflate"}|Hdrs],
ewgi_api:response_headers(
Hdrs1,
ewgi_api:response_message_body(Body1, Ctx)
)
end;
false ->
Ctx
end,
Ctx1.

can_encode_response(Headers) ->
ContentType = proplists:get_value("Content-type", Headers),
ContentEnc = proplists:get_value("Content-encoding", Headers),
can_encode_response1(ContentType, ContentEnc).

can_encode_response1(ContentType, undefined) ->
lists:member(ContentType, ?ENCODABLE);
can_encode_response1(_ContentType, _ContentEncoding) ->
%% the response is already encoded in some way so we don't do anything
false.

parse_encoding(undefined) ->
false;
parse_encoding(Encoding) ->
%% FIXME: read HTTP specs to see how to do this properly (e.g. check the q=X and the order)
case string:str(Encoding, "gzip") of
X when X>0 ->
gzip;
_ ->
case string:str(Encoding, "deflate") of
Y when Y>0 ->
deflate;
_ ->
false
end
end.
16 changes: 16 additions & 0 deletions src/middleware/ewgi_hello/ewgi_hello.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
%% @author Filippo Pacini <filippo.pacini@gmail.com>
%% @copyright 2009 S.G. Consulting.

%% @doc Hello world application and to_upper middleware

-module(ewgi_hello).
-author('Filippo Pacini <filippo.pacini@gmail.com>').

-export([run/2]).

run({ewgi_context, Request, _Response}, []) ->
ResponseHeaders = [{"Content-type", "text/plain"}],
Response = {ewgi_response, {200, "OK"}, ResponseHeaders,
[<<"Hello world!">>], undefined},
{ewgi_context, Request, Response}.

31 changes: 31 additions & 0 deletions src/middleware/ewgi_index/ewgi_index.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
%% @author Filippo Pacini <filippo.pacini@gmail.com>
%% @copyright 2009 S.G. Consulting.

%% @doc Example Index. List of examples with links.

-module(ewgi_index).
-author('Filippo Pacini <filippo.pacini@gmail.com>').

-export([run/2]).

run({ewgi_context, Request, _Response}, []) ->
Body = "<html><head><title>Ewgi Examples</title></head>
<body>
<h2>Ewgi Examples</h2>
<ul>
<li><a href=\"/hello\">Hello World</a>: simple hello world (source file: src/ewgi_hello.erl)</li>
<li><a href=\"/HELLO\">HELLO WORD</a>: simple middleware transforming all the body in uppercase (source file: src/ewgi_to_upper.erl)</li>
<li><a href=\"/test.txt\">File streaming</a>: streams the the file priv/www/test.txt (source file: src/ewgi_stream_file.erl)</li>
<li><a href=\"/gzhello\">Gzip encodes the Hello World example</a>: if the browser accepts gzip encoding the result of the hello_app is gzipped (source file: src/ewgi_deflate.erl)</li>
<li><a href=\"/postex\">Post example</a>: middleware handling of POST data (source file: src/ewgi_post.erl)</li>
<li>Session examples (src/ewgi_session.erl):
<ul><li><a href=\"/session/cookie\">client-side</a>: encrypted client-side session storage using cookies (source file: src/ewgi_session_cookie_store.erl)</li>
<li><a href=\"/session/server\">server-side</a>: server-side session storage using an ets table (source file: src/ewgi_session_server_store.erl)</li>
</ul></li>
</ul>
</body>
</html>",
ResponseHeaders = [{"Content-type", "text/html"}],
Response = {ewgi_response, {200, "OK"}, ResponseHeaders,
Body, undefined},
{ewgi_context, Request, Response}.
129 changes: 129 additions & 0 deletions src/middleware/ewgi_post/ewgi_post.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
%% @author Filippo Pacini <filippo.pacini@gmail.com>
%% @copyright 2009 S.G. Consulting.

%% @doc Post example

-module(ewgi_post).
-author('Filippo Pacini <filippo.pacini@gmail.com>').

-export([run/2]).
-export([post_app_example/1]).

-define(DEFAULT_MAX_LENGTH, 2097152). %% 2 MB maximum

run(Ctx, [NoPostApp, FailedPostApp, SuccessPostApp]) ->
run(Ctx, [NoPostApp, FailedPostApp, SuccessPostApp, ?DEFAULT_MAX_LENGTH]);
run(Ctx, [NoPostApp, FailedPostApp, SuccessPostApp, MaxLength]) ->
Parser = post_parse_middleware(MaxLength, SuccessPostApp, FailedPostApp),
case ewgi_api:request_method(Ctx) of
'GET' ->
NoPostApp(Ctx);
'POST' ->
Parser(Ctx)
end.

%% MaxLength is the maximum size (in bytes) that the server will
%% receive from the client. App should be the application called when
%% the parse is successful (or unnecessary). ErrApp should be an
%% error application when the content length exceeds the maximum
%% specified limit.
post_parse_middleware(MaxLength, App, ErrApp)
when is_integer(MaxLength), MaxLength > 0, is_function(App, 1) ->
fun(Ctx) ->
case ewgi_api:request_method(Ctx) of
Method when Method =:= 'POST';
Method =:= 'PUT' ->
case ewgi_api:remote_user_data(Ctx) of
undefined ->
%% Check content-type first
Ct = ewgi_api:content_type(Ctx),
parse_post(Ctx, App, ErrApp, parse_ct(Ct), MaxLength);
_ ->
App(Ctx)
end;
_ ->
App(Ctx)
end
end.

%% Parse content-type (ignoring additional vars for now)
%% Should look like "major/minor; var=val"
parse_ct(L) when is_list(L) ->
case string:tokens(L, ";") of
[H|_] ->
H;
_ ->
undefined
end.

parse_post(Ctx, App, ErrApp, "application/x-www-form-urlencoded", Max) ->
case ewgi_api:content_length(Ctx) of
L when is_integer(L), L > Max ->
%% shouldn't we set an error message here?
ErrApp(Ctx);
L when is_integer(L), L > 0 ->
Input = read_input_string(Ctx, L),
Vals = ewgi_api:parse_post(Input),
Ctx1 = ewgi_api:remote_user_data(Vals, Ctx),
App(Ctx1);
_ ->
ErrApp(Ctx)
end;
parse_post(Ctx, App, _, _, _) ->
%% Silently ignore other content-types
App(Ctx).

read_input_string(Ctx, L) when is_integer(L), L > 0 ->
R = ewgi_api:read_input(Ctx),
iolist_to_binary(R(read_input_string_cb([]), L)).

read_input_string_cb(Acc) ->
fun(eof) ->
lists:reverse(Acc);
({data, B}) ->
read_input_string_cb([B|Acc])
end.

%%
%% example functions on how to use the post handling middleware
%%
post_app_example(Ctx) ->
run(Ctx, [fun display_form/1,
fun post_app_error/1,
fun display_form_data/1]).

post_app_error({ewgi_context, Request, _}) ->
Response = {ewgi_response, {400, "BAD REQUEST"}, [],
[<<"Maximum content-length exceeded.">>],
undefined},
{ewgi_context, Request, Response}.

display_form_data({ewgi_context, Request, _Response}=Ctx) ->
Body =
case ewgi_api:remote_user_data(Ctx) of
undefined ->
"undefined";
Body1 ->
io_lib:format("~p", [Body1])
end,
ResponseHeaders = [{"Content-type", "text/plain"}],
Response = {ewgi_response,
{200, "OK"},
ResponseHeaders,
[Body], undefined},
{ewgi_context, Request, Response}.

display_form({ewgi_context, Request, _Response}) ->
Body = <<"<form action=\"/postex\" method=\"post\">
Un: <input type=\"text\" name=\"un\" value=\"\"/>
<br/>
Pw: <input type=\"text\" name=\"pw\" value=\"\"/>
<br/><br/>
<input type=\"submit\" name=\"submit\" value=\"Login\"/>
</form>">>,
ResponseHeaders = [{"Content-type", "text/html"}],
Response = {ewgi_response,
{200, "OK"},
ResponseHeaders,
[Body], undefined},
{ewgi_context, Request, Response}.
108 changes: 108 additions & 0 deletions src/middleware/ewgi_stream_file/ewgi_stream_file.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
%% @author Hunter Morris <hunter.morris@smarkets.com>
%% @author Filippo Pacini <filippo.pacini@gmail.com>

%% @doc ewgi stream file application
-module(ewgi_stream_file).

-export([run/2]).

%%
%% Files opened in raw mode cannot be read in processes distinct
%% from the one that opened the file! So we need to make sure
%% that both the open and read operations are done in the same
%% process.
%%
%% Since a webserver might need to spawn a new process to handle
%% our stream (Yaws and the old Inets implementation did)
%% we'll get around that by delaying the open operation to the
%% head of the stream.
%%
run(Ctx, [File]) ->
case file:read_file_info(File) of
{ok, _} ->
Mime = guess_mime(File),
LoadIoDevice = {open, File, [raw, binary]},
%% Set ChunkSize to an optimal value
ChunkSize = 1024,
Stream = iodevice_stream(LoadIoDevice, ChunkSize),
ewgi_api:response_status(
{200, "OK"},
ewgi_api:response_headers(
[{"Content-type", Mime}],
ewgi_api:response_message_body(Stream, Ctx)
)
);
_ ->
%% Respond with 404...
ewgi_api:response_message_body(
"404 NOT FOUND",
ewgi_api:response_status({404, "NOT FOUND"}, Ctx)
)
end.

iodevice_stream({open, File, Modes}, ChunkSize) ->
fun() ->
case file:open(File, Modes) of
{ok, IoDevice} ->
{<<>>, iodevice_stream(IoDevice, ChunkSize)};
_ ->
{}
end
end;
iodevice_stream(IoDevice, ChunkSize) ->
fun() ->
case file:read(IoDevice, ChunkSize) of
eof ->
file:close(IoDevice),
{};
{ok, Data} ->
{Data, iodevice_stream(IoDevice, ChunkSize)};
{error, Reason} ->
io:format("got error: ~p~n", [Reason]),
{}
end
end.


%% @spec guess_mime(string()) -> string()
%% @doc Guess the mime type of a file by the extension of its filename.
guess_mime(File) ->
%% Taken from webmachine.
case filename:extension(File) of
".html" ->
"text/html";
".xhtml" ->
"application/xhtml+xml";
".xml" ->
"application/xml";
".css" ->
"text/css";
".js" ->
"application/x-javascript";
".jpg" ->
"image/jpeg";
".jpeg" ->
"image/jpeg";
".gif" ->
"image/gif";
".png" ->
"image/png";
".ico" ->
"image/x-icon";
".swf" ->
"application/x-shockwave-flash";
".zip" ->
"application/zip";
".bz2" ->
"application/x-bzip2";
".gz" ->
"application/x-gzip";
".tar" ->
"application/x-tar";
".tgz" ->
"application/x-gzip";
".htc" ->
"text/x-component";
_ ->
"text/plain"
end.
14 changes: 14 additions & 0 deletions src/middleware/ewgi_to_upper/ewgi_to_upper.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
%% @author Filippo Pacini <filippo.pacini@gmail.com>
%% @copyright 2009 S.G. Consulting.

%% @doc to_upper middleware

-module(ewgi_to_upper).
-author('Filippo Pacini <filippo.pacini@gmail.com>').

-export([run/2]).

run(Ctx, []) ->
Body = ewgi_api:response_message_body(Ctx),
Body1 = [string:to_upper(erlang:binary_to_list(B)) || B <- Body],
ewgi_api:response_message_body(Body1, Ctx).

0 comments on commit abf8c0a

Please sign in to comment.