Thursday, March 22, 2012

Cowboy + ErlyDTL

This is a continuation of part 1 so I'm going to assume we're starting where we left off there.

Now that we have a Cowboy server that can handle static pages, we need to set it up to handle dynamic pages.  In comes ErlyDTL.  This is basically going to be the same as OJ's tutorial but for Cowboy instead of webmachine.

ErlyDTL Install
Change your rebar.config to include the ErlyDTL dependency.
%%-*- mode: erlang -*-
{sub_dirs, [
"rel"
]}.
{erl_opts, [debug_info]}. %, fail_on_warning]}.
{require_otp_vsn, "R15"}.
{deps_dir, ["deps"]}.
{deps,
[
{cowboy, ".*", {git, "https://github.com/extend/cowboy.git", {branch, "master"}}},
{erlydtl, ".*", {git, "https://github.com/evanmiller/erlydtl.git", "HEAD"}}
]
}.
view raw rebar.config hosted with ❤ by GitHub
Run ./rebar get-deps compile to make sure that step worked.

Make a page controlled by code
Modify src/simple_server_http.erl to add a path and handler name to the dynamic page you're creating in dispatch_rules/0.  In this case, requests for the 'pony' resource (http://127.0.0.1/pony) should be handled by simple_server_http_pony.
-module(simple_server_http).
-behaviour(gen_server).
-define(SERVER, ?MODULE).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-record(state, {}).
%% ------------------------------------------------------------------
%% API Function Definitions
%% ------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% ------------------------------------------------------------------
%% gen_server Function Definitions
%% ------------------------------------------------------------------
dispatch_rules() ->
%% {Host, list({Path, Handler, Opts})}
[{'_', [
{[], simple_server_http_static, [<<"html">>,<<"index.html">>]}
, {[<<"pony">>], simple_server_http_pony, []}
, {'_', simple_server_http_catchall, []}
]}].
confval(Key, Default) ->
case application:get_env(Key) of
undefined -> Default;
Val -> Val
end.
init([]) ->
Port = confval(port, 80),
Ip = confval(ip, "127.0.0.1"),
NumAcceptors = confval(num_acceptors, 16),
IpStr = case is_list(Ip) of true -> Ip; false -> inet_parse:ntoa(Ip) end,
error_logger:info_msg("simple_server listening on http://~s:~B/~n", [IpStr,Port]),
%%
%% Name, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts
cowboy:start_listener(http, NumAcceptors,
cowboy_tcp_transport, [{port, Port}],
cowboy_http_protocol, [{dispatch, dispatch_rules()}]
),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{noreply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------
Create src/simple_server_http_pony.erl
%%
%% show details on a specific process
%%
-module(simple_server_http_pony).
-behaviour(cowboy_http_handler).
-export([init/3, handle/2, terminate/2]).
init({tcp, http}, Req, _Opts) ->
{ok, Req, undefined_state}.
handle(Req, State) ->
{ok, Body} = mylittlepony_dtl:render([{pony_name, "Dazzleglow"}]),
Headers = [{<<"Content-Type">>, <<"text/html">>}],
{ok, Req2} = cowboy_http_req:reply(200, Headers, Body, Req),
{ok, Req2, State}.
terminate(_Req, _State) ->
ok.

Create templates/mylittlepony.dtl
<html><body>Your favorite Little Pony is {{ pony_name }}.</body></html>

Remove the previous release, compile, generate, run.
rm -R rel/simple_server
./rebar compile generate
sudo erl -sname simple_server -pa $PWD/ebin $PWD/deps/*/ebin -boot start_sasl -s simple_server
Go to http://127.0.0.1/pony.  Dazzleglow is everyone's favorite pony.

Take in user input
If only the world were so simple that we could just create answers...  The following shows how to deal with GET/POST parameters so we can take input from those pesky users.

Create a new dispatch rule and handler for a page called captainflint.  I'm going to assume you know how to add a dispatch rule to src/simpleserver_http.erl at this point.

<html>
<body>
GET squawk:<br />
{{ squawk_get }}<br /><br />
POST squawk:<br />
{{ squawk_post }}<br /><br />
<form method="POST">
post_input: <input type="text" name="post_input" /><br />
post_input: <input type="text" name="post_input" /><br />
<input type="submit" value="POST it!" /><br />
post_input2:<input type="text" name="post_input2" /><br />
</form>
</body>
</html>
%%
%% show details on a specific process
%%
-module(simple_server_http_captainflint).
-behaviour(cowboy_http_handler).
-export([init/3, handle/2, terminate/2]).
init({tcp, http}, Req, _Opts) ->
{ok, Req, undefined_state}.
handle(Req, State) ->
{GetVal, _} = cowboy_http_req:qs_val(<<"get_input">>, Req),
PostValArr = get_post_value(<<"post_input">>, Req),
PostVal = case PostValArr of
undefined -> undefined;
_ -> string:join([binary_to_list(X) || X <- PostValArr], ",")
end,
{ok, Body} = captainflint_dtl:render([{squawk_get, GetVal}, {squawk_post, PostVal}]),
Headers = [{<<"Content-Type">>, <<"text/html">>}],
{ok, Req2} = cowboy_http_req:reply(200, Headers, Body, Req),
{ok, Req2, State}.
get_post_values(Req) ->
{Method, _} = cowboy_http_req:method(Req),
get_post_values(Method, Req).
get_post_values('POST', Req) ->
{Vals, _} = cowboy_http_req:body_qs(Req),
Vals;
get_post_values(_, _) ->
undefined.
get_post_value(Name, Req) ->
PostVals = get_post_values(Req),
extract_post_value(Name, PostVals).
extract_post_value(_, undefined) ->
undefined;
extract_post_value(Name, PostVals) ->
Matches = [X || X <- PostVals, Name =:= element(1,X)],
process_post_value(Matches).
process_post_value([]) ->
undefined;
process_post_value(Vals) ->
{_, Result} = lists:unzip(Vals),
Result.
terminate(_Req, _State) ->
ok.

Go to http://127.0.0.1/captainflint?get_input=Arrr.  Thar she blows.  Now you have a working example of how to access GET and multi-valued POST parameters.

No comments:

Post a Comment