当前位置: 首页 > 工具软件 > ejabberd > 使用案例 >

Ejabberd源码解读-ejabberd_hooks模块

易宣
2023-12-01

在ejabberd 中,hooks 是很重要的一个模块,作为系统hook(钩子),通过调用ejabberd_hooks:add函数,注册一系列方法在ets表hook中,为某些特定事件(event)钩挂相应的行为(action),使在事件(event)发生时,触发对应的所有行为(actions)

1 ejabberd_hooks启动
查看ejabberd_app.erl文件,启动过程语句

%%%启动一个supervisor,并启动和监控定义子进程
    Sup = ejabberd_sup:start_link()

ejabberd_sup:

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
    Hooks =
    {ejabberd_hooks,
     {ejabberd_hooks, start_link, []},
     permanent,
     brutal_kill,
     worker,
     [ejabberd_hooks]},
     ...
     {ok, {{one_for_one, 10, 1},
      [Hooks,
       NodeGroups,
       SystemMonitor,
       Router,
       Router_multicast,
       S2S,
       Local,
       Captcha,
       S2SInSupervisor,
       S2SOutSupervisor,
       ServiceSupervisor,
       IQSupervisor,
       FrontendSocketSupervisor,
       Listener]}}.

启动详细参数可以参照supervisor行为规范
ejabberd_hooks:

start_link() ->
    gen_server:start_link({local, ejabberd_hooks}, ejabberd_hooks, [], []).

由上述代码可知ejabberd_hooks启动的是一个gen_sever进程,作为ejabberd_sup子进程存在

init([]) ->
    ets:new(hooks, [named_table]),
    {ok, #state{}}.

gen_server:start_link/4执行后,调用init/1函数新建ets表hooks,用于存储绑定事件行为,存储格式以disco_info为例:

{{disco_info,<<"localhost">>},
 [{50,mod_offline,get_info},
  {75,mod_caps,disco_info},
  {100,mod_disco,get_info}]}

2 add hooks
add hooks是需要绑定事件行为(event-action)的模块,通过调用ejabberd_hooks:add和ejabberd_hooks:add_dist函数绑定
add在ejabberd_hooks中定义如下

%%%以下针对不同情况是否包含主机模块等参数匹配不同处理,最终都会调用gen_server:call/2
%%%然后在handle_call回调模块中实现添加
%%% @doc See add/4.
add(Hook, Function, Seq) when is_function(Function) ->
    add(Hook, global, undefined, Function, Seq).

-spec add(atom(), HostOrModule :: binary() | atom(), fun() | atom() , number()) -> ok.
add(Hook, Host, Function, Seq) when is_function(Function) ->
    add(Hook, Host, undefined, Function, Seq);

%%% @doc Add a module and function to this hook.
%%% The integer sequence is used to sort the calls: low number is called before high number.
add(Hook, Module, Function, Seq) ->
    add(Hook, global, Module, Function, Seq).

-spec add(atom(), binary() | global, atom(), atom() | fun(), number()) -> ok.

add(Hook, Host, Module, Function, Seq) ->
    gen_server:call(ejabberd_hooks, {add, Hook, Host, Module, Function, Seq}).

-spec add_dist(atom(), atom(), atom(), atom() | fun(), number()) -> ok.

add_dist(Hook, Node, Module, Function, Seq) ->
    gen_server:call(ejabberd_hooks, {add, Hook, global, Node, Module, Function, Seq}).

-spec add_dist(atom(), binary() | global, atom(), atom(), atom() | fun(), number()) -> ok.

add_dist(Hook, Host, Node, Module, Function, Seq) ->
    gen_server:call(ejabberd_hooks, {add, Hook, Host, Node, Module, Function, Seq}).
%%%handle_call回调模块
handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) ->
    HookFormat = {Seq, Module, Function},
    Reply = handle_add(Hook, Host, HookFormat),
    {reply, Reply, State};
handle_call({add, Hook, Host, Node, Module, Function, Seq}, _From, State) ->
    HookFormat = {Seq, Node, Module, Function},
    Reply = handle_add(Hook, Host, HookFormat),
    {reply, Reply, State};
%%% in-memory storage operation: Handle adding hook in ETS table
handle_add(Hook, Host, El) ->
    case ets:lookup(hooks, {Hook, Host}) of
        [{_, Ls}] ->
            case lists:member(El, Ls) of
                true ->
                    ok;
                false ->
                    NewLs = lists:merge(Ls, [El]),
                    ets:insert(hooks, {{Hook, Host}, NewLs}),
                    ok
            end;
        [] ->
            NewLs = [El],
            ets:insert(hooks, {{Hook, Host}, NewLs}),
            ok
    end.

3 delete hooks
delete hooks处理逻辑和add hooks是同样的,这里不做详细说明,可自行查看ejabberd_hooks源码
4 run hooks
在发生某个event 的时候,run相对应的hook,从而执行该hook对应的**所有**actions
1 run(Hook, Host, Args) 根据hook名字依次调用,不管每次的调用结果
2 run_fold(Hook, Host, Val, Args) 根据hook名字依次调用,每次的调用结果下次继续使用
run和run_fold在ejabberd_hooks中定义如下:

-spec run(atom(), list()) -> ok.

%%% @doc Run the calls of this hook in order, don't care about function results.
%%% If a call returns stop, no more calls are performed.
run(Hook, Args) ->
    run(Hook, global, Args).

-spec run(atom(), binary() | global, list()) -> ok.

run(Hook, Host, Args) ->
    %%%查询ets表hooks信息,然后执行run1/3
    case ets:lookup(hooks, {Hook, Host}) of
    [{_, Ls}] ->
        run1(Ls, Hook, Args);
    [] ->
        ok
    end.

-spec run_fold(atom(), any(), list()) -> any().

%%% @doc Run the calls of this hook in order.
%%% The arguments passed to the function are: [Val | Args].
%%% The result of a call is used as Val for the next call.
%%% If a call returns 'stop', no more calls are performed and 'stopped' is returned.
%%% If a call returns {stop, NewVal}, no more calls are performed and NewVal is returned.
run_fold(Hook, Val, Args) ->
    run_fold(Hook, global, Val, Args).

-spec run_fold(atom(), binary() | global, any(), list()) -> any().

run_fold(Hook, Host, Val, Args) ->
    %%%查询ets表hooks,然后执行run_fold1/4
    case ets:lookup(hooks, {Hook, Host}) of
    [{_, Ls}] ->
        run_fold1(Ls, Hook, Val, Args);
    [] ->
        Val
    end.
-spec run1([local_hook()|distributed_hook()], atom(), list()) -> ok.

run1([], _Hook, _Args) ->
    ok;
%%% Run distributed hook on target node.
%%% It is not attempted again in case of failure. Next hook will be executed
run1([{_Seq, Node, Module, Function} | Ls], Hook, Args) ->
    %%% MR: Should we have a safe rpc, like we have a safe apply or is bad_rpc enough ?
    %%%远程节点执行M:F:A
    case ejabberd_cluster:call(Node, Module, Function, Args) of
    timeout ->
        ?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p",
               [Node, {Hook, Args}]),
        run1(Ls, Hook, Args);
    {badrpc, Reason} ->
        ?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p",
               [Node, Reason, {Hook, Args}]),
        run1(Ls, Hook, Args);
    stop ->
        ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
              "Stop.", [self(), node(), Node]), % debug code
        ok;
    Res ->
        ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
              "The response is:~n~s", [self(), node(), Node, Res]), % debug code
        run1(Ls, Hook, Args)
    end;
run1([{_Seq, Module, Function} | Ls], Hook, Args) ->
    %%%本地执行M:F:A
    Res = safe_apply(Module, Function, Args),
    case Res of
    {'EXIT', Reason} ->
        ?ERROR_MSG("~p~nrunning hook: ~p", [Reason, {Hook, Args}]),
        run1(Ls, Hook, Args);
    stop ->
        ok;
    _ ->
        run1(Ls, Hook, Args)
    end.


run_fold1([], _Hook, Val, _Args) ->
    Val;
run_fold1([{_Seq, Node, Module, Function} | Ls], Hook, Val, Args) ->
    case ejabberd_cluster:call(Node, Module, Function, [Val | Args]) of
    {badrpc, Reason} ->
        ?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p",
               [Node, Reason, {Hook, Args}]),
        run_fold1(Ls, Hook, Val, Args);
    timeout ->
        ?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p",
               [Node, {Hook, Args}]),
        run_fold1(Ls, Hook, Val, Args);
    stop ->
        stopped;
    {stop, NewVal} ->
        ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
              "Stop, and the NewVal is:~n~p", [self(), node(), Node, NewVal]), % debug code
        NewVal;
    NewVal ->
        ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
              "The NewVal is:~n~p", [self(), node(), Node, NewVal]), % debug code
        run_fold1(Ls, Hook, NewVal, Args)
    end;
run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
    Res = safe_apply(Module, Function, [Val | Args]),
    case Res of
    {'EXIT', Reason} ->
        ?ERROR_MSG("~p~nrunning hook: ~p", [Reason, {Hook, Args}]),
        run_fold1(Ls, Hook, Val, Args);
    stop ->
        stopped;
    {stop, NewVal} ->
        NewVal;
    NewVal ->
        run_fold1(Ls, Hook, NewVal, Args)
    end.
 类似资料: