Generic Web Server/RESTful Router
Language: Erlang
-module(web_server).
-behaviour(gen_server).
-define(SERVER, ?MODULE).
-define(OK, <<"ok">>).
%% API
-export([start_link/1, dispatch_requests/1, stop/0]).
%% gen_server callbacks
%% (Same old, same old. You can skip down to the handle/2 function...)
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
start_link(Port) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [Port], []).
init([Port]) ->
mochiweb_http:start(
[{port, Port},
{loop, fun(Req) -> dispatch_requests(Req) end}]),
erlang:monitor(process, mochiweb_http),
{ok, []}.
stop() ->
gen_server:cast(?SERVER, stop).
dispatch_requests(Req) ->
Path = Req:get(path),
Action = clean_path(Path),
handle(Action, Req).
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({'DOWN', _, _, {mochiweb_http, _}, _}, State) ->
{stop, normal, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
mochiweb_http:stop(),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% This was ripped off from the following blog:
%% http://willcodeforfoo.com/2009/07/29/using-mochiweb-to-create-a-webframework-in-erlang/
handle(Path, Req) ->
CleanPath = clean_path(Path),
CAtom = erlang:list_to_atom(top_level_request(CleanPath)),
ControllerPath = parse_controller_path(CleanPath),
case CAtom of
home ->
IndexContents = case file:read_file("www/index.html") of
{ok, Contents} ->
Contents;
_ ->
"<html><head><title>Error</title></head><body><h1>Uh oh</h1></body></html>"
end,
Req:ok({"text/html", IndexContents});
assets ->
Req:ok(assets:get(ControllerPath));
ControllerAtom ->
Body = case Req:get(method) of
'GET' -> ControllerAtom:get(ControllerPath);
'POST' -> ControllerAtom:post(ControllerPath, decode_data_from_request(Req));
'PUT' -> ControllerAtom:put(ControllerPath, decode_data_from_request(Req));
'DELETE' -> ControllerAtom:delete(ControllerPath, decode_data_from_request(Req));
Other -> subst("Other ~p on: ~s~n", [users, Other])
end,
Req:ok({"text/html", Body})
end.
%% Helper methods:
%% Parse the request body as JSON
decode_data_from_request(Req) ->
RecvBody = Req:recv_body(),
Data = case RecvBody of
<<>> ->
erlang:list_to_binary("{}");
Bin ->
Bin
end,
{struct, Struct} = mochijson2:decode(Data),
Struct.
% Parse the URL path
parse_controller_path(CleanPath) ->
case string:tokens(CleanPath, "/") of
[] ->
[];
[_RootPath|Rest] ->
Rest
end.
%% Strip off the query string off the URL
clean_path(Path) ->
case string:str(Path, "?") of
0 ->
Path;
N ->
string:substr(Path, 1, string:len(Path) - (N+1))
end.
top_level_request(Path) ->
case string:tokens(Path, "/") of
[CleanPath|_Others] ->
CleanPath;
[] ->
"home"
end.
Reveal More

