`
liuzhaomin
  • 浏览: 204265 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Apache中的挂钩剖析(2)

阅读更多
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中声明的格式。

关于作者
张中庆,目前主要的研究方向是嵌入式浏览器,移动中间件以及大规模服务器设计。目前正在进行Apache的源代码分析,计划出版《Apache源代码全景分析》上下册。Apache系列文章为本书的草案部分,对Apache感兴趣的朋友可以通过flydish1234 at sina.com.cn与之联系!

如果你觉得本文不错,请点击文后的“推荐本文”链接!!
分享到:
评论

相关推荐

    Apache中文手册免费下载

    Apache中文手册免费下载Apache中文手册免费下载Apache中文手册免费下载Apache中文手册免费下载Apache中文手册免费下载Apache中文手册免费下载Apache中文手册免费下载Apache中文手册免费下载Apache中文手册免费下载...

    apache kafka源码剖析高清part2

    apache kafka源码剖析高清 带索引书签目录_徐郡明(著) 电子工业出版社,分为part1和part2,一起下载解压

    Apache Server源代码分析(PDF)

    7. **错误处理和异常恢复**:Apache如何处理错误,如404找不到文件或500内部服务器错误,以及如何优雅地恢复异常状态,都是源代码分析中的重要部分。 8. **并发和线程模型**:Apache的多路复用模型允许高效地处理...

    Apache2中文参考文档

    Apache2的配置指令、模块介绍、错误处理、安全设置、日志分析等内容都会在这里详尽阐述。 关于标签提到的"Apache 中文",这意味着文档是用中文编写的,对于中文用户来说,能更直观地理解复杂的服务器概念和配置细节...

    Apache2中文使用手册

    6. **日志管理**:Apache2会记录所有请求和错误信息,理解如何解读和分析这些日志文件对于故障排查和性能优化很有帮助。日志文件的位置和格式可以在配置文件中设置。 7. **模块管理**:Apache2的模块化设计使得功能...

    Apache源代码分析

    本文将重点剖析Apache中的内存管理结构与方法,特别是与APR库(Apache Portable Runtime)相关的部分。 #### 内存池概念 Apache的内存管理基于内存池(Memory Pool)的概念。内存池是一种预先分配大块内存,并从中...

    APACHE日志分析工具

    很多apache日志分析工具都是要安装到服务器上的,而且安装非常麻烦,于是我写了一个单机版(exe,Windows),方便大家分析apache访问日志,绿色版的,直接解压就可以用。 功能: 1、导入apache访问日志; 2、访问...

    《Apache Kafka源码剖析》.part2.rar

    Apache Kafka源码剖析 PDF较大,分6份上传!一起解压即可。

    Apache2中文文档

    在安装Apache2的过程中,`apache2.exe`文件通常是Windows平台上的安装程序。运行此文件将引导用户完成安装步骤,包括选择安装路径、设置端口号、决定是否作为服务启动等。在Windows上,Apache通常会默认监听80端口,...

    Apache2中文教程

    Apache2的日志功能可以帮助你监控服务器的运行状态,分析访问模式。默认情况下,它会记录访问日志(access.log)和错误日志(error.log)。你可以通过配置`ErrorLog`和`CustomLog`指令来自定义日志位置和格式。 七...

    Apache2 中文使用手册

    `glossary.html`是术语表,对于初学者来说非常有用,它解释了Apache2中常见的技术术语,比如虚拟主机、模块、MIME类型等。 `content-negotiation.html`讨论了内容协商,这是Apache2的一种特性,允许服务器根据...

    apache中文手册chm版

    apache中文手册chm版

    apache kafka源码剖析高清part1

    apache kafka源码剖析高清 带索引书签目录_徐郡明(著) 电子工业出版社

    Apache日志分析手册

    Apache日志分析手册详细介绍了如何在Linux环境下利用...总的来说,Apache日志分析手册提供了一套系统的方法和工具,通过这些方法和工具,可以有效监控和优化Apache服务器的性能,及时发现和解决生产环境中的问题。

    apache commons all 中文api合集

    apache commons all 中文api合集

    《Apache Kafka源码剖析》.part5.rar

    Apache Kafka源码剖析 PDF较大,分5份上传!一起解压即可。

    apache中文手册(html)

    apache中文手册: Apache HTTP Server Version 2.2 文档 版本说明 Apache 2.1/2.2 版本的新特性 Apache 2.0 版本的新特性 从 2.0 升级到 2.2 Apache许可证 参考手册 编译与安装 启动 停止与重新启动 运行时...

Global site tag (gtag.js) - Google Analytics