* src/web/*: Plugin architecture for HTTP modules (thanks to

Massimiliano Mirra)

SVN Revision: 713
This commit is contained in:
Alexey Shchepin 2007-01-25 05:53:58 +00:00
parent 456185d0d3
commit 00807235c4
5 changed files with 155 additions and 121 deletions

View File

@ -1,3 +1,8 @@
2007-01-25 Alexey Shchepin <alexey@sevcom.net>
* src/web/*: Plugin architecture for HTTP modules (thanks to
Massimiliano Mirra)
2007-01-24 Mickael Remond <mickael.remond@process-one.net>
* doc/guide.tex: Documentation for the

View File

@ -31,8 +31,15 @@
request_keepalive,
request_content_length,
request_lang = "en",
use_http_poll = false,
use_web_admin = false,
%% XXX bard: request handlers are configured in
%% ejabberd.cfg under the HTTP service. For example,
%% to have the module test_web handle requests with
%% paths starting with "/test/module":
%%
%% {5280, ejabberd_http, [http_poll, web_admin,
%% {request_handlers, [{["test", "module"], mod_test_web}]}]}
%%
request_handlers = [],
end_of_request = false,
trail = ""
}).
@ -71,16 +78,32 @@ start_link({SockMod, Socket}, Opts) ->
_ ->
ok
end,
UseHTTPPoll = lists:member(http_poll, Opts),
UseWebAdmin = lists:member(web_admin, Opts),
?DEBUG("S: ~p~n", [{UseHTTPPoll, UseWebAdmin}]),
%% XXX bard: for backward compatibility: expand web_admin and
%% http_poll in Opts respectively to {["admin"],
%% ejabberd_web_admin} and {["http-poll"], ejabberd_http_poll}
RequestHandlers =
case lists:keysearch(request_handlers, 1, Opts) of
{value, {request_handlers, H}} -> H;
false -> []
end ++
case lists:member(web_admin, Opts) of
true -> [{["admin"], ejabberd_web_admin}];
false -> []
end ++
case lists:member(http_poll, Opts) of
true -> [{["http-poll"], ejabberd_http_poll}];
false -> []
end,
?DEBUG("S: ~p~n", [RequestHandlers]),
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
{ok, proc_lib:spawn_link(ejabberd_http,
receive_headers,
[#state{sockmod = SockMod1,
socket = Socket1,
use_http_poll = UseHTTPPoll,
use_web_admin = UseWebAdmin}])}.
request_handlers = RequestHandlers}])}.
become_controller(_Pid) ->
@ -192,23 +215,44 @@ process_header(State, Data) ->
end,
#state{sockmod = SockMod,
socket = Socket,
use_http_poll = State#state.use_http_poll,
use_web_admin = State#state.use_web_admin};
request_handlers = State#state.request_handlers};
_ ->
#state{end_of_request = true}
#state{end_of_request = true,
request_handlers = State#state.request_handlers}
end;
{error, _Reason} ->
#state{end_of_request = true};
#state{end_of_request = true,
request_handlers = State#state.request_handlers};
_ ->
#state{end_of_request = true}
#state{end_of_request = true,
request_handlers = State#state.request_handlers}
end.
%% XXX bard: search through request handlers looking for one that
%% matches the requested URL path, and pass control to it. If none is
%% found, answer with HTTP 404.
process([], _) ->
ejabberd_web:error(not_found);
process(Handlers, Request) ->
[{PathPattern, HandlerModule} | HandlersLeft] = Handlers,
case lists:prefix(PathPattern, Request#request.path) of
true ->
%% LocalPath is the path "local to the handler", i.e. if
%% the handler was registered to handle "/test/" and the
%% requested path is "/test/foo/bar", the local path is
%% ["foo", "bar"]
LocalPath = lists:nthtail(length(PathPattern), Request#request.path),
HandlerModule:process(LocalPath, Request);
false ->
process(HandlersLeft, Request)
end.
process_request(#state{request_method = 'GET',
request_path = {abs_path, Path},
request_auth = Auth,
request_lang = Lang,
use_http_poll = UseHTTPPoll,
use_web_admin = UseWebAdmin} = State) ->
request_handlers = RequestHandlers} = State) ->
case (catch url_decode_q_split(Path)) of
{'EXIT', _} ->
process_request(false);
@ -225,8 +269,11 @@ process_request(#state{request_method = 'GET',
q = LQuery,
auth = Auth,
lang = Lang},
case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin},
Request) of
%% XXX bard: This previously passed control to
%% ejabberd_web:process_get, now passes it to a local
%% procedure (process) that handles dispatching based on
%% URL path prefix.
case process(RequestHandlers, Request) of
El when element(1, El) == xmlelement ->
make_xhtml_output(State, 200, [], El);
{Status, Headers, El} when
@ -247,8 +294,7 @@ process_request(#state{request_method = 'POST',
request_lang = Lang,
sockmod = SockMod,
socket = Socket,
use_http_poll = UseHTTPPoll,
use_web_admin = UseWebAdmin} = State)
request_handlers = RequestHandlers} = State)
when is_integer(Len) ->
case SockMod of
gen_tcp ->
@ -275,8 +321,7 @@ process_request(#state{request_method = 'POST',
auth = Auth,
data = Data,
lang = Lang},
case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin},
Request) of
case process(RequestHandlers, Request) of
El when element(1, El) == xmlelement ->
make_xhtml_output(State, 200, [], El);
{Status, Headers, El} when

View File

@ -24,7 +24,7 @@
setopts/2,
controlling_process/2,
close/1,
process_request/1]).
process/2]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@ -83,8 +83,7 @@ close({http_poll, FsmRef}) ->
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
process_request(#request{path = [],
data = Data} = Request) ->
process([], #request{data = Data} = Request) ->
case catch parse_request(Data) of
{ok, ID1, Key, NewKey, Packet} ->
ID = if
@ -130,7 +129,7 @@ process_request(#request{path = [],
_ ->
{200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], ""}
end;
process_request(_Request) ->
process(_, _Request) ->
{400, [], {xmlelement, "h1", [],
[{xmlcdata, "400 Bad Request"}]}}.

View File

@ -12,13 +12,18 @@
%% External exports
-export([make_xhtml/1,
process_get/2]).
error/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_http.hrl").
%% XXX bard: there are variants of make_xhtml in ejabberd_http and
%% ejabberd_web_admin. It might be a good idea to centralize it here
%% and also create an ejabberd_web.hrl file holding the macros, so
%% that third parties can use ejabberd_web as an "utility" library.
make_xhtml(Els) ->
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
{"xml:lang", "en"},
@ -48,98 +53,7 @@ make_xhtml(Els) ->
{"name", Name},
{"value", Value}])).
process_get({_, true},
#request{auth = Auth,
path = ["admin", "server", SHost | RPath],
q = Query,
lang = Lang} = Request) ->
Host = jlib:nameprep(SHost),
case lists:member(Host, ?MYHOSTS) of
true ->
US = case Auth of
{SJID, P} ->
case jlib:string_to_jid(SJID) of
error ->
unauthorized;
#jid{user = U, server = S} ->
case ejabberd_auth:check_password(U, S, P) of
true ->
{U, S};
false ->
unauthorized
end
end;
_ ->
unauthorized
end,
case US of
{User, Server} ->
case acl:match_rule(
Host, configure, jlib:make_jid(User, Server, "")) of
deny ->
{401, [], make_xhtml([?XC("h1", "Not Allowed")])};
allow ->
ejabberd_web_admin:process_admin(
Host, Request#request{path = RPath,
us = US})
end;
unauthorized ->
{401,
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
ejabberd_web:make_xhtml([{xmlelement, "h1", [],
[{xmlcdata, "401 Unauthorized"}]}])}
end;
false ->
{404, [], make_xhtml([?XC("h1", "Not found")])}
end;
process_get({_, true},
#request{auth = Auth,
path = ["admin" | RPath],
q = Query,
lang = Lang} = Request) ->
US = case Auth of
{SJID, P} ->
case jlib:string_to_jid(SJID) of
error ->
unauthorized;
#jid{user = U, server = S} ->
case ejabberd_auth:check_password(U, S, P) of
true ->
{U, S};
false ->
unauthorized
end
end;
_ ->
unauthorized
end,
case US of
{User, Server} ->
case acl:match_rule(
global, configure, jlib:make_jid(User, Server, "")) of
deny ->
{401, [], make_xhtml([?XC("h1", "Not Allowed")])};
allow ->
ejabberd_web_admin:process_admin(
global, Request#request{path = RPath,
us = US})
end;
unauthorized ->
{401,
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
ejabberd_web:make_xhtml([{xmlelement, "h1", [],
[{xmlcdata, "401 Unauthorized"}]}])}
end;
process_get({true, _},
#request{path = ["http-poll" | RPath],
q = _Query,
lang = _Lang} = Request) ->
ejabberd_http_poll:process_request(Request#request{path = RPath});
process_get(_, _Request) ->
{404, [], make_xhtml([?XC("h1", "Not found")])}.
error(not_found) ->
{404, [], make_xhtml([?XC("h1", "404 Not Found")])};
error(not_allowed) ->
{401, [], make_xhtml([?XC("h1", "401 Unauthorized")])}.

View File

@ -14,7 +14,9 @@
-vsn('$Revision$ ').
%% External exports
-export([process_admin/2,
-export([process/2,
%% XXX bard: unexported, since it is only called from process/2 now
%% process_admin/2,
list_users/4,
list_users_in_diapason/4]).
@ -54,6 +56,75 @@
{"size", Size}])).
-define(INPUTST(Type, Name, Value, Size), ?INPUT(Type, Name, ?T(Value), Size)).
process(["server", SHost | RPath], #request{auth = Auth,
q = Query,
lang = Lang} = Request) ->
Host = jlib:nameprep(SHost),
case lists:member(Host, ?MYHOSTS) of
true ->
case get_auth(Auth) of
{User, Server} ->
case acl:match_rule(
Host, configure, jlib:make_jid(User, Server, "")) of
deny ->
ejabberd_web:error(not_allowed);
allow ->
process_admin(
Host, Request#request{path = RPath,
us = {User, Server}})
end;
unauthorized ->
{401,
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
ejabberd_web:make_xhtml([{xmlelement, "h1", [],
[{xmlcdata, "401 Unauthorized"}]}])}
end;
false ->
ejabberd_web:error(not_found)
end;
process(RPath, #request{auth = Auth,
q = Query,
lang = Lang} = Request) ->
case get_auth(Auth) of
{User, Server} ->
case acl:match_rule(
global, configure, jlib:make_jid(User, Server, "")) of
deny ->
ejabberd_web:error(not_allowed);
allow ->
process_admin(
global, Request#request{path = RPath,
us = {User, Server}})
end;
unauthorized ->
%% XXX bard: any reason to send this data now and not
%% always in case of an 401? ought to check http specs...
{401,
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
ejabberd_web:make_xhtml([{xmlelement, "h1", [],
[{xmlcdata, "401 Unauthorized"}]}])}
end.
get_auth(Auth) ->
case Auth of
{SJID, P} ->
case jlib:string_to_jid(SJID) of
error ->
unauthorized;
#jid{user = U, server = S} ->
case ejabberd_auth:check_password(U, S, P) of
true ->
{U, S};
false ->
unauthorized
end
end;
_ ->
unauthorized
end.
make_xhtml(Els, global, Lang) ->
{200, [html],
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},