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
Added about 1 year ago by Segal_normal zdzolton