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與之聯系!
假如你覺得本文不錯,請點擊文后的“推薦本文”鏈接!!