5.5.1 为什么引入挂钩
在Apache1.3版本中,对HTTP 请求的处理包括若干个固定阶段,比如地址转换阶段、身份确认阶段、身份认证阶段、权限确认阶段、MIME类型识别阶段等等,这也意味着Apache1.3 中的挂钩数目是有限的,固定的。这个反映在模块结构中就是针对每个HOOK都对应一个函数指针。比如如果需要检查用户的身份是否合法则只需要调用 ap_check_user_i;如果需要核查用户的权限是否满足则只需要调用auth_checker;而如果需要记录日志,则只需要调用logger 函数即可。这种对请求的处理策略非常清晰明了。不过这种方法也有明显的局限性,首先就是所有的模块都维护所有的挂钩,但对于某个模块而言只有很少的几个挂 钩会被使用,比如ap_check_user_id挂钩可能只有安全模块才用到,模块中不使用的挂钩都被置为NULL;最重要的问题是很难增加新的挂钩。 如果需要增加一个新的挂钩,就必须修改module结构,这涉及到所有的模块的修改,工作量很大,而且需要重新进行编译,因此这种策略使得模块的扩展性能 极差。
为了使得Apache 2.0版本为了能够更具模块化,更具扩展性,apache采取了更加灵活的处理策略,即“模块自行管理”策略,即挂钩(Hooks)的概念,Hooks的 使用使得函数从静态的变为动态的,模块编写者可以通过Hooks自行增加处理句柄,而不需要所有的模块都千篇一律。因此每次增加新函数,唯一必须修改的就 是增加函数的模块而已。
为了对挂钩有大体的了解,我们首先来看一下Apache2.0的HTTP请求处理流程。
从大的方面来看,Apache对HTTP的请求可以分为连接、处理和断开连接三个阶段。从小的方面 而言,每个阶段又可以分为更多的子阶段。比如对HTTP的请求,我们可以进一步划分为客户身份验证、客户权限认证、请求校验、URL重定向等阶段,每一个 阶段调用相应的函数进行处理。在Apache中,这些子阶段可以用术语“挂钩(HOOK)”来描述。Apache中对请求的处理过程实质上就是依次调用一 系列挂钩的过程,不过由于HTTP请求类型的不同,它们所对应的挂钩数目和类型也不尽相同。对于典型的HTTP请求,有一个默认的挂钩调用顺序,你可以按 照这个默认的顺序进行调用,也可以不遵守这个顺序,你可以根据自己的情况调整调用顺序。整个HTTP的请求可以用下图来描述:
在上面的图示中,存在六种不同的挂钩,对于请求a,其指需要调用Hook2.、1;而对于请求b,则需要调用1、6、5;请求c则需要调用1、4、3、6四个挂钩。
在Apache中,挂钩总是和挂钩函数联系在一起的。挂钩是用来表示在处理HTTP请求中一组类似的操作,与之 对应,挂钩函数就是操作函数。不过即使挂钩相同,对应的挂钩函数也未必相同。举个简单的例子,配置文件模块和虚拟主机模块都需要身份验证挂钩来确认访问者 能否访问相应的资源,但两个模块的验证方法则差别很大。因此一个挂钩同时是和一组挂钩函数联系在一起的。因此请求处理过程中,调用挂钩的时候实际上就是调 用挂钩函数组。调用过程可以用下图示意。Apache对指定挂钩的挂钩函数调用有两种,一种就是将挂钩函数组中的每个函数都调用一次,比如图中的挂钩4; 另一种就是从前往后调用,一旦找到合适的就停止继续调用,图中1、2、3就是这种情况。
通过挂钩机制,你可以自行修改服务器的行为,比如修改挂钩函数或者增加挂钩函数,甚至增加挂钩。不过增加挂钩只是2.0才提供的功能。
5.5.2 声明挂钩
Apache中关于挂钩的实现大部分是通过宏来实现的,而且这些宏大多数非常复杂。挂钩的实现主要定义在文件apr_hook.h和apr_hook.c中,另外在config.c中也有部分定义。
Apache中对挂钩的使用总是从定义一个挂钩开始的,在Apache中声明一个挂钩,总是通过宏
#define AP_DECLARE_HOOK(ret,name,args) /
APR_DECLARE_EXTERNAL_HOOK(ap,AP,ret,name,args)
来实现的。在该宏中,ret是定义的挂钩 的返回类型;而name则是定义的挂钩的名称;args则是挂钩函数需要的额外参数,通常是以bracket形式出现的。例如下面的语句就声明了一个返回 类型为整数,函数名称为do_something,函数参数为(request_rec *r,int n)的挂钩:
AP_DECLARE_HOOK(int , do_something , (request_rec *r , int n))
不过AP_DECLARE_HOOK内部则是调用的APR_DECLARE_EXTERNAL_HOOK宏。该宏定义如下:
#define APR_DECLARE_EXTERNAL_HOOK(ns,link,ret,name,args) /
typedef ret ns##_HOOK_##name##_t args; /
link##_DECLARE(void) ns##_hook_##name(ns##_HOOK_##name##_t *pf, /
const char * const *aszPre, /
const char * const *aszSucc, int nOrder); /
link##_DECLARE(ret) ns##_run_##name args; /
APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name); /
typedef struct ns##_LINK_##name##_t /
{ /
ns##_HOOK_##name##_t *pFunc; /
const char *szName; /
const char * const *aszPredecessors; /
const char * const *aszSuccessors; /
int nOrder; /
} ns##_LINK_##name##_t;
从上面的定义可以看出,该宏定义冗长,而且事实上,Apache中的关于挂钩定义的每个宏都是很长的。分析颇为费力。在该宏中出现最多的符号当之不让的就是“##”。##宏主要用来实现连接前后的字符串。
APR_DECLARE_EXTERNAL_HOOK宏展来后实现了五个子功能的定义,这几个功能我们分别介绍,其所用的示例则是配置模块的post_config挂钩,在Http_config.中Apache定义了post_config挂钩如下:
AP_DECLARE_HOOK(int,post_config,(apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp,server_rec *s)),该宏进一步替换展开后为
APR_DECLARE_EXTERNAL_HOOK(ap,AP,int,post_config, (apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp,server_rec *s)),在该宏中:
(1)、typedef ret ns##_HOOK_##name##_t args;
该宏主要用来定义对应的post_config的挂钩的执行函数原型,比如post_config挂钩执行函数原型定义则是:
typedef int ap_HOOK_post_config_t (apr_pool_t *pconf,apr_pool_t *plog,apr_pool_t *ptemp,server_rec *s);
(2)、link##_DECLARE(void) ns##_hook_##name(ns##_HOOK_##name##_t *pf, /
const char * const *aszPre, /
const char * const *aszSucc, int nOrder); /
该宏则用来定义指定挂钩的函数原型,其中ns##_HOOK_##name##_t *pf则是前面定义了对应于该挂钩的执行函数指针,这个函数最终会加入到在请求处理期间的指定时刻进行调用的列表中,而aszPre和aszSucc在形 式和功能上都很相近,都是以NULL为结束符的字符串数组,比如{“mod_mime.c”,NULL}。aszPre数组会为这个挂钩规定必须在这个函 数之前调用的函数模块,而aszSucc则是规定必须在这个函数之后进行调用的函数模块。上面的post_config挂钩其定义展开后如下:
AP_DECLARE(void) ap_hook_post_config(ap_HOOK_post_config* pf;
const char * const *aszPre,
const char * const *aszSucc,
int nOrder);
函数的第四个参数nOrder则是该挂钩的综合排序参数,整数类型。该整数代表了这个函数与所有的 其余同一阶段的其他函数相比较时候的综合位置。如果这个数值越低,则这个挂钩函数将在列表中排列越靠前,因此也越早被调用。如果两个模块为它们的模块设定 相同的nOrder,而且模块之间没有依赖关系,则它们的调用顺序则不确定。针对这个参数,Apache提供了5个通用的宏: APR_HOOK_REALLY_FIRST、APR_HOOK_FIRST、APR_HOOK_MIDDLE、APR_HOOK_LAST、 APR_HOOK_REALLY_LAST,其定义如下:
#define APR_HOOK_REALLY_FIRST (-10)
#define APR_HOOK_FIRST 0
#define APR_HOOK_MIDDLE 10
#define APR_HOOK_LAST 20
#define APR_HOOK_REALLY_LAST 30
从上面可以看出,nOrder的值得范围 介于-10到30之间。如果函数是在列表中是必须第一个得到的,则必须将其设置为APR_HOOK_FIRST或者 APR_HOOK_REALLY_FIRST;如果是必须最后一个调用的,则将其设置为APR_HOOK_LAST或者 APR_HOOK_REALLY_LAST。如果其调用次序无关紧要,则可以设置为APR_HOOK_MIDDLE。
所有挂钩最终通过排序函数进行,这个我们在后面的部分介绍。
(3)、link##_DECLARE(ret) ns##_run_##name args; /
挂钩的定义最终是为了被调用,因此Apache中定义了对挂钩的调用函数。通常挂钩调用函数形式如下:ap_run_hookname();上面的宏真是用来定义挂钩调用函数。
比如对于post_config挂钩而言,其对外的调用函数则是ap_run_post_config()。
(4)、APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name); /
该宏展开后如下所示
#define APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name) /
link##_DECLARE(apr_array_header_t *) ns##_hook_get_##name(void)
该宏了用于返回挂钩访问函数原型,在模块外部可以调用改函数获得注册为该挂钩的所有函数。参数ns 和link分别为挂钩函数名字空间前缀和联接申明前缀,一般情况为ap和AP,name为挂钩名。访问函数的原型一般为AP_DECLARE (apr_array_header_t *) ap_hook_get_name(void)。如果对于post_config挂钩而言,该宏对应的则是AP_DECLARE (apr_array_header_t *) ap_hook_get_post_config(void)。通过该函数,Apache可以获取挂钩定义的原型。
(5)、typedef struct ns##_LINK_##name##_t /
{ /
ns##_HOOK_##name##_t *pFunc; /
const char *szName; /
const char * const *aszPredecessors; /
const char * const *aszSuccessors; /
int nOrder; /
} ns##_LINK_##name##_t;
该宏定义了一个结构类型,用来保存挂钩的相关定义信息,比如挂钩的调用函数指针、挂钩名称等等,这些信息与在挂钩定义宏中的信息完全相同。比如对于post_config来说,其展开后的结果如下所示,结构中每个字段的含义前面已经说明,此处不再赘述。
typedef struct ap_LINK_post_config_t
{
ap_HOOK_post_config_t *pFunc;
const char *szName;
const char * const *aszPredecessors;
const char * const *aszSuccessors;
int nOrder;
} ap_LINK_post_config_t;
为此,我们可以看到,尽管对外我们声明的仅仅是AP_DECLARE_HOOK宏,而内部却一口气实现了五个功能,每个功能彼此相关。
5.5.3 挂钩链声明(APR_HOOK_LINK)
在Apache中,系统定义了一定数量的 挂钩,这些挂钩总的来说可以分为两大类:启动挂钩和请求挂钩。启动挂钩是随着服务器启动进行调用的挂钩;而请求挂钩则是服务器处理请求时候进行调用的挂 钩。这两种挂钩的实质唯一区别就是它们在服务器中的平均调用频率以及它们在被调用时候服务器充当的角色。所有启动挂钩作为启动服务器的用户进行调用,通常 是指UNIX超级用户,而所有请求挂钩都是作为配置文件中指定的用户进行调用。
Apache中预定义了大量的挂钩,启动挂钩有pre_config、 post_config、open_logs以及child_init;请求挂钩包括pre_connection、 process_connection、create_request等等。Apache中声明的挂钩通常都是全局的,存在且仅存在一个。不过对应于挂钩 的调用函数则通常不止一个。比如对于挂钩post_config,在核心模块core.c中调用的挂钩函数是core_post_config,在 status模块mod_status.c中调用的挂钩函数是status_init,而在include模块mod_include.c中对应的挂钩函 数则是include_post_config。对于同一个挂钩,不同模块对应于其的处理函数各不相同,为了能够保存各个模块中对同一挂钩的使用信息, Apache使用apr_array_header_t数组进行保存。
为此该宏的定义很简单,实际上无非就是声明了一个apr_array_header_t类型的数组,用来保存对应于某个挂钩的所有挂钩函数。比如APR_HOOK_LINK(post_config)展开后定义如下:
apr_array_header_t *link_post_config;
各个模块中对挂钩post_config 进行处理的信息都保存在数组link_post_config中,其中link_post_config数组中每个元素的类型都是 ap_LINK_post_config_t结构;当然不同的数组,其类型也不相同,不过形式上都是ap_LINK_XXXX格式的。关于挂钩的大部分信 息都由这个结构提供。
5.5.4 挂钩结构(APR_HOOK_STRUCT)
正如前面所说,对于每一个挂钩,Apache 都会定义一个apr_array_header_t数组来保存它的相关信息。通常,该数组定义在实现挂钩的文件中,比如在config.c文件 Apache实现了header_parser、pre_config、post_config、open_logs、child_init以及 handler、quick_handler、optional_fn_retrieve八个挂钩,那么该文件中就应该定义八个数组来保存它们的信息。一 旦定义挂钩数组,那么该数组将在整个Apache中保持唯一。当某个模块想要使用该挂钩的时候,其就向模块内对于该挂钩的处理结构压入数组。为了便于各模 块对数组的访问,原则上必须将数组声明为全局变量,这是最简单的实现方式。
不过Apache2.0中并不支持直接访问挂钩数组,因此你想直接将数据压入处理结构那是“妄想”。为此Apache2.0中引入了APR_HOOK_STRUCT宏。该宏定义如下:
#define APR_HOOK_STRUCT(members) /
static struct { members } _hooks;
该宏展开后实际上定义了一个限于模块内使用的结构_hook,该模块内实现的所有挂钩的对应数组都保存为_hook的成员。比如config.c中的APR_HOOK_STRUCT定义如下:
APR_HOOK_STRUCT(
APR_HOOK_LINK(header_parser)
APR_HOOK_LINK(pre_config)
APR_HOOK_LINK(post_config)
APR_HOOK_LINK(open_logs)
APR_HOOK_LINK(child_init)
APR_HOOK_LINK(handler)
APR_HOOK_LINK(quick_handler)
APR_HOOK_LINK(optional_fn_retrieve)
)
其展开后变为如下:
static struct{
apr_array_header_t *link_header_parser;
apr_array_header_t *link_pre_config;
apr_array_header_t *link_post_config;
apr_array_header_t *link_open_logs;
apr_array_header_t *link_child_init;
apr_array_header_t *link_handler;
apr_array_header_t *link_quick_handler;
apr_array_header_t *link_optional_fn_retrieve;
}_hook;
因此任何对挂钩数组的访问都必须通过_hook来实现。比如我们在某个模块中使用挂钩post_config,那么在数组中增加数据可以用下面的代码:
ap_LINK_post_config_t *pHook;
if (!_hooks.link_post_config) {
_hooks.link_post_config = apr_array_make(apr_hook_global_pool, 1,
sizeof(ap_LINK_post_config_t));
apr_hook_sort_register("post_config", &_hooks.link_post_config);
}
pHook = apr_array_push(_hooks.link_post_config);
pHook->pFunc = …;
pHook->aszPredecessors = …;
pHook->aszSuccessors = …;
pHook->nOrder = …;
pHook->szName = …;
不过有几点必须注意的是:
(1)、_hook在某个文件中如果定义,则只能定义一次。该文件中使用的定义的挂钩数组统统添加到该结构中,正所谓“大肚能容,容天下难容之事”。
(2)、_hook的定义为 static,这意味这该结构实际上是模块内部的私有结构,外部模块无法直接访问_hook结构,而且_hook结构不仅仅在一个文件中出现,只要实现了 挂钩,按道理就应该有一个_hook结构。尽管如此,由于static属性,它们相互之间“绝缘”,不会相互干扰。
(3)、当某个模块想使用某个挂钩,比如post_config的时候,其一不能直接访问post_config挂钩数组,二不能访问被屏蔽的模块内_hook,那么它应该怎么办呢?它只能使用挂钩注册函数。正如前面所述,挂钩注册函数通常形如ap_hook_name。比如模块需要使用post_config挂钩,其必须调用ap_hook_post_config进行注册。现在如果我们将在编写模块的时候遇到类似下面的代码,你就不会纳闷了:
static void register_hooks(apr_pool_t *p)
{
ap_hook_handler(status_handler, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_post_config(status_init, NULL, NULL, APR_HOOK_MIDDLE);
}
register_hooks是模块mod_status.c中的挂钩注册函数,在该模块中,Apache注册了两个挂钩:handler和status_init,分别对应的挂钩函数为status_handler和status_init。
不过即使你查遍Apache的是所有的文件,你也不可能找到ap_hook_handler和ap_hook_post_config的函数声明和实现。不仅如此,所有的ap_hook_HOOKNAME形式的函数你都不可能找到你平常希望的函数声明和实现。那么Apache到底是在哪儿实现了这些挂钩函数呢??一切答案都在宏APR_IMPLEMENT_EXTERNAL_HOOK_BASE里面。
5.5.5 挂钩函数(APR_IMPLEMENT_EXTERNAL_HOOK_BASE)
从宏的名字我们就可以大体看出该宏实际上是实现了具体的挂钩注册函数,如果将其展开后我们会更加一目了然。该宏的定义也是冗长的很,如下所示:
#define APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) /
link##_DECLARE(void) ns##_hook_##name(ns##_HOOK_##name##_t *pf, /
const char * const *aszPre, const char * const *aszSucc,int nOrder) /
{ /
ns##_LINK_##name##_t *pHook; /
if(!_hooks.link_##name) /
{ /
_hooks.link_##name=apr_array_make(apr_hook_global_pool,1,sizeof(ns##_LINK_##name##_t)); /
apr_hook_sort_register(#name,&_hooks.link_##name); /
} /
pHook=apr_array_push(_hooks.link_##name); /
pHook->pFunc=pf; /
pHook->aszPredecessors=aszPre; /
pHook->aszSuccessors=aszSucc; /
pHook->nOrder=nOrder; /
pHook->szName=apr_hook_debug_current; /
if(apr_hook_debug_enabled) /
apr_hook_debug_show(#name,aszPre,aszSucc); /
} /
APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name) /
{ /
return _hooks.link_##name; /
}
对于post_config挂钩,我们该宏展开的结果如下:
AP_DECLARE(int) ap_hook_post_config(ap_HOOK_post_config_t *pf,
const char * const *aszPre,
const char * const *aszSucc,
int nOrder)
{
ap_LINK_post_config_t *pHook;
if (!_hooks.link_post_config) {
_hooks.link_post_config = apr_array_make(apr_hook_global_pool, 1,
sizeof(ap_LINK_post_config_t));
apr_hook_sort_register("post_config", &_hooks.link_post_config);
}
pHook = apr_array_push(_hooks.link_post_config);
pHook->pFunc = pf;
pHook->aszPredecessors = aszPre;
pHook->aszSuccessors = aszSucc;
pHook->nOrder = nOrder;
pHook->szName = apr_hook_debug_current;
if (apr_hook_debug_enabled)
apr_hook_debug_show("post_config", aszPre, aszSucc);
}
AP_DECLARE(apr_array_header_t *) ap_hook_get_post_config(void) {
return _hooks.link_post_config;
}
从上面的展开结果中我们可以很清楚的看出APR_IMPLEMENT_EXTERNAL_HOOK_BASE宏实现了我们所需要的挂钩注册函数以及挂钩信息获取函数。
挂钩注册函数中的很多代码非常熟悉,一看原来就是前面我们APR_HOOK_STRUCT中用过的示例代码。挂钩注册函数首先检查挂钩数组是否为空,如果为空则说明是第一次注册该挂钩,所以创建新数组并注册该挂钩类型以供以后排序用;否则,直接加入一条记录。
5.5.6 使用挂钩
一旦各个模块在挂钩数组中注册了自己感兴趣的挂钩,那么剩下的事情无非就是调用这些挂钩,实际上也就是最终调用挂钩对应的函数。Apache中的挂钩调用函数形式通常如ap_run_HOOKNAME所 示,比如ap_run_post_config就是调用post_config挂钩。尽管所有的挂钩对外提供的调用形式都是一样的,但是内部实现却不尽相 同,分别体现于三个宏:AP_IMPLEMENT_HOOK_VOID、AP_IMPLEMENT_HOOK_RUN_FIRST以及 AP_IMPLEMENT_HOOK_RUN_ALL。
(1)、对于AP_IMPLEMENT_HOOK_VOID,调用函数将逐个的调用挂钩数组中的所有的挂钩函数,直到遍历调用结束或者发生错误为止。这种类型通常称之为VOID,是由于其没有任何返回值,其声明如下:
#define APR_IMPLEMENT_EXTERNAL_HOOK_VOID(ns,link,name,args_decl,args_use) /
APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) /
link##_DECLARE(void) ns##_run_##name args_decl /
{ /
ns##_LINK_##name##_t *pHook; /
int n; /
/
if(!_hooks.link_##name) /
return; /
/
pHook=(ns##_LINK_##name##_t *)_hooks.link_##name->elts; /
for(n=0 ; n < _hooks.link_##name->nelts ; ++n) /
pHook[n].pFunc args_use; /
}
比如对于config.c中的child_init挂钩,其就是VOID类型,声明语句如下:
AP_IMPLEMENT_HOOK_VOID(child_init,
(apr_pool_t *pchild, server_rec *s),
(pchild, s))
撇去APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name)不管,这里我们仅仅关心剩下的的展开结果,如下:
AP_DECLARE(void) ap_run_child_init(apr_pool_t *pchild,server_rec* s)
{
ap_LINK_child_init_t pHook;
int n;
if(!_hooks.link_child_init)
return;
pHook=(ap_LINK_child_init_t)_hooks.link_child_init->elts;
for(n=0;n<_hooks.link_child_init->nelts;++n)
pHook[n].pFunc(pchild, s);
}
从展开结果可以看出,即使在逐次调用过程中发生了错误,调用也不会停止,它就是“一头拉不回头的牛”。
(2)、AP_IMPLEMENT_HOOK_ALL简称ALL类型,其与AP_IMPLEMENT_HOOK_VOID几乎相同,唯一不同的就是ALL类型具有返回值。宏声明如下:
#define APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ns,link,ret,name,args_decl,args_use,ok,decline) /
APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) /
link##_DECLARE(ret) ns##_run_##name args_decl /
{ /
ns##_LINK_##name##_t *pHook; /
int n; /
ret rv; /
/
if(!_hooks.link_##name) /
return ok; /
/
pHook=(ns##_LINK_##name##_t *)_hooks.link_##name->elts; /
for(n=0 ; n < _hooks.link_##name->nelts ; ++n) /
{ /
rv=pHook[n].pFunc args_use; /
/
if(rv != ok && rv != decline) /
return rv; /
} /
return ok; /
}
open_logs挂钩就是属于这种类型,其在config.c中的定义如下:
AP_IMPLEMENT_HOOK_RUN_ALL(int, open_logs,
(apr_pool_t *pconf, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s),
(pconf, plog, ptemp, s), OK, DECLINED)
因此将它展开后的结果如下:
AP_DECLARE(int) ap_run_open_logs(apr_pool_t *pconf, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
ap_LINK_open_logs_t *pHook;
int n;
ret rv;
if(!_hooks.link_open_logs)
return ok;
pHook=(ap_LINK_open_logs_t *)_hooks.link_open_logs->elts; /
for(n=0 ; n < _hooks.link_open_logs->nelts ; ++n) /
{
rv=pHook[n].pFunc(pconf, plog, ptemp, s);
if(rv != ok && rv != decline) /
return rv;
}
return ok;
}
从展开的结果来看,ALL类型在对挂钩数组进行遍历调用的时候,即使调用的请求被“DECLINE”,调用也将继续;只有调用请求发生错误才返回该错误值,同时退出遍历。
(3)、AP_IMPLEMENT_HOOK_FIRST简称为FIRST类型,对于该类型Apache核心从头逐一遍历挂钩数组中所注册的挂钩函数,直到遇到一个能够完成所提交任务的函数或者发生错误为止。该宏的定义如下:
#define APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ns,link,ret,name,args_decl,args_use,decline) /
APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) /
link##_DECLARE(ret) ns##_run_##name args_decl /
{ /
ns##_LINK_##name##_t *pHook; /
int n; /
ret rv; /
/
if(!_hooks.link_##name) /
return decline; /
/
pHook=(ns##_LINK_##name##_t *)_hooks.link_##name->elts; /
for(n=0 ; n < _hooks.link_##name->nelts ; ++n) /
{ /
rv=pHook[n].pFunc args_use; /
/
if(rv != decline) /
return rv; /
} /
return decline; /
}
quick_handler就是属于该类型的挂钩,其在config.c中定义如下:
AP_IMPLEMENT_HOOK_RUN_FIRST(int, quick_handler, (request_rec *r, int lookup),
(r, lookup), DECLINED)
该宏展开后的结果如下所示:
AP_DECLARE(ret) ap_run_quick_handler(request_rec *r, int lookup)
{
ap_LINK_quick_handler_t *pHook;
int n;
ret rv;
if(!_hooks.link_quick_handler)
return decline;
pHook=(ap_LINK_quick_handler_t *)_hooks.link_quick_handler->elts;
for(n=0 ; n < _hooks.link_quick_handler->nelts ; ++n) /
{
rv=pHook[n].pFunc(r,look_up);
if(rv != decline)
return rv;
}
return decline;
}
从展开结果中可以看出,在遍历调用过程中一旦找到合适的函数,即rv!=decline的时候,函数即返回,不再继续遍历调用,即使后面仍然有合法的可调用函数。
任何挂钩都必须而且只能是三种类型中的一种。任何挂钩在被真正执行之前都必须调用这三个宏中的一个进行声明。
5.5.6 编写自己挂钩
使用挂钩所带来的最大的一个好处就是可以自行增加挂钩,而不需要全局统一。下面的部分,我们通过编写一个简单的挂钩同时在模块中使用该挂钩从而来加深理解上面所分析的内容。比如现在我们定义在了一个能够在Apache处理请求时调用的函数some_hook,其函数原型为:
int some_hook(request_rec* r,int n);
那么我们分为四个步骤来声明我们的挂钩。
(1)、挂钩声明
我们使用AP_DECLARE_HOOK宏声明一个名称为some_hook的挂钩,声明如下:
AP_DECLARE_HOOK(int,some_hook,(request_rec* r,int n))
(2)、挂钩数组声明
由于some_hook是新声明的挂钩,为此,我们必须同时声明新的挂钩数组来保存各个模块对挂钩的注册使用。声明的语句如下:
APR_HOOK_STRUCT(
APR_HOOK_LINK(some_hook)
……
)
(3)、声明函数调用类型
挂钩声明完毕之后,真正被ap_run_name调用之前,我们还必须声明挂钩的调用类型,或者是VOID类型,或者是FIRST类型,或者是ALL类型。此处我们声明some_hook挂钩为VOID类型,声明语句如下:
AP_IMPLEMENT_HOOK_RUN_VOID(some_hook,(request_rec* r,int n),(r,n))
定义完该宏,实际上也对外声明了该挂钩的执行函数ap_run_some_hook。
(4)、注册挂钩函数
至第三步为止,对挂钩的声明已经基本结束,也意味着下一步的工作实际上应该落实到挂钩使用者身上了。比如如果模块som_module中想使用挂钩some_hook,则其必须在挂钩注册函数中注册该挂钩,挂钩注册函数为ap_hook_some_hook,代码示例如下:
static void register_hooks()
{
……
ap_hook_some_hook(some_hook_function,NULL,NULL,HOOK_MIDDLE);
}
AP_DECLARE_DATA module core_module = {
……
register_hooks /* register hooks */
};
(5)、编写挂钩函数
最后剩下的内容就是编写具体的挂钩调用函数。比如在some_module模块中,我们希望挂钩函数只是打印出“Hello World”语句,而且从(4)中看出挂钩函数名称为some_hook_function,因此挂钩函数声明为如下:
static void some_hook_function(request_rec* r,int n)
{
ap_rputs(“Hello World/n”);
return;
}
需要注意的是,这边的挂钩函数必须符合AP_IMPLEMENT_HOOK_RUN_XXX中声明的格式。
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中:
#define APR_OPTIONAL_HOOK(ns,name,pfn,aszPre,aszSucc,nOrder) do { /
ns##_HOOK_##name##_t *apu__hook = pfn; /
apr_optional_hook_add(#name,(void (*)(void))apu__hook,aszPre, aszSucc, nOrder); /
} while (0)
5.5.7.3 APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL
对于标准挂钩,其实现分为VOID、FIRST和ALL三种,而对于可选挂钩,实现则归结只有一种ALL类型,即APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL,该宏定义如下:
#define APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ns,link,ret,name,args_decl,args_use,ok,decline) /
link##_DECLARE(ret) ns##_run_##name args_decl /
{ /
ns##_LINK_##name##_t *pHook; /
int n; /
ret rv; /
apr_array_header_t *pHookArray=apr_optional_hook_get(#name); /
if(!pHookArray) /
return ok; /
pHook=(ns##_LINK_##name##_t *)pHookArray->elts; /
for(n=0 ; n < pHookArray->nelts ; ++n) /
{ /
rv=(pHook[n].pFunc)args_use; /
/
if(rv != ok && rv != decline) /
return rv; /
} /
return ok; /
}
在status模块mod_status.c中我们声明的挂钩status_hook就是一个可选挂钩,该挂钩实现如下:
APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ap, STATUS, int, status_hook,
(request_rec *r, int flags),
(r, flags),
OK, DECLINED)
该宏展开后即实现了ap_run_status_hook函数,其实现过程,即展开结果如下所示:
AP_DECLARE(int) ap_run_status_hook(request_rec* r,int flags)
{
ap_LINK_status_hook_t *pHook;
int n;
int rv;
apr_array_header_t *pHookArray=apr_optional_hook_get(status_name);
if(!pHookArray)
return ok;
pHook=(ap_LINK_status_hook_t *)pHookArray->elts;
for(n=0 ; n < pHookArray->nelts ; ++n)
{
rv=(pHook[n].pFunc)(r,flags);
if(rv != ok && rv != decline)
return rv;
}
return ok;
}
5.5.8 可选函数
与挂钩存在类似的问题,函数也可能存在可选挂钩的问 题。如果Apache在调用某一个函数的时候,该函数尚未被加载,会发生什么呢。你可能觉得DSO是个好的解决方法。如果指定的函数没有,则动态加载 DSO模块进行查找。不过这种策略并不但是最完美的方案。首先,不是所有的平台都支持DSO;另一方面,
可选函数的特点正如其名,这就决定了它相对于 Apache的动态性。正常的函数都是编写之后才会加入Apache中进行编译,而且一旦编译就无法更改。而可选函数则是在需要的时候动态产生的。在产生 之前,没有人为之进行过专门的编写,因此链接代码中自然也就找不到该函数的实现。
可选函数的使用可以分为五大步骤:声明、实现、注册、获取以及函数调用。
5.5.8.1.可选函数的声明
声明一个可选函数通过宏APR_DECLARE_OPTIONAL_FN来实现,比如我们如果想声明一个optional_fun可选函数,其返回int类型,参数需要字符串类型,那么声明可以如下:
APR_DECLARE_OPTIONAL_FN(int,optional_fun,(const char* params))
APR_DECLARE_OPTIONAL_FN宏定义如下:
#define APR_DECLARE_OPTIONAL_FN(ret,name,args) /
typedef ret (APR_OPTIONAL_FN_TYPE(name)) args
如果将上面的宏展开,则可以看出,该宏只是声明了一个apr_OFN_optional_fun_t类型的函数指针:
typedef int (apr_OFN_optional_fun_t)(const char* params)
一旦声明完毕,我们则将其进行实现如下,在实现中必须注意名称以及函数参数类型的匹配:
int optional_fun(const char* params)
{
……
return 0;
}
5.5.8.2.可选函数的注册
可选函数由Apache核心统一维护。与可选挂钩类似,Apache核心维护了一个 全局的哈希表s_phOptionalFunctions,该哈希表的键为可选函数的名称,而值则是对应的函数指针。为了便于Apache使用,我们必须 在s_phOptionalFunctions中登记可选函数。函数的注册通过APR_REGISTER_OPTIONAL_FN进行,APR_REGISTER_OPTIONAL_FN定义如下:
#define APR_REGISTER_OPTIONAL_FN(name) do { /
APR_OPTIONAL_FN_TYPE(name) *apu__opt = name; /
apr_dynamic_fn_register(#name,(apr_opt_fn_t *)apu__opt); /
} while(0)
该宏内部实际调用了函数apr_dynamic_fn_register进行实际的注册。事实上,Apache中提供了相关函数来支持对s_phOptionalFunctions的操作,除了apr_dynamic_fn_register之外,还包括apr_register_optional_fn、apr_dynamic_fn_retrieve、apr_retrieve_optional_fn。不过其中apr_retrieve_optional_fn和apr_register_optional_fn在Apache2.0中已经被废弃,因此不再多说。
apr_dynamic_fn_register的原型如下:
APU_DECLARE_NONSTD(void) apr_dynamic_fn_register(const char *szName,
apr_opt_fn_t *pfn)
参数szName是可选函数的名称,pfn则是实际可选函数的指针。如果s_phOptionalFunctions哈希表存在,函数只是简单的调用apr_hash_set将记录插入表中。
apr_dynamic_fn_retrieve函数原型为
APU_DECLARE(apr_opt_fn_t *) apr_dynamic_fn_retrieve(const char *szName)
该函数根据给定的函数名称获取实际的函数指针。
事实上,这两个函数都不允许直接调用,它们作为Apache的内部函数而存在,对外提供的则是宏APR_REGISTER_OPTIONAL_FN和APR_RETRIEVE_OPTIONAL_FN。
对于optional_fun可选函数,注册语句如下:
APR_REGISTER_OPTIONAL_FN(optional_fun);
当用户想使用可选函数的时候,首先必须获得其函数指针,用法如下:
APR_OPTIONAL_FN_TYPE(some_fn) *pfn;
pfn=APR_RETRIEVE_OPTIONAL_FN(some_fn);
5.5.8.3.可选函数的使用
5.5.9智能挂钩
5.5.10挂钩工作机制
在前面的部分,我们对挂钩进行了详细的分析,但是还缺乏一个整体上的概念。从整体上来看挂钩的工作机制可以用下图来描述:
一个模块从挂钩的角度来看,其最重要的无非是两个方面:挂钩注册函数和挂钩处理函数。挂钩注册函数通常是模块结构中的register_hooks函数指针,该函数指针将调用实际的挂钩注册函数进行挂钩注册。挂钩注册的过程很简单,通过宏ap_hook_xxx实现。比如上图中声明了两个挂钩abc和xyz。
与此同时,模块中也将声明与挂钩对应的挂钩函数,该挂钩被触发的时候,该函数将被调用。正如前面描述,可以使用宏ap_run_xxx触发指定的挂钩。不过挂钩的触发通常是由核心模块在对客户端请求进行处理的过程中进行。
我们来看一个具体的例子,这是核心模块中关于挂钩的部分。
从模块的结构中可以看出,模块结构中包含一个指针register_hook,该指针通常指向模块中的实际的挂钩注册函数,比如,对于核心模块而言,其挂钩注册函数register_hooks,那么结构中的该指针也为register_hooks:
AP_DECLARE_DATA module core_module = {
STANDARD20_MODULE_STUFF,
create_core_dir_config, /* create per-directory config structure */
merge_core_dir_configs, /* merge per-directory config structures */
create_core_server_config, /* create per-server config structure */
merge_core_server_configs, /* merge per-server config structures */
core_cmds, /* command apr_table_t */
register_hooks /* register hooks */
};
而具体的register_hooks函数则如下:
static void register_hooks(apr_pool_t *p)
{
ap_hook_create_connection(core_create_conn, NULL, NULL,
APR_HOOK_REALLY_LAST);
ap_hook_pre_connection(core_pre_connection, NULL, NULL,
APR_HOOK_REALLY_LAST);
ap_hook_post_config(core_post_config,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_translate_name(ap_core_translate,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_map_to_storage(core_map_to_storage,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_open_logs(ap_open_logs,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_type_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_fixups(core_override_type,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_access_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_create_request(core_create_req, NULL, NULL, APR_HOOK_MIDDLE);
APR_OPTIONAL_HOOK(proxy, create_req, core_create_proxy_req, NULL, NULL,
APR_HOOK_MIDDLE);
ap_hook_pre_mpm(ap_create_scoreboard, NULL, NULL, APR_HOOK_MIDDLE);
……
}
该函数的任务非常的简单,即声明该模块需要实现的挂钩,以及对应的处理函数,供主程序调用。
关于作者
张中庆, 目前主要的研究方向是嵌入式浏览器,移动中间件以及大规模服务器设计。目前正在进行Apache的源代码分析,计划出版《Apache源代码全景分析》上 下册。Apache系列文章为本书的草案部分,对Apache感兴趣的朋友可以通过flydish1234 at sina.com.cn与之联系!
关于作者
张中庆, 目前主要的研究方向是嵌入式浏览器,移动中间件以及大规模服务器设计。目前正在进行Apache的源代码分析,计划出版《Apache源代码全景分析》上 下册。Apache系列文章为本书的草案部分,对Apache感兴趣的朋友可以通过flydish1234 at sina.com.cn与之联系!