5.5.7 可选挂钩
与标准挂钩相比,可选挂钩基本上没有太大的差异,唯一的区别就在于可选挂钩不一定需要被实现——这看起来令人迷惑的。不过你很快就会明白了。考虑一下,如果某个挂钩Hook_A是声明在一个可选模块中,那么正常情况下该模块没有被加载。如此此时某个模块想使用挂钩Hook_A,那么会发生什么情况呢。对于标准模块,Apache可能根本就无法进行编译。而可选挂钩则可以解决这种问题。对于可选挂钩,即使它没有被导入并运行,其余的模块也可以使用它。
可选挂钩的声明方法与标准挂钩声明没有任何区别,都是通过AP_DECLARE_HOOK进行的,比如下面的语句声明一个可选挂钩:
AP_DECLARE_HOOK(int , optional_hook , (request_rec *r , int n))
与标准挂钩相比,可选挂钩没有内部私有的数据结构。在标准挂钩中,为了保存各个模块对声明的挂钩的使用情况,通过声明AP_HOOK_STRUCT结构来实现。这种做法实际上是由挂钩实现者自行进行维护;而对于可选挂钩,模块编写者可以不需要维护该AP_HOOK_STRUCT结构了,该结构则转交内核维护。
在实现上,可选挂钩的声明从标准挂钩的AP_IMPLEMENT_HOOK_RUN_ALL形式转变为AP_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL
5.5.7.1可选挂钩数组
在Apache2.0中,任何模块对可选挂钩的调用信息都由Apache核心进行维护。在Apache内部,核心定义了两个全局哈希表s_phOptionalHooks和s_phOptionalFunctions分别保存所有的可选挂钩以及对应的挂钩处理句柄。S_phOptionalHooks哈希表中,可选挂钩名称用来作为哈希表Key键,而挂钩对应的挂钩数组则作为哈希表的Value值。其结构如上图所示。在Apr_hooks.c中,Apache提供了两个支持函数:apr_optional_hook_get和apr_optional_hook_add。

apr_optional_hook_get函数用来在哈希表s_phOptionalHooks中查找指定挂钩的挂钩数组,如果找到了则返回数组;否则返回NULL。
apr_optional_hook_add函数的原型声明如下:
APU_DECLARE(void) apr_optional_hook_add(const char *szName,void (*pfn)(void),
const char * const *aszPre,
const char * const *aszSucc,int nOrder)
该函数主要在可选挂钩szName数组中,增加一个登记项,登记的挂钩函数为pfn。同时aszPre、aszSucc以及nOrder与标准挂钩的含义相同。
在登记之前,函数必须能够在哈希表中找到挂钩szName对应的挂钩数组,这个可以通过apr_optional_hook_get来完成。由于可选挂钩可以没有任何挂钩函数,因此上图中挂钩数组为NULL也是可能的,此时必须为该挂钩首先生成挂钩数组,将该挂钩数组在哈希表中与键szName关联起来,同时进行排序。
如果szName挂钩数组已经存在,则直接调用apr_array_push相关信息压入数组并赋值。
可选挂钩数组中每个元素的结构都是apr_LINK__optional_t类型,其是宏APR_DECLARE_EXTERNAL_HOOK展开的结果,apr_LINK__optional_t结构实际如下所示:
typedef struct ap_LINK_optional_t
{
ap_HOOK_optional_t *pFunc;
const char *szName;
const char * const *aszPredecessors;
const char * const *aszSuccessors;
int nOrder;
} ap_LINK_optional_t;
5.5.7.2 声明可选挂钩(APR_OPTIONAL_HOOK)
对于标准挂钩,注册使用挂钩通常使用ap_hook_name之类的函数,这些函数最终将使用信息登记到挂钩数组中去。而对于可选挂钩,由于不存在AP_HOOK_STRUCT宏,因此也就不存在挂钩数组了。在前面我们提到过,可选挂钩的保存是由Apache内核维护的,我们展开宏APR_OPTIONAL_HOOK就知道了。
APR_OPTIONAL_HOOK宏定义在ap_optional_hooks.h中: