Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Copied the middleware examples from ewgi_examples to src/middleware.

  • Loading branch information...
commit abf8c0a7823d557a9073a0f97623fd3b6840e6e6 1 parent 9fee9f7
Davide davide authored
7 Emakefile
@@ -12,4 +12,11 @@
12 12 {outdir, "ebin"},
13 13 debug_info,
14 14 {d, debug}]
  15 +}.
  16 +
  17 +{["src/middleware/*/*"],
  18 + [{i, "include"},
  19 + {outdir, "ebin"},
  20 + debug_info,
  21 + {d, debug}]
15 22 }.
70 src/middleware/ewgi_deflate/ewgi_deflate.erl
... ... @@ -0,0 +1,70 @@
  1 +%% @author Filippo Pacini <filippo.pacini@gmail.com>
  2 +%% @copyright 2009 S.G. Consulting.
  3 +
  4 +%% @doc deflate ewgi middleware.
  5 +
  6 +-module(ewgi_deflate).
  7 +-author('Filippo Pacini <filippo.pacini@gmail.com>').
  8 +
  9 +-export([run/2]).
  10 +
  11 +-define(ENCODABLE, ["text/plain", "text/html", "text/xml"]).
  12 +
  13 +run(Ctx, []) ->
  14 + %% get the accept encoding header
  15 + AcceptEnc = ewgi_api:get_header_value("accept-encoding", Ctx),
  16 + Hdrs = ewgi_api:response_headers(Ctx),
  17 + %% check gzip/deflate
  18 + Ctx1 = case can_encode_response(Hdrs) of
  19 + true ->
  20 + case parse_encoding(AcceptEnc) of
  21 + false ->
  22 + Ctx;
  23 + gzip ->
  24 + %% FIXME: does not work if response_message_body streams a file
  25 + Body1 = zlib:gzip(ewgi_api:response_message_body(Ctx)),
  26 + Hdrs1 = [{"Content-encoding", "gzip"}|Hdrs],
  27 + ewgi_api:response_headers(
  28 + Hdrs1,
  29 + ewgi_api:response_message_body(Body1, Ctx)
  30 + );
  31 + deflate ->
  32 + %% FIXME: does not work if response_message_body streams a file
  33 + Body1 = zlib:compress(ewgi_api:response_message_body(Ctx)),
  34 + Hdrs1 = [{"Content-encoding", "deflate"}|Hdrs],
  35 + ewgi_api:response_headers(
  36 + Hdrs1,
  37 + ewgi_api:response_message_body(Body1, Ctx)
  38 + )
  39 + end;
  40 + false ->
  41 + Ctx
  42 + end,
  43 + Ctx1.
  44 +
  45 +can_encode_response(Headers) ->
  46 + ContentType = proplists:get_value("Content-type", Headers),
  47 + ContentEnc = proplists:get_value("Content-encoding", Headers),
  48 + can_encode_response1(ContentType, ContentEnc).
  49 +
  50 +can_encode_response1(ContentType, undefined) ->
  51 + lists:member(ContentType, ?ENCODABLE);
  52 +can_encode_response1(_ContentType, _ContentEncoding) ->
  53 + %% the response is already encoded in some way so we don't do anything
  54 + false.
  55 +
  56 +parse_encoding(undefined) ->
  57 + false;
  58 +parse_encoding(Encoding) ->
  59 + %% FIXME: read HTTP specs to see how to do this properly (e.g. check the q=X and the order)
  60 + case string:str(Encoding, "gzip") of
  61 + X when X>0 ->
  62 + gzip;
  63 + _ ->
  64 + case string:str(Encoding, "deflate") of
  65 + Y when Y>0 ->
  66 + deflate;
  67 + _ ->
  68 + false
  69 + end
  70 + end.
16 src/middleware/ewgi_hello/ewgi_hello.erl
... ... @@ -0,0 +1,16 @@
  1 +%% @author Filippo Pacini <filippo.pacini@gmail.com>
  2 +%% @copyright 2009 S.G. Consulting.
  3 +
  4 +%% @doc Hello world application and to_upper middleware
  5 +
  6 +-module(ewgi_hello).
  7 +-author('Filippo Pacini <filippo.pacini@gmail.com>').
  8 +
  9 +-export([run/2]).
  10 +
  11 +run({ewgi_context, Request, _Response}, []) ->
  12 + ResponseHeaders = [{"Content-type", "text/plain"}],
  13 + Response = {ewgi_response, {200, "OK"}, ResponseHeaders,
  14 + [<<"Hello world!">>], undefined},
  15 + {ewgi_context, Request, Response}.
  16 +
31 src/middleware/ewgi_index/ewgi_index.erl
... ... @@ -0,0 +1,31 @@
  1 +%% @author Filippo Pacini <filippo.pacini@gmail.com>
  2 +%% @copyright 2009 S.G. Consulting.
  3 +
  4 +%% @doc Example Index. List of examples with links.
  5 +
  6 +-module(ewgi_index).
  7 +-author('Filippo Pacini <filippo.pacini@gmail.com>').
  8 +
  9 +-export([run/2]).
  10 +
  11 +run({ewgi_context, Request, _Response}, []) ->
  12 + Body = "<html><head><title>Ewgi Examples</title></head>
  13 +<body>
  14 +<h2>Ewgi Examples</h2>
  15 +<ul>
  16 +<li><a href=\"/hello\">Hello World</a>: simple hello world (source file: src/ewgi_hello.erl)</li>
  17 +<li><a href=\"/HELLO\">HELLO WORD</a>: simple middleware transforming all the body in uppercase (source file: src/ewgi_to_upper.erl)</li>
  18 +<li><a href=\"/test.txt\">File streaming</a>: streams the the file priv/www/test.txt (source file: src/ewgi_stream_file.erl)</li>
  19 +<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>
  20 +<li><a href=\"/postex\">Post example</a>: middleware handling of POST data (source file: src/ewgi_post.erl)</li>
  21 +<li>Session examples (src/ewgi_session.erl):
  22 +<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>
  23 +<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>
  24 +</ul></li>
  25 +</ul>
  26 +</body>
  27 +</html>",
  28 + ResponseHeaders = [{"Content-type", "text/html"}],
  29 + Response = {ewgi_response, {200, "OK"}, ResponseHeaders,
  30 + Body, undefined},
  31 + {ewgi_context, Request, Response}.
129 src/middleware/ewgi_post/ewgi_post.erl
... ... @@ -0,0 +1,129 @@
  1 +%% @author Filippo Pacini <filippo.pacini@gmail.com>
  2 +%% @copyright 2009 S.G. Consulting.
  3 +
  4 +%% @doc Post example
  5 +
  6 +-module(ewgi_post).
  7 +-author('Filippo Pacini <filippo.pacini@gmail.com>').
  8 +
  9 +-export([run/2]).
  10 +-export([post_app_example/1]).
  11 +
  12 +-define(DEFAULT_MAX_LENGTH, 2097152). %% 2 MB maximum
  13 +
  14 +run(Ctx, [NoPostApp, FailedPostApp, SuccessPostApp]) ->
  15 + run(Ctx, [NoPostApp, FailedPostApp, SuccessPostApp, ?DEFAULT_MAX_LENGTH]);
  16 +run(Ctx, [NoPostApp, FailedPostApp, SuccessPostApp, MaxLength]) ->
  17 + Parser = post_parse_middleware(MaxLength, SuccessPostApp, FailedPostApp),
  18 + case ewgi_api:request_method(Ctx) of
  19 + 'GET' ->
  20 + NoPostApp(Ctx);
  21 + 'POST' ->
  22 + Parser(Ctx)
  23 + end.
  24 +
  25 +%% MaxLength is the maximum size (in bytes) that the server will
  26 +%% receive from the client. App should be the application called when
  27 +%% the parse is successful (or unnecessary). ErrApp should be an
  28 +%% error application when the content length exceeds the maximum
  29 +%% specified limit.
  30 +post_parse_middleware(MaxLength, App, ErrApp)
  31 + when is_integer(MaxLength), MaxLength > 0, is_function(App, 1) ->
  32 + fun(Ctx) ->
  33 + case ewgi_api:request_method(Ctx) of
  34 + Method when Method =:= 'POST';
  35 + Method =:= 'PUT' ->
  36 + case ewgi_api:remote_user_data(Ctx) of
  37 + undefined ->
  38 + %% Check content-type first
  39 + Ct = ewgi_api:content_type(Ctx),
  40 + parse_post(Ctx, App, ErrApp, parse_ct(Ct), MaxLength);
  41 + _ ->
  42 + App(Ctx)
  43 + end;
  44 + _ ->
  45 + App(Ctx)
  46 + end
  47 + end.
  48 +
  49 +%% Parse content-type (ignoring additional vars for now)
  50 +%% Should look like "major/minor; var=val"
  51 +parse_ct(L) when is_list(L) ->
  52 + case string:tokens(L, ";") of
  53 + [H|_] ->
  54 + H;
  55 + _ ->
  56 + undefined
  57 + end.
  58 +
  59 +parse_post(Ctx, App, ErrApp, "application/x-www-form-urlencoded", Max) ->
  60 + case ewgi_api:content_length(Ctx) of
  61 + L when is_integer(L), L > Max ->
  62 + %% shouldn't we set an error message here?
  63 + ErrApp(Ctx);
  64 + L when is_integer(L), L > 0 ->
  65 + Input = read_input_string(Ctx, L),
  66 + Vals = ewgi_api:parse_post(Input),
  67 + Ctx1 = ewgi_api:remote_user_data(Vals, Ctx),
  68 + App(Ctx1);
  69 + _ ->
  70 + ErrApp(Ctx)
  71 + end;
  72 +parse_post(Ctx, App, _, _, _) ->
  73 + %% Silently ignore other content-types
  74 + App(Ctx).
  75 +
  76 +read_input_string(Ctx, L) when is_integer(L), L > 0 ->
  77 + R = ewgi_api:read_input(Ctx),
  78 + iolist_to_binary(R(read_input_string_cb([]), L)).
  79 +
  80 +read_input_string_cb(Acc) ->
  81 + fun(eof) ->
  82 + lists:reverse(Acc);
  83 + ({data, B}) ->
  84 + read_input_string_cb([B|Acc])
  85 + end.
  86 +
  87 +%%
  88 +%% example functions on how to use the post handling middleware
  89 +%%
  90 +post_app_example(Ctx) ->
  91 + run(Ctx, [fun display_form/1,
  92 + fun post_app_error/1,
  93 + fun display_form_data/1]).
  94 +
  95 +post_app_error({ewgi_context, Request, _}) ->
  96 + Response = {ewgi_response, {400, "BAD REQUEST"}, [],
  97 + [<<"Maximum content-length exceeded.">>],
  98 + undefined},
  99 + {ewgi_context, Request, Response}.
  100 +
  101 +display_form_data({ewgi_context, Request, _Response}=Ctx) ->
  102 + Body =
  103 + case ewgi_api:remote_user_data(Ctx) of
  104 + undefined ->
  105 + "undefined";
  106 + Body1 ->
  107 + io_lib:format("~p", [Body1])
  108 + end,
  109 + ResponseHeaders = [{"Content-type", "text/plain"}],
  110 + Response = {ewgi_response,
  111 + {200, "OK"},
  112 + ResponseHeaders,
  113 + [Body], undefined},
  114 + {ewgi_context, Request, Response}.
  115 +
  116 +display_form({ewgi_context, Request, _Response}) ->
  117 + Body = <<"<form action=\"/postex\" method=\"post\">
  118 +Un: <input type=\"text\" name=\"un\" value=\"\"/>
  119 +<br/>
  120 + Pw: <input type=\"text\" name=\"pw\" value=\"\"/>
  121 +<br/><br/>
  122 + <input type=\"submit\" name=\"submit\" value=\"Login\"/>
  123 +</form>">>,
  124 + ResponseHeaders = [{"Content-type", "text/html"}],
  125 + Response = {ewgi_response,
  126 + {200, "OK"},
  127 + ResponseHeaders,
  128 + [Body], undefined},
  129 + {ewgi_context, Request, Response}.
108 src/middleware/ewgi_stream_file/ewgi_stream_file.erl
... ... @@ -0,0 +1,108 @@
  1 +%% @author Hunter Morris <hunter.morris@smarkets.com>
  2 +%% @author Filippo Pacini <filippo.pacini@gmail.com>
  3 +
  4 +%% @doc ewgi stream file application
  5 +-module(ewgi_stream_file).
  6 +
  7 +-export([run/2]).
  8 +
  9 +%%
  10 +%% Files opened in raw mode cannot be read in processes distinct
  11 +%% from the one that opened the file! So we need to make sure
  12 +%% that both the open and read operations are done in the same
  13 +%% process.
  14 +%%
  15 +%% Since a webserver might need to spawn a new process to handle
  16 +%% our stream (Yaws and the old Inets implementation did)
  17 +%% we'll get around that by delaying the open operation to the
  18 +%% head of the stream.
  19 +%%
  20 +run(Ctx, [File]) ->
  21 + case file:read_file_info(File) of
  22 + {ok, _} ->
  23 + Mime = guess_mime(File),
  24 + LoadIoDevice = {open, File, [raw, binary]},
  25 + %% Set ChunkSize to an optimal value
  26 + ChunkSize = 1024,
  27 + Stream = iodevice_stream(LoadIoDevice, ChunkSize),
  28 + ewgi_api:response_status(
  29 + {200, "OK"},
  30 + ewgi_api:response_headers(
  31 + [{"Content-type", Mime}],
  32 + ewgi_api:response_message_body(Stream, Ctx)
  33 + )
  34 + );
  35 + _ ->
  36 + %% Respond with 404...
  37 + ewgi_api:response_message_body(
  38 + "404 NOT FOUND",
  39 + ewgi_api:response_status({404, "NOT FOUND"}, Ctx)
  40 + )
  41 + end.
  42 +
  43 +iodevice_stream({open, File, Modes}, ChunkSize) ->
  44 + fun() ->
  45 + case file:open(File, Modes) of
  46 + {ok, IoDevice} ->
  47 + {<<>>, iodevice_stream(IoDevice, ChunkSize)};
  48 + _ ->
  49 + {}
  50 + end
  51 + end;
  52 +iodevice_stream(IoDevice, ChunkSize) ->
  53 + fun() ->
  54 + case file:read(IoDevice, ChunkSize) of
  55 + eof ->
  56 + file:close(IoDevice),
  57 + {};
  58 + {ok, Data} ->
  59 + {Data, iodevice_stream(IoDevice, ChunkSize)};
  60 + {error, Reason} ->
  61 + io:format("got error: ~p~n", [Reason]),
  62 + {}
  63 + end
  64 + end.
  65 +
  66 +
  67 +%% @spec guess_mime(string()) -> string()
  68 +%% @doc Guess the mime type of a file by the extension of its filename.
  69 +guess_mime(File) ->
  70 + %% Taken from webmachine.
  71 + case filename:extension(File) of
  72 + ".html" ->
  73 + "text/html";
  74 + ".xhtml" ->
  75 + "application/xhtml+xml";
  76 + ".xml" ->
  77 + "application/xml";
  78 + ".css" ->
  79 + "text/css";
  80 + ".js" ->
  81 + "application/x-javascript";
  82 + ".jpg" ->
  83 + "image/jpeg";
  84 + ".jpeg" ->
  85 + "image/jpeg";
  86 + ".gif" ->
  87 + "image/gif";
  88 + ".png" ->
  89 + "image/png";
  90 + ".ico" ->
  91 + "image/x-icon";
  92 + ".swf" ->
  93 + "application/x-shockwave-flash";
  94 + ".zip" ->
  95 + "application/zip";
  96 + ".bz2" ->
  97 + "application/x-bzip2";
  98 + ".gz" ->
  99 + "application/x-gzip";
  100 + ".tar" ->
  101 + "application/x-tar";
  102 + ".tgz" ->
  103 + "application/x-gzip";
  104 + ".htc" ->
  105 + "text/x-component";
  106 + _ ->
  107 + "text/plain"
  108 + end.
14 src/middleware/ewgi_to_upper/ewgi_to_upper.erl
... ... @@ -0,0 +1,14 @@
  1 +%% @author Filippo Pacini <filippo.pacini@gmail.com>
  2 +%% @copyright 2009 S.G. Consulting.
  3 +
  4 +%% @doc to_upper middleware
  5 +
  6 +-module(ewgi_to_upper).
  7 +-author('Filippo Pacini <filippo.pacini@gmail.com>').
  8 +
  9 +-export([run/2]).
  10 +
  11 +run(Ctx, []) ->
  12 + Body = ewgi_api:response_message_body(Ctx),
  13 + Body1 = [string:to_upper(erlang:binary_to_list(B)) || B <- Body],
  14 + ewgi_api:response_message_body(Body1, Ctx).

0 comments on commit abf8c0a

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