[Apache](http://zh.wikipedia.org/wiki/Apache)是Apache軟件基金會的一個開放源代碼的Web服務器,可以在大多數電腦操作系統中運行,由于其跨平臺和安全性被廣泛使用,是最流行的Web服務器端軟件之一。Apache支持許多特性,大部分通過模塊擴展實現。常見的模塊包括mod_auth(權限驗證)、mod_ssl(SSL和TLS支持)mod_rewrite(URL重寫)等。一些通用的語言也支持以Apache模塊的方式與Apache集成。如Perl,Python,Tcl,和PHP等。
當PHP需要在Apache服務器下運行時,一般來說,它可以mod_php5模塊的形式集成,此時mod_php5模塊的作用是接收Apache傳遞過來的PHP文件請求,并處理這些請求,然后將處理后的結果返回給Apache。如果我們在Apache啟動前在其配置文件中配置好了PHP模塊(mod_php5),PHP模塊通過注冊apache2的ap_hook_post_config掛鉤,在Apache啟動的時候啟動此模塊以接受PHP文件的請求。
除了這種啟動時的加載方式,Apache的模塊可以在運行的時候動態裝載,這意味著對服務器可以進行功能擴展而不需要重新對源代碼進行編譯,甚至根本不需要停止服務器。我們所需要做的僅僅是給服務器發送信號HUP或者AP_SIG_GRACEFUL通知服務器重新載入模塊。但是在動態加載之前,我們需要將模塊編譯成為動態鏈接庫。此時的動態加載就是加載動態鏈接庫。 Apache中對動態鏈接庫的處理是通過模塊mod_so來完成的,因此mod_so模塊不能被動態加載,它只能被靜態編譯進Apache的核心。這意味著它是隨著Apache一起啟動的。
Apache是如何加載模塊的呢?我們以前面提到的mod_php5模塊為例。首先我們需要在Apache的配置文件httpd.conf中添加一行:
LoadModule php5_module modules/mod_php5.so
這里我們使用了LoadModule命令,該命令的第一個參數是模塊的名稱,名稱可以在模塊實現的源碼中找到。第二個選項是該模塊所處的路徑。如果需要在服務器運行時加載模塊,可以通過發送信號HUP或者AP_SIG_GRACEFUL給服務器,一旦接受到該信號,Apache將重新裝載模塊,而不需要重新啟動服務器。
在配置文件中添加了所上所示的指令后,Apache在加載模塊時會根據模塊名查找模塊并加載,對于每一個模塊,Apache必須保證其文件名是以“mod_”開始的,如PHP的mod_php5.c。如果命名格式不對,Apache將認為此模塊不合法。Apache的每一個模塊都是以module結構體的形式存在,module結構的name屬性在最后是通過宏STANDARD20_MODULE_STUFF以__FILE__體現。關于這點可以在后面介紹mod_php5模塊時有看到。這也就決定了我們的文件名和模塊名是相同的。通過之前指令中指定的路徑找到相關的動態鏈接庫文件后,Apache通過內部的函數獲取動態鏈接庫中的內容,并將模塊的內容加載到內存中的指定變量中。
在真正激活模塊之前,Apache會檢查所加載的模塊是否為真正的Apache模塊,這個檢測是通過檢查module結構體中的magic字段實現的。而magic字段是通過宏STANDARD20_MODULE_STUFF體現,在這個宏中magic的值為MODULE_MAGIC_COOKIE,MODULE_MAGIC_COOKIE定義如下:
#define MODULE_MAGIC_COOKIE 0x41503232UL /* "AP22" */
最后Apache會調用相關函數(ap_add_loaded_module)將模塊激活,此處的激活就是將模塊放入相應的鏈表中(ap_top_modules鏈表:ap_top_modules鏈表用來保存Apache中所有的被激活的模塊,包括默認的激活模塊和激活的第三方模塊。)
Apache加載的是PHP模塊,那么這個模塊是如何實現的呢到我們以Apache2的mod_php5模塊為例進行說明。
### Apache2的mod_php5模塊說明
Apache2的mod_php5模塊包括sapi/apache2handler和sapi/apache2filter兩個目錄在apache2_handle/mod_php5.c文件中,模塊定義的相關代碼如下:
AP_MODULE_DECLARE_DATA module php5_module = {
STANDARD20_MODULE_STUFF,
/* 宏,包括版本,小版本,模塊索引,模塊名,下一個模塊指針等信息,其中模塊名以__FILE__體現 */
create_php_config, /* create per-directory config structure */
merge_php_config, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
php_dir_cmds, /* 模塊定義的所有的指令 */
php_ap2_register_hook
/* 注冊鉤子,此函數通過ap_hoo_開頭的函數在一次請求處理過程中對于指定的步驟注冊鉤子 */
};
它所對應的是Apache的module結構,module的結構定義如下:
typedef struct module_struct module;
struct module_struct {
int version;
int minor_version;
int module_index;
const char *name;
void *dynamic_load_handle;
struct module_struct *next;
unsigned long magic;
void (*rewrite_args) (process_rec *process);
void *(*create_dir_config) (apr_pool_t *p, char *dir);
void *(*merge_dir_config) (apr_pool_t *p, void *base_conf, void *new_conf);
void *(*create_server_config) (apr_pool_t *p, server_rec *s);
void *(*merge_server_config) (apr_pool_t *p, void *base_conf, void *new_conf);
const command_rec *cmds;
void (*register_hooks) (apr_pool_t *p);
}
上面的模塊結構與我們在mod_php5.c中所看到的結構有一點不同,這是由于STANDARD20_MODULE_STUFF的原因,這個宏它包含了前面8個字段的定義。STANDARD20_MODULE_STUFF宏的定義如下:
/** Use this in all standard modules */
#define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, \
MODULE_MAGIC_NUMBER_MINOR, \
-1, \
__FILE__, \
NULL, \
NULL, \
MODULE_MAGIC_COOKIE, \
NULL /* rewrite args spot */
在php5_module定義的結構中,php_dir_cmds是模塊定義的所有的指令集合,其定義的內容如下:
const command_rec php_dir_cmds[] =
{
AP_INIT_TAKE2("php_value", php_apache_value_handler, NULL,
OR_OPTIONS, "PHP Value Modifier"),
AP_INIT_TAKE2("php_flag", php_apache_flag_handler, NULL,
OR_OPTIONS, "PHP Flag Modifier"),
AP_INIT_TAKE2("php_admin_value", php_apache_admin_value_handler,
NULL, ACCESS_CONF|RSRC_CONF, "PHP Value Modifier (Admin)"),
AP_INIT_TAKE2("php_admin_flag", php_apache_admin_flag_handler,
NULL, ACCESS_CONF|RSRC_CONF, "PHP Flag Modifier (Admin)"),
AP_INIT_TAKE1("PHPINIDir", php_apache_phpini_set, NULL,
RSRC_CONF, "Directory containing the php.ini file"),
{NULL}
};
這是mod_php5模塊定義的指令表。它實際上是一個command_rec結構的數組。當Apache遇到指令的時候將逐一遍歷各個模塊中的指令表,查找是否有哪個模塊能夠處理該指令,如果找到,則調用相應的處理函數,如果所有指令表中的模塊都不能處理該指令,那么將報錯。如上可見,mod_php5模塊僅提供php_value等5個指令。
php_ap2_register_hook函數的定義如下:
void php_ap2_register_hook(apr_pool_t *p)
{
ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
}
以上代碼聲明了pre_config,post_config,handler和child_init 4個掛鉤以及對應的處理函數。其中pre_config,post_config,child_init是啟動掛鉤,它們在服務器啟動時調用。handler掛鉤是請求掛鉤,它在服務器處理請求時調用。其中在post_config掛鉤中啟動php。它通過php_apache_server_startup函數實現。php_apache_server_startup函數通過調用sapi_startup啟動sapi,并通過調用php_apache2_startup來注冊sapi module struct(此結構在本節開頭中有說明),最后調用php_module_startup來初始化PHP, 其中又會初始化Zend引擎,以及填充zend_module_struct中的treat_data成員(通過php_startup_sapi_content_types)等。
到這里,我們知道了Apache加載mod_php5模塊的整個過程,可是這個過程與我們的SAPI有什么關系呢?mod_php5也定義了屬于Apache的sapi_module_struct結構:
static sapi_module_struct apache2_sapi_module = {
"apache2handler",
"Apache 2.0 Handler",
?
php_apache2_startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */
?
NULL, /* activate */
NULL, /* deactivate */
?
php_apache_sapi_ub_write, /* unbuffered write */
php_apache_sapi_flush, /* flush */
php_apache_sapi_get_stat, /* get uid */
php_apache_sapi_getenv, /* getenv */
?
php_error, /* error handler */
?
php_apache_sapi_header_handler, /* header handler */
php_apache_sapi_send_headers, /* send headers handler */
NULL, /* send header handler */
?
php_apache_sapi_read_post, /* read POST data */
php_apache_sapi_read_cookies, /* read Cookies */
?
php_apache_sapi_register_variables,
php_apache_sapi_log_message, /* Log message */
php_apache_sapi_get_request_time, /* Request Time */
NULL, /* Child Terminate */
?
STANDARD_SAPI_MODULE_PROPERTIES
};
這些方法都專屬于Apache服務器。以讀取cookie為例,當我們在Apache服務器環境下,在PHP中調用讀取Cookie時,最終獲取的數據的位置是在激活SAPI時。它所調用的方法是read_cookies。
SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);
對于每一個服務器在加載時,我們都指定了sapi_module,而Apache的sapi_module是apache2_sapi_module。其中對應read_cookies方法的是php_apache_sapi_read_cookies函數。
又如flush函數,在ext/standard/basic_functions.c文件中,其實現為sapi_flush:
SAPI_API int sapi_flush(TSRMLS_D)
{
if (sapi_module.flush) {
sapi_module.flush(SG(server_context));
return SUCCESS;
} else {
return FAILURE;
}
}
如果我們定義了此前服務器接口的flush函數,則直接調用flush對應的函數,返回成功,否則返回失敗。對于我們當前的Apache模塊,其實現為php_apache_sapi_flush函數,最終會調用Apache的ap_rflush,刷新apache的輸出緩沖區。當然,flush的操作有時也不會生效,因為當PHP執行flush函數時,其所有的行為完全依賴于Apache的行為,而自身卻做不了什么,比如啟用了Apache的壓縮功能,當沒有達到預定的輸出大小時,即使使用了flush函數,Apache也不會向客戶端輸出對應的內容。
### Apache的運行過程
Apache的運行分為啟動階段和運行階段。在啟動階段,Apache為了獲得系統資源最大的使用權限,將以特權用戶root(*nix系統)或超級管理員Administrator(Windows系統)完成啟動,并且整個過程處于一個單進程單線程的環境中。這個階段包括配置文件解析(如http.conf文件)、模塊加載(如mod_php,mod_perl)和系統資源初始化(例如日志文件、共享內存段、數據庫連接等)等工作。
Apache的啟動階段執行了大量的初始化操作,并且將許多比較慢或者花費比較高的操作都集中在這個階段完成,以減少了后面處理請求服務的壓力。
在運行階段,Apache主要工作是處理用戶的服務請求。在這個階段,Apache放棄特權用戶級別,使用普通權限,這主要是基于安全性的考慮,防止由于代碼的缺陷引起的安全漏洞。Apache對HTTP的請求可以分為連接、處理和斷開連接三個大的階段。同時也可以分為11個小的階段,依次為:Post-Read-Request,URI Translation,Header Parsing,Access Control,Authentication,Authorization,MIME Type Checking,FixUp,Response,Logging,CleanUp
### Apache Hook機制
Apache 的Hook機制是指:Apache 允許模塊(包括內部模塊和外部模塊,例如mod_php5.so,mod_perl.so等)將自定義的函數注入到請求處理循環中。換句話說,模塊可以在Apache的任何一個處理階段中掛接(Hook)上自己的處理函數,從而參與Apache的請求處理過程。mod_php5.so/ php5apache2.dll就是將所包含的自定義函數,通過Hook機制注入到Apache中,在Apache處理流程的各個階段負責處理php請求。關于Hook機制在Windows系統開發也經常遇到,在Windows開發既有系統級的鉤子,又有應用級的鉤子。
以上介紹了apache的加載機制,hook機制,apache的運行過程以及php5模塊的相關知識,下面簡單的說明在查看源碼中的一些常用對象。
### Apache常用對象
在說到Apache的常用對象時,我們不得不先說下httpd.h文件。httpd.h文件包含了Apache的所有模塊都需要的核心API。它定義了許多系統常量。但是更重要的是它包含了下面一些對象的定義。
**request_rec對象**當一個客戶端請求到達Apache時,就會創建一個request_rec對象,當Apache處理完一個請求后,與這個請求對應的request_rec對象也會隨之被釋放。request_rec對象包括與一個HTTP請求相關的所有數據,并且還包含一些Apache自己要用到的狀態和客戶端的內部字段。
**server_rec對象**server_rec定義了一個邏輯上的WEB服務器。如果有定義虛擬主機,每一個虛擬主機擁有自己的server_rec對象。server_rec對象在Apache啟動時創建,當整個httpd關閉時才會被釋放。它包括服務器名稱,連接信息,日志信息,針對服務器的配置,事務處理相關信息等server_rec對象是繼request_rec對象之后第二重要的對象。
**conn_rec對象**conn_rec對象是TCP連接在Apache的內部實現。它在客戶端連接到服務器時創建,在連接斷開時釋放。
### 參考資料
《The Apache Modules Book--Application Development with Apache》
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和Zend引擎
- 第二節 SAPI概述
- Apache模塊
- 嵌入式
- FastCGI
- 第三節 PHP腳本的執行
- 詞法分析和語法分析
- opcode
- opcode處理函數查找
- 第四節 小結
- 第三章 變量及數據類型
- 第一節 變量的結構和類型
- 哈希表(HashTable)
- PHP的哈希表實現
- 鏈表簡介
- 第二節 常量
- 第三節 預定義變量
- 第四節 靜態變量
- 第五節 類型提示的實現
- 第六節 變量的生命周期
- 變量的賦值和銷毀
- 變量的作用域
- global語句
- 第七節 數據類型轉換
- 第八節 小結
- 第四章 函數的實現
- 第一節 函數的內部結構
- 函數的內部結構
- 函數間的轉換
- 第二節 函數的定義,傳參及返回值
- 函數的定義
- 函數的參數
- 函數的返回值
- 第三節 函數的調用和執行
- 第四節 匿名函數及閉包
- 第五節 小結
- 第五章 類和面向對象
- 第一節 類的結構和實現
- 第二節 類的成員變量及方法
- 第三節 訪問控制的實現
- 第四節 類的繼承,多態及抽象類
- 第五節 魔術方法,延遲綁定及靜態成員
- 第六節 PHP保留類及特殊類
- 第七節 對象
- 第八節 命名空間
- 第九節 標準類
- 第十節 小結
- 第六章 內存管理
- 第一節 內存管理概述
- 第二節 PHP中的內存管理
- 第三節 內存使用:申請和銷毀
- 第四節 垃圾回收
- 新的垃圾回收
- 第五節 內存管理中的緩存
- 第六節 寫時復制(Copy On Write)
- 第七節 內存泄漏
- 第八節 小結
- 第七章 Zend虛擬機
- 第一節 Zend虛擬機概述
- 第二節 語法的實現
- 詞法解析
- 語法分析
- 實現自己的語法
- 第三節 中間代碼的執行
- 第四節 PHP代碼的加密解密
- 第五節 小結
- 第八章 線程安全
- 第二節 線程,進程和并發
- 第三節 PHP中的線程安全
- 第九章 錯誤和異常處理
- 第十章 輸出緩沖
- 第十六章 PHP語言特性的實現
- 第一節 循環語句
- foreach的實現
- 第二十章 怎么樣系列(how to)
- 附錄
- 附錄A PHP及Zend API
- 附錄B PHP的歷史
- 附錄C VLD擴展使用指南
- 附錄D 怎樣為PHP貢獻
- 附錄E phpt測試文件說明
- 附錄F PHP5.4新功能升級解析
- 附錄G:re2c中文手冊