Permalink
Browse files

Copied the middleware examples from ewgi_examples to src/middleware.

  • Loading branch information...
1 parent 9fee9f7 commit abf8c0a7823d557a9073a0f97623fd3b6840e6e6 @davide davide committed Oct 10, 2009
View
7 Emakefile
@@ -12,4 +12,11 @@
{outdir, "ebin"},
debug_info,
{d, debug}]
+}.
+
+{["src/middleware/*/*"],
+ [{i, "include"},
+ {outdir, "ebin"},
+ debug_info,
+ {d, debug}]
}.
View
70 src/middleware/ewgi_deflate/ewgi_deflate.erl
@@ -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.
View
16 src/middleware/ewgi_hello/ewgi_hello.erl
@@ -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}.
+
View
31 src/middleware/ewgi_index/ewgi_index.erl
@@ -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}.
View
129 src/middleware/ewgi_post/ewgi_post.erl
@@ -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}.
View
108 src/middleware/ewgi_stream_file/ewgi_stream_file.erl
@@ -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.
View
14 src/middleware/ewgi_to_upper/ewgi_to_upper.erl
@@ -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.