I wanted to have this simple thing: modulename,funname, and parameters inside the URL:
http://www.server.tld/modulename/fun/arg1/arg2
wmodulename:Fun(arg1, arg2)
(Note the 'w' prefix, I explain it later)
Here's the code:
-module(webservice).
-export([start/0, start/2, loop/2, stop/0, test/0]).
-export([dolog/2]).
-define(PORT, 8080).
start() ->
start("/home/rolphin/Devel/Web", ?PORT).
start(Wwwroot, Port) ->
Loop = fun (Req) ->
?MODULE:loop(Req, Wwwroot)
end,
mochiweb_http:start([{loop, Loop}, {name, ?MODULE}, {port, Port}]).
stop() ->
mochiweb_http:stop(?MODULE).
loop(Req, DocRoot) ->
log(Req),
case string:tokens(Req:get(path), "/") of
[ "dump" ] ->
Req:ok({"text/plain",
io_lib:format("~p~n", [Req:dump()])});
[ "favicon.ico" ] ->
Req:respond({404, [], ""});
[ "codepath" ] ->
Req:ok({"text/plain",
io_lib:format("codepath: ~p~n", [code:get_path()])});
[ "codepath", "json" ] ->
Req:ok({"text/plain",
mochijson:encode({array, code:get_path()})});
[ Path, Fun | Elems ] ->
% Every module name should begin with 'w'
dispatch(Req, DocRoot, list_to_atom("w" ++ Path), Fun, Elems);
[] ->
launch(Req, DocRoot, wdefault, do, []);
_ ->
Req:respond({502, [], []})
end.
dispatch(Req, DocRoot, Module, Fun, Elems) ->
M = Req:get(method),
case M of
'GET' ->
launch(Req, DocRoot, Module, Fun, Elems);
'POST' ->
launch(Req, DocRoot, Module, Fun, Elems);
'PUT' ->
launch(Req, DocRoot, Module, Fun, Elems);
'DELETE' ->
launch(Req, DocRoot, Module, Fun, Elems);
'HEAD' ->
launch(Req, DocRoot, Module, Fun, Elems);
_Any ->
launch(Req, DocRoot, wdefault, get, [])
end.
launch(Req, DocRoot, wcontent, Fun, Args) ->
case catch wcontent:default(Req, DocRoot, [ Fun | Args] ) of
{'EXIT', {Type, _Error}} ->
Req:ok({"text/plain",
io_lib:format("GET Error: '~p' for '~p' ~p ~p~n~p~n", [Type, wcontent, Fun, Args, _Error])});
_ ->
ok
end;
launch(Req, DocRoot, Module, Fun, Args) ->
F = list_to_atom(Fun),
case catch Module:F(Req, DocRoot, Args) of
{'EXIT', {Type, _Error}} ->
Req:ok({"text/plain",
io_lib:format("~p Error: '~p' for ~p ~p ~p~n~p~n", [Req:get(method), Type, Module, Fun, Args, _Error])});
_ ->
ok
end.
log(Req) ->
Ip = Req:get(peer),
spawn(?MODULE, dolog, [Req, Ip]).
dolog(Req, Ip) ->
stat_logger:log("~p ~p", [Ip, Req:get(path)]).
First you can see that I use "mochiweb:start" and "mochiweb:stop" in the "start" and "stop" funs.
Then parameters are extracted from the URI:
case string:tokens(Req:get(path), "/") of
I split the URI on the "/" character, and compare the resulting list with various possibilities.
Then the main part of the code:
launch(Req, DocRoot, Module, Fun, Args) ->
F = list_to_atom(Fun),
case catch Module:F(Req, DocRoot, Args) of
{'EXIT', {Type, _Error}} ->
Req:ok({"text/plain",
io_lib:format("~p Error: '~p' for ~p ~p ~p~n~p~n", [Req:get(method), Type, Module, Fun, Args, _Error])});
_ ->
ok
end.
Using "catch" prevent your app from crashing without any information. If a module doesn't exists, you'll be able to see why (the main problem is sometimes the module is not in the code:path...)
Every webservice module name should start with "w", it's some sort of namespace :), now you know the reason for the 'w' prefix:
[ Path, Fun | Elems ] ->
% Every module name should begin with 'w'
dispatch(Req, DocRoot, list_to_atom("w" ++ Path), Fun, Elems);
Now, how can I use it ? It's simple, you just have to build a module with such a template:
-module(wsample).
-export([do/3]).
do(Req, _DocRoot, Args) ->
Req:ok({"text/plain", [ <<"Hello ">>, hd(Args) , <<", you are authorized :)">>] }).
Then you build it, and store it somewhere in your "code:path". And call
in your erl shell. Then locate your webrowser to the URI
webservice:start().
www.server.tld:8080/sample/do/PUT-ANYTHING-YOU-WANT-HERE
As a sample module, here is a module that is used everyday the "mobile tags" generator:
-module(wbarcode).
-export([do/3]).
-define(PNG, "image/png").
do(Req, _DocRoot, _Args) ->
barcode(Req).
% the process 'barcode' must be there
barcode(Req) ->
Options = Req:parse_qs(),
%io:format("~p~n", [Options]),
Text = proplists:get_value("text", Options, "http://www.shootit.fr"),
Res = proplists:get_value("res", Options, "72"),
Z = proplists:get_value("z", Options, "1"),
case barcode:generate('barcode', Text) of
timeout ->
Req:ok({"text/plain", [], <<"Timeout">>});
{data, {Width, Height}, Data} ->
Zoom = list_to_integer(Z),
Xres = integer_to_list(list_to_integer(Res) * Zoom),
Yres = integer_to_list(list_to_integer(Res) * Zoom),
Params = [ {"PS", [integer_to_list(Width + 1), integer_to_list(Height + 1)]}, {"HW", [Xres, Yres]} ],
case gs:draw(gs, Data, Params) of
{png, Image} ->
Req:ok({?PNG, Image});
_E ->
Req:ok({"text/plain", [], [ <<"Error: ">>,
io_lib:format("~s", [_E])]})
end
end.