From 5cce39ecd7ab9417067330f1ac4bb0e41580f0df Mon Sep 17 00:00:00 2001 From: Dave Bryson Date: Tue, 10 Feb 2009 18:57:38 -0600 Subject: [PATCH] Completely revamped and improved --- README.textile | 46 +++++++----- include/beepbeep.hrl | 23 +++--- priv/skel/src/home_controller.erl | 27 +++++++ priv/skel/src/skel.app | 1 + priv/skel/src/skel.erl | 6 -- priv/skel/src/skel_app.erl | 6 -- priv/skel/src/skel_deps.erl | 8 -- priv/skel/src/skel_sup.erl | 20 +++-- priv/skel/src/skel_web.erl | 110 +++++++--------------------- priv/skel/views/base.html | 14 ++++ priv/skel/views/home/index.html | 4 + priv/skel/views/home/show.html | 10 +++ priv/skel/www/stylesheets/style.css | 9 +++ src/beepbeep.erl | 104 ++++++++++++++++++++++++++ src/beepbeep_api.erl | 27 ------- src/beepbeep_args.erl | 100 +++++++++++++++++++++++++ src/beepbeep_router.erl | 109 +++++++++++++++++++++++++++ src/gen_controller.erl | 26 ------- src/mochiweb_env.erl | 107 +++++++++++++++++++++++++++ 19 files changed, 558 insertions(+), 199 deletions(-) create mode 100644 priv/skel/src/home_controller.erl create mode 100644 priv/skel/views/base.html create mode 100644 priv/skel/views/home/index.html create mode 100644 priv/skel/views/home/show.html create mode 100644 priv/skel/www/stylesheets/style.css create mode 100644 src/beepbeep.erl delete mode 100644 src/beepbeep_api.erl create mode 100644 src/beepbeep_args.erl create mode 100644 src/beepbeep_router.erl delete mode 100644 src/gen_controller.erl create mode 100644 src/mochiweb_env.erl diff --git a/README.textile b/README.textile index 15e9ea8..b04f664 100644 --- a/README.textile +++ b/README.textile @@ -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:
  
 %% 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.
 
  
 
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:

-

Hello from {{ data }}

+

Hello from {{ name }}

Which will result in: @@ -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... diff --git a/include/beepbeep.hrl b/include/beepbeep.hrl index e185149..48d3c59 100644 --- a/include/beepbeep.hrl +++ b/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"}]). diff --git a/priv/skel/src/home_controller.erl b/priv/skel/src/home_controller.erl new file mode 100644 index 0000000..0ad555c --- /dev/null +++ b/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. + + diff --git a/priv/skel/src/skel.app b/priv/skel/src/skel.app index 6bc3106..fc16aae 100644 --- a/priv/skel/src/skel.app +++ b/priv/skel/src/skel.app @@ -5,6 +5,7 @@ skel, skel_app, skel_sup, + skel_web, skel_deps ]}, {registered, []}, diff --git a/priv/skel/src/skel.erl b/priv/skel/src/skel.erl index 49263c8..af441e8 100644 --- a/priv/skel/src/skel.erl +++ b/priv/skel/src/skel.erl @@ -1,10 +1,4 @@ -%% @author author -%% @copyright YYYY author. - -%% @doc TEMPLATE. - -module(skel). --author('author '). -export([start/0, stop/0]). ensure_started(App) -> diff --git a/priv/skel/src/skel_app.erl b/priv/skel/src/skel_app.erl index 6b8228e..eff4035 100644 --- a/priv/skel/src/skel_app.erl +++ b/priv/skel/src/skel_app.erl @@ -1,10 +1,4 @@ -%% @author author -%% @copyright YYYY author. - -%% @doc Callbacks for the skel application. - -module(skel_app). --author('author '). -behaviour(application). -export([start/2,stop/1]). diff --git a/priv/skel/src/skel_deps.erl b/priv/skel/src/skel_deps.erl index b53552d..f5ed21f 100644 --- a/priv/skel/src/skel_deps.erl +++ b/priv/skel/src/skel_deps.erl @@ -1,12 +1,4 @@ -%% @author author -%% @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 '). -export([ensure/0, ensure/1]). -export([get_base_dir/0, get_base_dir/1]). diff --git a/priv/skel/src/skel_sup.erl b/priv/skel/src/skel_sup.erl index 20b40f0..f439d93 100644 --- a/priv/skel/src/skel_sup.erl +++ b/priv/skel/src/skel_sup.erl @@ -1,10 +1,4 @@ -%% @author author -%% @copyright YYYY author. - -%% @doc Supervisor for the skel application. - -module(skel_sup). --author('author '). -behaviour(supervisor). @@ -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}}. diff --git a/priv/skel/src/skel_web.erl b/priv/skel/src/skel_web.erl index bc27128..268abd0 100644 --- a/priv/skel/src/skel_web.erl +++ b/priv/skel/src/skel_web.erl @@ -1,104 +1,46 @@ -module(skel_web). -author('Dave Bryson '). --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). diff --git a/priv/skel/views/base.html b/priv/skel/views/base.html new file mode 100644 index 0000000..b1b33f8 --- /dev/null +++ b/priv/skel/views/base.html @@ -0,0 +1,14 @@ + + + + + Welcome to BeepBeep + + + +

BeepBeep

+

A simple example using BeepBeep

+ {% block content %}{% endblock %} + + diff --git a/priv/skel/views/home/index.html b/priv/skel/views/home/index.html new file mode 100644 index 0000000..b15de3b --- /dev/null +++ b/priv/skel/views/home/index.html @@ -0,0 +1,4 @@ +{% extends "../base.html" %} +{% block content %} + Message is: {{ data }} +{% endblock %} diff --git a/priv/skel/views/home/show.html b/priv/skel/views/home/show.html new file mode 100644 index 0000000..7b4bf03 --- /dev/null +++ b/priv/skel/views/home/show.html @@ -0,0 +1,10 @@ +{% extends "../base.html" %} +{% block content %} +You choose year: {{ year }} +

+ Session id: {{ sid }} +

+

+ Name: {{ name }} +

+{% endblock %} diff --git a/priv/skel/www/stylesheets/style.css b/priv/skel/www/stylesheets/style.css new file mode 100644 index 0000000..b0f66f6 --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/src/beepbeep.erl b/src/beepbeep.erl new file mode 100644 index 0000000..3869e19 --- /dev/null +++ b/src/beepbeep.erl @@ -0,0 +1,104 @@ +%% +%% Dispatcher called from the mochiweb server +%% Maps Urls to controllers and their views +%% +-module(beepbeep). +-author('Dave Bryson '). + +-export([dispatch/1,get_view_file/1]). + +dispatch(Env) -> + PathComponents = beepbeep_args:path_components(Env), + %% Map the request to our app + {ControllerName,ActionName,Args} = case PathComponents of + [] -> + {"home","index",[]}; + [C] -> + {C,"index",[]}; + [C,A | Params] -> + {C,A,Params} + end, + case beepbeep_router:get_controller(ControllerName) of + {ok,Controller} -> + process_request(Env,Controller,ActionName,Args); + no_controller -> + %% Try static + F = beepbeep_args:path(Env), + {static, F} + end. + + + +get_view_file(ViewFile) -> + %%filename:join([get_base_dir()|["views",ViewFile]]), + beepbeep_router:get_view(ViewFile). + + +%%get_static_path() -> +%% filename:join([get_base_dir()|["www"]]). + + +%%% Internal below +%%get_base_dir() -> +%% {file, Here} = code:is_loaded(?MODULE), +%% filename:dirname(filename:dirname(Here)). + +process_request(Env,ControllerName,ActionName,Args) -> + Env1 = beepbeep_args:set_action(Env,ActionName), + error_logger:info_report(Env1), + Controller = ControllerName:new(Env1), + case try_filter(Controller) of + ok -> + case catch(Controller:handle_request(ActionName,Args)) of + {'EXIT',_} -> + {error,no_action}; + Response -> + handle_response(Response) + end; + Any -> + Any + end. + +try_filter(ControllerName) -> + case catch(ControllerName:before_filter()) of + {'EXIT', {undef,_}} -> + ok; + Any -> + Any + end. + +%% Handle all responses from controller +handle_response({render,View,Data}) -> + {ok,Content} = render_template(View,Data), + {ok,200,"text/html",[],Content}; + +handle_response({render,View,Data,Options}) -> + {ok,Content} = render_template(View,Data), + {ok, + proplists:get_value(status,Options,200), + proplists:get_value(content_type,Options,"text/html"), + proplists:get_value(headers,Options,[]), + Content}; + +handle_response({text,Data}) -> + {ok,200,"text/plain",[],Data}; + +%% This seems stupid...better way?? +handle_response({redirect,Url}) -> + {redirect,Url}; + +handle_response({static,File}) -> + {static,File}. + +render_template(ViewFile,Data) -> + FullPathToFile = get_view_file(ViewFile), + error_logger:info_msg("Trying file: ~s~n",[FullPathToFile]), + Pieces = string:tokens(ViewFile,"/"), + Name = string:join(Pieces,"_"), + Name1 = filename:basename(Name,".html"), + ModName = list_to_atom(Name1 ++ "_view"), + + erlydtl:compile(FullPathToFile,ModName), + ModName:render(Data). + + diff --git a/src/beepbeep_api.erl b/src/beepbeep_api.erl deleted file mode 100644 index 305cd53..0000000 --- a/src/beepbeep_api.erl +++ /dev/null @@ -1,27 +0,0 @@ -%% -%% Helper methods...more to come -%% --module(beepbeep_api). --author('Dave Bryson '). - --export([set_session/3,get_session/2,get_param/2]). --include("beepbeep.hrl"). - -%% Set a Key:Value in the Session -set_session(Params,Key,Value) -> - beepbeep_session_server:set_session_data(Params#params.sid,Key,Value). - -%% Get a value from the session by key -get_session(Params,Key) -> - Data = beepbeep_session_server:get_session_data(Params#params.sid), - proplists:get_value(Key,Data). - -%% Get a value from the request params for a given key -get_param(Params,Key) -> - case Key of - id -> - Params#params.id; - _Any -> - Data = Params#params.data, - proplists:get_value(Key,Data) - end. diff --git a/src/beepbeep_args.erl b/src/beepbeep_args.erl new file mode 100644 index 0000000..f820a29 --- /dev/null +++ b/src/beepbeep_args.erl @@ -0,0 +1,100 @@ +%% +%% Main BeepBeep API to use in Controllers +%% +-module(beepbeep_args). +-compile(export_all). +-author('Dave Bryson '). + +%% @spec path(Env) -> Path +%% @doc return the path +path(Env) -> + proplists:get_value("PATH_INFO",Env). + +%% @spec path_components(Env) -> [] +%% @doc return the path as an array parsed on the "/" +path_components(Env) -> + string:tokens(path(Env),"/"). + +%% @spec server_software(Env) -> ServerSoftware +%% @doc return the name of the server +server_software(Env) -> + proplists:get_value("SERVER_SOFTWARE", Env). + +%% @spec server_name(Env) -> ServerName +%% @doc return the hostname of the server +server_name(Env) -> + proplists:get_value("SERVER_NAME", Env). + +%% @spec server_protocol(Env) -> ServerProtocol +%% @doc return the protocol i.e. http +server_protocol(Env) -> + proplists:get_value("SERVER_PROTOCOL", Env). + +server_port(Env) -> + proplists:get_value("SERVER_PORT", Env). + +method(Env) -> + proplists:get_value("REQUEST_METHOD", Env). + +content_type(Env) -> + proplists:get_value("CONTENT_TYPE", Env). + +content_length(Env) -> + proplists:get_value("CONTENT_LENGTH", Env). + +remote_addr(Env) -> + proplists:get_value("REMOTE_ADDR", Env). + +get_all_headers(Env) -> + lists:foldl(fun({"HTTP_" ++ _, _}=Pair, Hdrs) -> + [Pair|Hdrs]; + (_, Hdrs) -> + Hdrs + end, [], Env). + +get_param(Key,Env) -> + Params = proplists:get_value("beepbeep.data",Env), + proplists:get_value(Key,Params,"?"). + +get_session_id(Env) -> + proplists:get_value("beepbeep_sid",Env). + +set_session_id(Value,Env) -> + case lists:keysearch("beepbeep_sid",1,Env) of + {value,_} -> + set_value("beepbeep_sid",Value,Env); + false -> + [proplists:property({"beepbeep_sid", Value})|Env] + end. + +%% Helpers for accessing Session Data +set_session_data(Env,Key,Value) -> + Sid = get_session_id(Env), + beepbeep_session_server:set_session_data(Sid,Key,Value). + +get_session_data(Env) -> + Sid = get_session_id(Env), + beepbeep_session_server:get_session_data(Sid). + +get_action(Env) -> + proplists:get_value("action_name",Env). + +set_action(Env,Value) -> + case lists:keysearch("action_name",1,Env) of + {value,_} -> + set_value("action_name",Value,Env); + false -> + [proplists:property({"action_name", Value})|Env] + end. + +get_value(Key, Env) -> + proplists:get_value(Key, Env). + +get_value(Key, Env, Default) -> + proplists:get_value(Key, Env, Default). + +get_all_values(Key, Env) -> + proplists:get_all_values(Key, Env). + +set_value(Key, Val, Env) -> + lists:keyreplace(Key, 1, Env, {Key, Val}). diff --git a/src/beepbeep_router.erl b/src/beepbeep_router.erl new file mode 100644 index 0000000..90f2171 --- /dev/null +++ b/src/beepbeep_router.erl @@ -0,0 +1,109 @@ +%%%------------------------------------------------------------------- +%%% Author : Dave Bryson +%%% +%%% Description : Maps the path of the controller to the actual controller +%%% name as an atom. Prevents calling list_to_atom in the dispatcher which +%%% could lead to a potential DOS attack. Thanks to Ville for catching that +%%% +%%%------------------------------------------------------------------- +-module(beepbeep_router). +-author('Dave Bryson '). + +-behaviour(gen_server). + +%% API +-export([start/1,get_controller/1,view_map/0,get_view/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state,{view_path,controllers}). + +%% +%% Start the app with the Basedir of the application +%% Basedir is determined in the webapp supervisor +%% +start(BaseDir) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, BaseDir, []). + +%% +%% Given the name of the controller called in the URL +%% return the module name +%% +get_controller(Controller) -> + gen_server:call(?MODULE,{get_controller,Controller}). + +%% +%% Return the fullpath and name of the requested template +%% +get_view(Name) -> + gen_server:call(?MODULE,{get_view,Name}). + +%% +%% Simple helper to view the name-controller mapping +%% +view_map() -> + gen_server:call(?MODULE,view). + +init(BaseDir) -> + %% Get the base directory for views + ViewPath = filename:join([BaseDir,"views"]), + %% Load the controllers + Controllers = load_controllers(BaseDir), + State = #state{view_path=ViewPath,controllers=Controllers}, + {ok, State}. + +%% ---------Callbacks------------------------------ +handle_call({get_controller,Controller},_From, State) -> + ControllerList = State#state.controllers, + Reply = case lists:keysearch(Controller,1,ControllerList) of + {value,{_,C}} -> + {ok,C}; + false -> + no_controller + end, + {reply, Reply, State}; + +handle_call({get_view,Name},_From,State) -> + Base = State#state.view_path, + TemplatePath = filename:join([Base,Name]), + {reply,TemplatePath,State}; + +handle_call(view,_From,State) -> + ControllerList = State#state.controllers, + io:format("~p~n",[ControllerList]), + {reply,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 functions +%%-------------------------------------------------------------------- +list_controllers(BaseDir) -> + %%{file, Here} = code:is_loaded(?MODULE), + %%BaseDir = filename:dirname(filename:dirname(Here)), + Path = filename:join([BaseDir,"src","*_controller.erl"]), + filelib:wildcard(Path). + + +load_controllers(BaseDir) -> + lists:foldl(fun(File,Acc) -> + OrgName = filename:basename(File,".erl"), + {ok,KeyName,_} = regexp:gsub(OrgName,"_controller",""), + AtomName = list_to_atom(OrgName), + [{KeyName,AtomName}|Acc] + end, + [], + list_controllers(BaseDir)). + + diff --git a/src/gen_controller.erl b/src/gen_controller.erl deleted file mode 100644 index 677ca97..0000000 --- a/src/gen_controller.erl +++ /dev/null @@ -1,26 +0,0 @@ -%% This behavior should be used by all controllers. -%% It provides the hook for before_filters and others -%% in the future. - --module(gen_controller). --author('Dave Bryson '). - --export([call/3]). --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{handle_request,2},{before_filter,1}]; -behaviour_info(_Other) -> - undefined. - -%% -%% Called from the controller. Always does the filter -%% then the actual request. -%% -call(M,A,Params) -> - case M:before_filter(Params) of - {ok} -> - M:handle_request(A,Params); - Any -> Any - end. - diff --git a/src/mochiweb_env.erl b/src/mochiweb_env.erl new file mode 100644 index 0000000..dbb0125 --- /dev/null +++ b/src/mochiweb_env.erl @@ -0,0 +1,107 @@ +%% This code is adapted from the ewgi project: +%% http://code.google.com/p/ewgi/ +%% In the future BeepBeep may use the ewgi interface +%% to support both mochiweb and yaws +-module(mochiweb_env). + +-export([setup_environment/1]). + +-include("beepbeep.hrl"). + +setup_environment(Req) -> + parse_request(Req). + +nhdr(L) when is_atom(L) -> + nhdr(atom_to_list(L)); +nhdr(L) when is_binary(L) -> + nhdr(binary_to_list(L)); +nhdr(L) when is_list(L) -> + underscoreize(L, []). + +underscoreize([], S) -> + lists:reverse(S); +underscoreize([$-|T], S) -> + underscoreize(T, [$_|S]); +underscoreize([H|T], S) -> + underscoreize(T, [H|S]). + +normalize_header({K, V}) -> + {string:to_upper(string:strip(nhdr(K))), string:strip(V)}. + +parse_request(Req) -> + Hdrs = parse_headers(Req), + lists:foldl(fun({El, ElName}, PList) -> + V = parse_element(El, Req), + case V of + undefined -> PList; + V -> + NewEl = proplists:property({ElName, V}), + [NewEl|PList] + end + end, Hdrs, ?BEEPBEEP_ENV_DATA). + +parse_element(server_sw, _Req) -> + "MochiWeb"; +parse_element(server_name, Req) -> + HostPort = Req:get_header_value(host), + case HostPort of + HostPort when is_list(HostPort) -> + hd(string:tokens(HostPort, ":")); + HostPort -> HostPort + end; +parse_element(server_port, Req) -> + HostPort = Req:get_header_value(host), + case HostPort of + HostPort when is_list(HostPort) -> + case length(HostPort) of + 2 -> lists:nth(2, HostPort); + _ -> undefined + end; + _ -> + undefined + end; +parse_element(server_protocol, Req) -> + {Maj, Min} = Req:get(version), + lists:flatten(io_lib:format("HTTP/~b.~b", [Maj, Min])); +parse_element(method, Req) -> + Req:get(method); +parse_element(path_info,Req) -> + Req:get(path); +parse_element(remote_addr, Req) -> + Req:get(peer); +parse_element(beepbeep_params,Req) -> + case Req:get(method) of + Method when Method =:= 'GET'; Method =:= 'HEAD' -> + Req:parse_qs(); + _ -> + Req:parse_post() + end; +parse_element(content_type, Req) -> + Req:get_header_value("content-type"); +parse_element(content_length, Req) -> + case Req:get_header_value("content-length") of + undefined -> undefined; + Length when is_integer(Length) -> + Length; + Length when is_list(Length) -> + list_to_integer(Length) + end. + +parse_headers(Req) -> + Hdrs = Req:get(headers), + lists:foldl(fun(Pair, Acc) -> + {K1, V1} = normalize_header(Pair), + %% Don't duplicate content-length and content-type + case K1 of + "CONTENT_LENGTH" -> + Acc; + "CONTENT_TYPE" -> + Acc; + K1 -> + [{lists:append(["HTTP_", K1]), V1}|Acc] + end + end, + [], + mochiweb_headers:to_list(Hdrs)). + +