Browse files

Completely revamped and improved

  • Loading branch information...
1 parent 8803a2f commit 5cce39ecd7ab9417067330f1ac4bb0e41580f0df @davebryson committed Feb 10, 2009
View
46 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:
<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:
@@ -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...
View
23 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"}]).
View
27 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.
+
+
View
1 priv/skel/src/skel.app
@@ -5,6 +5,7 @@
skel,
skel_app,
skel_sup,
+ skel_web,
skel_deps
]},
{registered, []},
View
6 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) ->
View
6 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]).
View
8 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]).
View
20 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).
@@ -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}}.
View
110 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).
View
14 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>
View
4 priv/skel/views/home/index.html
@@ -0,0 +1,4 @@
+{% extends "../base.html" %}
+{% block content %}
+ Message is: {{ data }}
+{% endblock %}
View
10 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 %}
View
9 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;
+}
View
104 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 <http://weblog.miceda.org>').
+
+-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).
+
+
View
27 src/beepbeep_api.erl
@@ -1,27 +0,0 @@
-%%
-%% Helper methods...more to come
-%%
--module(beepbeep_api).
--author('Dave Bryson <http://weblog.miceda.org>').
-
--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.
View
100 src/beepbeep_args.erl
@@ -0,0 +1,100 @@
+%%
+%% Main BeepBeep API to use in Controllers
+%%
+-module(beepbeep_args).
+-compile(export_all).
+-author('Dave Bryson <http://weblog.miceda.org>').
+
+%% @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}).
View
109 src/beepbeep_router.erl
@@ -0,0 +1,109 @@
+%%%-------------------------------------------------------------------
+%%% Author : Dave Bryson <http://weblog.mitre.org>
+%%%
+%%% 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 <http://weblog.miceda.org>').
+
+-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)).
+
+
View
26 src/gen_controller.erl
@@ -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 <http://weblog.miceda.org>').
-
--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.
-
View
107 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)).
+
+

0 comments on commit 5cce39e

Please sign in to comment.