Skip to content
This repository has been archived by the owner on Nov 7, 2018. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
Completely revamped and improved
  • Loading branch information
davebryson committed Feb 11, 2009
1 parent 8803a2f commit 5cce39e
Show file tree
Hide file tree
Showing 19 changed files with 558 additions and 199 deletions.
46 changes: 26 additions & 20 deletions README.textile
Expand Up @@ -32,48 +32,50 @@ To run the sample:

h3. How it works:

You write a controller similar to how you'd write a "gen_server" based app, but in our
case you use the included "gen_controller" behavior. In the controller you define the functions
you want to expose to requests. BeepBeep will automatically map Url requests to controller and
functions (or actions). For example a request to "/hello/show" would map to the "hello_controller"
and invoke the "show" function.
You write a controller with a set of method that look like this:

Here's a controller example:
handle_request(Action,Params)

where Action is a string that will match to request in the Url and
Params is an Array of optional parameters.

BeepBeep will automatically map Url requests to controller and functions (or actions). For example a request to "/hello/show" would map to the "hello_controller"
and invoke the "handle_request("show",[])" function.

Here's anexample:

<pre>
<code>
%% hello_controller.erl
-module(hello_controller).

-export([show/1]).
-export([handle_request/2, before_filter/1]).
-export([handle_request/2, before_filter/0]).

-behaviour(gen_controller).
-include("beepbeep.hrl").

show(Params) ->
gen_controller:call(?MODULE,index,Params).

%% Callback for show
handle_request(show,Params) ->
{render, "hello/show.html",[{name,"BeepBeep"}],Params}.
%% Maps to http://localhost:8000/hello/show
handle_request("show",[]) ->
{render, "hello/show.html",[{name,"BeepBeep"}]};

%% Maps to http://localhost:8000/hello/feed/2007
handle_request("feed",[Year]) ->
%% Now Year contains the value 2007
{render,"hello/feed.html",[{year,Year}]}.


%% Callback filter hook
before_filter(Params) ->
{ok}.
ok.

</code>
</pre>

From "handle_request" we return a tuple that tells the framework what template to use. Templates
are located in the template directory. In our example we'ill use the template located in the
are located in the template directory. In our example we'll use the template located in the
subdirectory "hello" and the file "show.html"

Here's an example of the "show.html" template:

<pre><code>
<h2>Hello from {{ data }} </h2>
<h2>Hello from {{ name }} </h2>
</code></pre>

Which will result in:
Expand All @@ -87,4 +89,8 @@ via erlyDTL.
This approach provides a clean separation of the erlang logic in the controller and the html code in
the template.

You can also implement the before_filter to check requests before the matching "handle_request" is
called. Filters that pass should simply return the atom ok, otherwise they should return one
of the request responses such as {render...} or {redirect..."

More to come...
23 changes: 12 additions & 11 deletions include/beepbeep.hrl
@@ -1,13 +1,14 @@

%% Session Id key
-define(SID,"_beepbeep_session_id").
-define(BEEPBEEP_SID, "_beepbeep_session_id_").

%% Holds parameter information passes around
-record(params,{
controller,
action,
id,
method,
data, %% Query and Post Data
sid %% Session Id
}).
%% Environment data
-define(BEEPBEEP_ENV_DATA, [{server_sw, "SERVER_SOFTWARE"},
{server_name, "SERVER_NAME"},
{server_protocol, "SERVER_PROTOCOL"},
{server_port, "SERVER_PORT"},
{method, "REQUEST_METHOD"},
{content_type, "CONTENT_TYPE"},
{content_length,"CONTENT_LENGTH"},
{path_info, "PATH_INFO"},
{remote_addr, "REMOTE_ADDR"},
{beepbeep_params, "beepbeep.data"}]).
27 changes: 27 additions & 0 deletions priv/skel/src/home_controller.erl
@@ -0,0 +1,27 @@
%%
%% Sample default controller
%%
-module(home_controller,[Env]).

-export([handle_request/2,before_filter/0]).

handle_request("index",[]) ->
{render,"home/index.html",[{data,"Hello There From BeepBeep!"}]};

handle_request("show",[Year]) ->
Sid = beepbeep_args:get_session_id(Env),
Name = beepbeep_args:get_param("name",Env),
{render,"home/show.html",[{year,Year},{sid,Sid},{name,Name}]}.


before_filter() ->
FilterOnly = ["show"],
case lists:member(beepbeep_args:get_action(Env),FilterOnly) of
true ->
error_logger:info_report("Doing the filter for SHOW~n"),
ok;
false ->
ok
end.


1 change: 1 addition & 0 deletions priv/skel/src/skel.app
Expand Up @@ -5,6 +5,7 @@
skel,
skel_app,
skel_sup,
skel_web,
skel_deps
]},
{registered, []},
Expand Down
6 changes: 0 additions & 6 deletions priv/skel/src/skel.erl
@@ -1,10 +1,4 @@
%% @author author <author@example.com>
%% @copyright YYYY author.

%% @doc TEMPLATE.

-module(skel).
-author('author <author@example.com>').
-export([start/0, stop/0]).

ensure_started(App) ->
Expand Down
6 changes: 0 additions & 6 deletions priv/skel/src/skel_app.erl
@@ -1,10 +1,4 @@
%% @author author <author@example.com>
%% @copyright YYYY author.

%% @doc Callbacks for the skel application.

-module(skel_app).
-author('author <author@example.com>').

-behaviour(application).
-export([start/2,stop/1]).
Expand Down
8 changes: 0 additions & 8 deletions priv/skel/src/skel_deps.erl
@@ -1,12 +1,4 @@
%% @author author <author@example.com>
%% @copyright YYYY author.

%% @doc Ensure that the relatively-installed dependencies are on the code
%% loading path, and locate resources relative
%% to this application's path.

-module(skel_deps).
-author('author <author@example.com>').

-export([ensure/0, ensure/1]).
-export([get_base_dir/0, get_base_dir/1]).
Expand Down
20 changes: 9 additions & 11 deletions priv/skel/src/skel_sup.erl
@@ -1,10 +1,4 @@
%% @author author <author@example.com>
%% @copyright YYYY author.

%% @doc Supervisor for the skel application.

-module(skel_sup).
-author('author <author@example.com>').

-behaviour(supervisor).

Expand Down Expand Up @@ -43,17 +37,21 @@ upgrade() ->
init([]) ->
Ip = case os:getenv("MOCHIWEB_IP") of false -> "0.0.0.0"; Any -> Any end,
WebConfig = [
{ip, Ip},
{port, 8000},
{docroot, skel_deps:local_path(["priv", "www"])}],
{ip, Ip},
{port, 8000}
],

BaseDir = skel_deps:get_base_dir(),

Web = {skel_web,
{skel_web, start, [WebConfig]},
permanent, 5000, worker, dynamic},

Router = {beepbeep_router,
{beepbeep_router, start, [BaseDir]},
permanent, 5000, worker, dynamic},
SessionServer = {beepbeep_session_server,
{beepbeep_session_server,start,[]},
permanent, 5000, worker, dynamic},

Processes = [Web,SessionServer],
Processes = [Router,SessionServer,Web],
{ok, {{one_for_one, 10, 10}, Processes}}.
110 changes: 26 additions & 84 deletions priv/skel/src/skel_web.erl
@@ -1,104 +1,46 @@
-module(skel_web).
-author('Dave Bryson <http://weblog.miceda.org>').

-export([start/1, stop/0, loop/2]).

-export([start/1, stop/0, loop/1]).
-include("beepbeep.hrl").

start(Options) ->
{DocRoot, Options1} = get_option(docroot, Options),
Loop = fun (Req) ->
?MODULE:loop(Req, DocRoot)
end,
mochiweb_http:start([{name, ?MODULE}, {loop, Loop} | Options1]).
?MODULE:loop(Req)
end,
mochiweb_http:start([{name, ?MODULE}, {loop, Loop} | Options]).

stop() ->
mochiweb_http:stop(?MODULE).

loop(Req, DocRoot) ->
Path = Req:get(path),
RequestMethod = Req:get(method),
Params = case RequestMethod of
Method when Method =:= 'GET'; Method =:= 'HEAD' ->
Req:parse_qs();
_ ->
Req:parse_post()
end,
loop(Req) ->
%% Setup env...
InitialEnv = mochiweb_env:setup_environment(Req),
Env = setup_session(Req,InitialEnv),
%%error_logger:info_report(Env),

%% Setup the Session
CookieKey = beepbeep_session_server:new_session(Req:get_cookie_value(?SID)),
%% Setup Params structure for controllers
P1 = #params{sid=CookieKey,data=Params,method=RequestMethod},


%% Route the request
case dispatch(Path,P1) of
{render,Template,Data,P} ->
H = mochiweb_cookies:cookie(?SID,P#params.sid, [{path, "/"}]),
{ok,Content} = render_template(Template,Data),
Req:ok({"text/html",[H],Content});
case beepbeep:dispatch(Env) of
{ok,Status,ContentType,H,Content} ->
Cookie = get_cookie(Env),
Headers = [Cookie|H],
Req:respond({Status,[{"Content-Type",ContentType}|Headers],Content});
%%Req:ok({"text/html",Headers,Content});
{redirect,Url} ->
Req:respond({302,
[{"Location", Url},
{"Content-Type", "text/html; charset=UTF-8"}],
""});
{static,File} ->
Req:serve_file(File,DocRoot)
{static, File} ->
"/" ++ StaticFile = File,
Req:serve_file(StaticFile,skel_deps:local_path(["www"]));
{error,_} ->
Req:respond({500,[],"Server Error"})
end.

%% Internal API

get_option(Option, Options) ->
{proplists:get_value(Option, Options), proplists:delete(Option, Options)}.

%% Route the Request
dispatch(Path,Params) ->
PathParts = string:tokens(Path,"/"),
P1 = extract_route_info(PathParts,Params),
Controller = P1#params.controller,
Action = P1#params.action,

Reply = case catch(Controller:Action(P1)) of
{'EXIT', {undef, _}} ->
%% Try static
"/" ++ StaticPath = Path,
{static,StaticPath};
Any ->
io:format("Ran with ~p~n",[Any]),
Any
end,
Reply.


%% Parse out the route information from the path
extract_route_info([],P) ->
%% Default route of root ("/") path
P1 = P,
P1#params{controller=main_controller,action=index};
extract_route_info([C],P) ->
%% Default "/hello" to "hello_controller:index"
P1 = P,
P1#params{controller=make_controller_name(C),action=index};
extract_route_info([C,A],P) ->
P1 = P,
P1#params{controller=make_controller_name(C),action=list_to_atom(A)};
extract_route_info([C,A,Id],P) ->
P1 = P,
P1#params{controller=make_controller_name(C),action=list_to_atom(A), id=Id}.

make_controller_name(ControllerName) ->
list_to_atom(ControllerName ++ "_controller").


%% Render the Template via erlydtl
render_template(File,Data) ->
FullPathToFile = skel_deps:local_path(["templates",File]),


%% Make a module name
F = lists:reverse(string:tokens(File,"/")),
[N,_] = string:tokens(hd(F),"."),
Mn = string:join([N,"template"],"_"),
ModName = list_to_atom(Mn),
get_cookie(Env) ->
mochiweb_cookies:cookie(?BEEPBEEP_SID,beepbeep_args:get_session_id(Env),[{path, "/"}]).

erlydtl:compile(FullPathToFile,ModName),
ModName:render(Data).
setup_session(Req,Env) ->
SessionKey = beepbeep_session_server:new_session(Req:get_cookie_value(?BEEPBEEP_SID)),
beepbeep_args:set_session_id(SessionKey,Env).
14 changes: 14 additions & 0 deletions priv/skel/views/base.html
@@ -0,0 +1,14 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Welcome to BeepBeep</title>
<link href="/stylesheets/style.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<h1>BeepBeep</h1>
<p>A simple example using BeepBeep</p>
{% block content %}{% endblock %}
</body>
</html>
4 changes: 4 additions & 0 deletions priv/skel/views/home/index.html
@@ -0,0 +1,4 @@
{% extends "../base.html" %}
{% block content %}
Message is: {{ data }}
{% endblock %}
10 changes: 10 additions & 0 deletions priv/skel/views/home/show.html
@@ -0,0 +1,10 @@
{% extends "../base.html" %}
{% block content %}
You choose year: {{ year }}
<p>
Session id: {{ sid }}
</p>
<p>
Name: {{ name }}
</p>
{% endblock %}
9 changes: 9 additions & 0 deletions priv/skel/www/stylesheets/style.css
@@ -0,0 +1,9 @@
body
{
background-color: #fff; color: #333;
font-family: verdana, arial, helvetica, sans-serif;
font-size: 13px;
margin-left: 50px;
margin-top: 50px;
margin-right: 200px;
}

0 comments on commit 5cce39e

Please sign in to comment.