chut / src / chut_webserver.erl

-module(chut_webserver).
-behaviour(gen_server).
-define(SERVER, ?MODULE).

%% API
-export([start_link/1, dispatch_requests/1, stop/0]).
%% gen_server callbacks
-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]) ->
    process_flag(trap_exit, true),
    io:format("~p (~p) starting on port ~p...~n", [?MODULE, self(), Port]),
    mochiweb_http:start([{port, Port},
                         {loop, fun(Req) -> dispatch_requests(Req) end}]),
    {ok, []}.

stop() ->
    gen_server:cast(?SERVER, stop).

dispatch_requests(Req) ->
    Path = Req:get(path),
    handle(Path, 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}.

%% Internal functions
handle("/message", Req) ->
    io:format("message call~n"),
    Params = Req:parse_qs(),
    From = proplists:get_value("from", Params),
    To = proplists:get_value("to", Params),
    Message = proplists:get_value("msg", Params),
    ok = check_params(Req, [From, To, Message], ["from", "to", "msg"]),
    {ok, H} = chut_client:connect(From),
    chut_client:message(From, To, Message),
    chut_client:disconnect(From, H),
    %% we clear the event messages because mochiweb re-uses the same procs
    %% over and over again in a pool. Otherwise, we leak memory and when the
    %% listen call gets the right message process, a backlog gets printed to
    %% the user.
    lib:flush_receive(),
    reply(Req, mochijson2:encode(ok), Params);

handle("/listen", Req) ->
    io:format("listen call~n"),
    Params = Req:parse_qs(),
    Nick = proplists:get_value("id", Params),
    {ok,Handler} = chut_client:connect(Nick),
    Msgs = chut_client:listen(Nick,Handler),
    JSON = mochijson2:encode([json_prepare(M) || M <- Msgs]),
    chut_client:disconnect(Nick, Handler),
    reply(Req, JSON, Params);

handle("/history", Req) ->
    io:format("history call~n"),
    Params = Req:parse_qs(),
    Nick = proplists:get_value("id",Params),
    {ok, Handler} = chut_client:connect(Nick),
    Msgs = chut_client:history(Nick),
    JSON = mochijson2:encode([json_prepare(M) || M <- Msgs]),
    chut_client:disconnect(Nick, Handler),
    reply(Req, JSON, Params);

handle(Path, Req) ->
    io:format("Unexpected ~p call~n", [Path]),
    Params = Req:parse_qs(),
    reply(Req, mochijson2:encode([error,list_to_binary(Path)]), Params).

%% have to convert lists to binaries to be strings in json
%% also gotta change the internal format of things to make it fit
json_prepare({Verb=[_|_],To,Msg}) -> json_prepare({unicode:characters_to_binary(Verb),To,Msg});
json_prepare({Verb,To=[_|_],Msg}) -> json_prepare({Verb,unicode:characters_to_binary(To),Msg});
json_prepare({Verb,To,Msg=[_|_]}) -> json_prepare({Verb,To,unicode:characters_to_binary(Msg)});
json_prepare({Verb,To,Msg}) ->
    {struct, [{action,Verb},
              {to,To},
              {message,mochiweb_html:escape(Msg)}]}.

reply(Req, JSON, Params) ->
    Callback = proplists:get_value("callback", Params),
    if Callback =/= undefined ->
        Req:respond({200, [{"Content-Type", "text/javascript"}],
                    [Callback,$(,JSON,$),$;]});
       Callback =:= undefined ->
        Req:respond({400, [], "Callback undefined"})
    end.
            
%% replies with an error whenever some parameters are undefined      
check_params(Req, Params, Names) ->
    case lists:any(fun(X) -> X =:= undefined end, Params) of
        true -> Req:respond({400,
                             [],
                             ["Some parameters are undefined:",
                              string:join(Names, ", ")]});
        false -> ok
    end.
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.