XMPP eJabberd 的 Hook 系统

DavidAlphaFox · 发布于 2017年09月23日 · 545 次阅读
84794b
本帖已被设为精华帖!

什么是Hook

Hook的广义理解

  1. Hook直译为“钩子”,钩子就是在事件传送到终点前截获并监控事件的传输或使用内存改写技术改写某个函数的入口点,并且能够在钩上事件时或替换函数后,处理一些自己特定的事件;
  2. Hook使它能够将自己的代码“融入”被勾住(Hook)的进程中,成为目标进程的一部分;
  3. Hook可以在不改变核心流程的情况,进行一些额外操作。

eJabberd的Hook

eJabberd中的Hook可以认为是eJabberd的插件系统,由于Erlang的语言的特性导致eJabberd不是很容易实现常见的插件系统。因此eJabberd设计了Hook系统,Hook系统完全是基于eJabberd的核心流程和事件订阅发布机制。

在eJabberd中,eJabbrd预先定义了一系列的事件。在eJabberd中,每个模块都可以订阅这些事件,当这些事件发生时,订阅的模块就会被调用起来,进行按顺序的操作。

eJabberd的Hook的核心功能

  1. 管理eJabberd中的所有订阅者(Hook模块);
  2. 事件发生时,调用订阅者处理事件;

Hook系统如何工作

Hook系统特性

  1. 分域名管理hook,eJabberd支持一个系统绑定多个域名,所以Hook系统可以为每个域名管理一组hook;
  2. 按优先级调用,优先级值越小的hook,越先被调用;
  3. 事件调用动作在事件发布者进程中执行;
  4. hook安全执行,hook不管引起何种异常,均不影响事件发布者进程;

Hook系统的启动

eJabberd的ejabberd_hooks进程是一个非常规范的gen_server进程,启动之后会创建一个名为hooks的命名ets表。

Hook的添加和删除

eJabberd的hook是具备优先级的,所以在添加hook的时候,会对将新的hook放入原有hook表中进行排序,请看下面的代码:

handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) ->
    Reply = case ets:lookup(hooks, {Hook, Host}) of
                [{_, Ls}] ->
                    El = {Seq, Module, Function},
                    case lists:member(El, Ls) of
                        true ->
                            ok;
                        false ->
                            NewLs = lists:merge(Ls, [El]),
                            ets:insert(hooks, {{Hook, Host}, NewLs}),
                            ok
                    end;
                [] ->
                    NewLs = [{Seq, Module, Function}],
                    ets:insert(hooks, {{Hook, Host}, NewLs}),
                    mongoose_metrics:create_generic_hook_metric(Host, Hook),
                    ok
            end,
    {reply, Reply, State};
handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) ->
    Reply = case ets:lookup(hooks, {Hook, Host}) of
                [{_, Ls}] ->
                    NewLs = lists:delete({Seq, Module, Function}, Ls),
                    ets:insert(hooks, {{Hook, Host}, NewLs}),
                    ok;
                [] ->
                    ok
            end,
    {reply, Reply, State};

由于ets表不具备事务性操作,ejabberd_hooks使用handle_call序列化这一添加和删除操作,保证操作顺序。在ets表中保存的是已经经过排序好的hook列表,这样可以大大减少每次执行hook所需要的时间。

Hook的触发

因为所有的ejabberd_c2s进程都会使用Hook系统,如果将调用都让ejabberd_hooks进程来操作, 会产生大量的延迟甚至会引起进程的崩溃。因此ejabberd的Hook系统,选择由事件的发布者进程来执行这些hook的调用的操作。请看下面的代码:

run_fold(Hook, Val, Args) ->
    run_fold(Hook, global, Val, Args).

run_fold(Hook, Host, Val, Args) ->
%% 针对Host取出相应的hook
    Res = case ets:lookup(hooks, {Hook, Host}) of
        [{_, Ls}] ->
            mongoose_metrics:increment_generic_hook_metric(Host, Hook),
            run_fold1(Ls, Hook, Val, Args);
        [] ->
            Val
    end,
    record(Hook, Res).

Hook系统将自身的ets直接暴露出来,让所有的事件发布者进程直接查询订阅相关事件的hook,之后使用一个内部函数run_fold1来进行安全执行。

run_fold1([], _Hook, Val, _Args) ->
    Val;
run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
    Res = hook_apply_function(Module, Function, Hook, 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.
%% 优先处理匿名函数
hook_apply_function(_Module, Function, _Hook, Val, Args) when is_function(Function) ->
    safely:apply(Function, [Val | Args]);
%% 处理模块到处的函数    
hook_apply_function(Module, Function, Hook, Val, Args) ->
    Result = safely:apply(Module, Function, [Val | Args]),
    record(Hook, Module, Function, Result).

safely:apply函数就是使用try catch包裹要执行的函数,捕获所有异常从而不影响事件发布进程的执行。

在hook被执行的过程中,高优先级的hook可以通过返回stop或{stop,NewVal}来阻止低优先级的hook被执行。同时在hook链的实行过程中,高优先级的hook可以改变低优先级hook被执行时传入的初始值。在整个hook链结束执行的时候,会返回最后一个hook的执行结果给事件发布者,事件发布者可以根据这个值进行进一步的操作。

开发Hook的注意事项

  1. hook操作不应该是一个无法退出的循环操作,因为这会影响到原有流程的执行,会引发不可预知的事情;
  2. hook函数尽量不要包含私有的上下文,虽然eJabberd的Hook系统中可以这样做,但是非常不推荐,因为Hook系统不会关联事件发布者到hook函数中;
  3. hook的函数尽量不要做耗时的超做,因为这会占用事件发布进程的时间片,影响消息的效率;
共收到 0 条回复
84794b DavidAlphaFox 将本帖设为了精华贴 09月23日 11:31
需要 登录/注册 后方可回复, 如果你还没有账号请点击这里 注册