天天看點

Apache001_ 子產品介紹

Apache是目前世界上使用最為廣泛的一種Web Server,它以跨平台、高效和穩定而聞名。按照去年官方統計的資料,Apache伺服器的裝機量占該市場60%以上的份額。尤其是在X(Unix/Linux)平台上,Apache是最常見的選擇。其它的Web Server産品,比如IIS,隻能運作在Windows平台上,是基于微軟.Net架構技術的不二選擇。

Apache支援許多特性,大部分通過子產品擴充實作。常見的子產品包括mod_auth(權限驗證)、mod_ssl(SSL和TLS支援) mod_rewrite(URL重寫)等。一些通用的語言也支援以Apache子產品的方式與Apache內建。 如Perl,Python,Tcl,和PHP等。

Apache并不是沒有缺點,它最為诟病的一點就是變得越來越重,被普遍認為是重量級的WebServer。是以,近年來又湧現出了很多輕量級的替代産品,比如lighttpd,nginx等等,這些WebServer的優點是運作效率很高,但缺點也很明顯,成熟度往往要低于Apache,通常隻能用于某些特定場合。

Apache是基于子產品化設計的,總體上看起來代碼的可讀性高于php的代碼,它的核心代碼并不多,大多數的功能都被分散到各個子產品中,各個子產品在系統啟動的時候按需載入。你如果想要閱讀Apache的源代碼,建議你直接從main.c檔案讀起,系統最主要的處理邏輯都包含在裡面。

MPM(Multi -Processing Modules,多重處理子產品)是Apache的核心元件之一,Apache通過MPM來使用作業系統的資源,對程序和線程池進行管理。Apache為了能夠獲得最好的運作性能,針對不同的平台(Unix/Linux、Window)做了優化,為不同的平台提供了不同的MPM,使用者可以根據實際情況進行選擇,其中最常使用的MPM有prefork和worker兩種。至于您的伺服器正以哪種方式運作,取決于安裝Apache過程中指定的MPM編譯參數,在X系統上預設的編譯參數為prefork。由于大多數的Unix都不支援真正的線程,是以采用了預派生子程序(prefork)方式,象Windows或者Solaris這些支援線程的平台,基于多程序多線程混合的worker模式是一種不錯的選擇。對此感興趣的同學可以閱讀有關資料,此處不再多講。Apache中還有一個重要的元件就是APR(Apache portable Runtime Library),即Apache可移植運作庫,它是一個對作業系統調用的抽象庫,用來實作Apache内部元件對作業系統的使用,提高系統的可移植性。Apache對于php的解析,就是通過衆多Module中的php Module來完成的。

Apache001_ 子產品介紹

當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中添加一行:

<code>1</code>

<code>LoadModule php5_module modules/mod_php5.so</code>

這裡我們使用了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定義如下:

<code>#define MODULE_MAGIC_COOKIE 0x41503232UL /* "AP22" */</code>

最後Apache會調用相關函數(ap_add_loaded_module)将子產品激活, 此處的激活就是将子產品放入相應的連結清單中(ap_top_modules連結清單: ap_top_modules連結清單用來儲存Apache中所有的被激活的子產品,包括預設的激活子產品和激活的第三方子產品。)

Apache對PHP的支援是通過Apache的子產品mod_php5來支援的。如果希望Apache支援PHP的話,在./configure步驟需要指定--with-apxs2=/usr/local/apache2/bin/apxs 表示告訴編譯器通過Apache的mod_php5/apxs來提供對PHP5的解析。

在最後一步make install的時候我們會看到将動态連結庫libphp5.so(Apache子產品)拷貝到apache2的安裝目錄的modules目錄下,并且還需要在httpd.conf配置檔案中添加LoadModule語句來動态将libphp5.so 子產品加載進來,進而實作Apache對php的支援。

由于該模式實在太經典了,是以這裡關于安裝部分不準備詳述了,相對來說比較簡單。我們知道nginx一般包括兩個用途HTTP Server和Reverse Proxy Server(反向代理伺服器)。在前端可以部署nginx作為reverse proxy server,後端布置多個Apache來實作機群系統server cluster架構的。

是以,實際生産中,我們仍舊能夠保留Apache+mod_php5的經典App Server,而僅僅使用nginx來當做前端的reverse proxy server來實作代理和負載均衡。 是以,建議nginx(1個或者多個)+多個apache的架構繼續使用下去。

Apache2的mod_php5子產品包括sapi/apache2handler和sapi/apache2filter兩個目錄 在apache2_handle/mod_php5.c檔案中,子產品定義的相關代碼如下:

<code>01</code>

<code>AP_MODULE_DECLARE_DATA module php5_module = {</code>

<code>02</code>

<code>    </code><code>STANDARD20_MODULE_STUFF,</code>

<code>03</code>

<code>        </code><code>/* 宏,包括版本,小版本,子產品索引,子產品名,下一個子產品指針等資訊,其中子產品名以__FILE__展現 */</code>

<code>04</code>

<code>    </code><code>create_php_config,      </code><code>/* create per-directory config structure */</code>

<code>05</code>

<code>    </code><code>merge_php_config,       </code><code>/* merge per-directory config structures */</code>

<code>06</code>

<code>    </code><code>NULL,                   </code><code>/* create per-server config structure */</code>

<code>07</code>

<code>    </code><code>NULL,                   </code><code>/* merge per-server config structures */</code>

<code>08</code>

<code>    </code><code>php_dir_cmds,           </code><code>/* 子產品定義的所有的指令 */</code>

<code>09</code>

<code>    </code><code>php_ap2_register_hook</code>

<code>10</code>

<code>        </code><code>/* 注冊鈎子,此函數通過ap_hoo_開頭的函數在一次請求處理過程中對于指定的步驟注冊鈎子 */</code>

<code>11</code>

<code>};</code>

它所對應的是Apache的module結構,module的結構定義如下:

<code>typedef</code> <code>struct</code> <code>module_struct module;</code>

<code>struct</code> <code>module_struct {</code>

<code>    </code><code>int</code> <code>version;</code>

<code>    </code><code>int</code> <code>minor_version;</code>

<code>    </code><code>int</code> <code>module_index;</code>

<code>    </code><code>const</code> <code>char</code> <code>*name;</code>

<code>    </code><code>void</code> <code>*dynamic_load_handle;</code>

<code>    </code><code>struct</code> <code>module_struct *next;</code>

<code>    </code><code>unsigned </code><code>long</code> <code>magic;</code>

<code>    </code><code>void</code> <code>(*rewrite_args) (process_rec *process);</code>

<code>    </code><code>void</code> <code>*(*create_dir_config) (apr_pool_t *p, </code><code>char</code> <code>*dir);</code>

<code>12</code>

<code>    </code><code>void</code> <code>*(*merge_dir_config) (apr_pool_t *p, </code><code>void</code> <code>*base_conf, </code><code>void</code><code>*new_conf);</code>

<code>13</code>

<code>    </code><code>void</code> <code>*(*create_server_config) (apr_pool_t *p, server_rec *s);</code>

<code>14</code>

<code>    </code><code>void</code> <code>*(*merge_server_config) (apr_pool_t *p, </code><code>void</code> <code>*base_conf, </code><code>void</code><code>*new_conf);</code>

<code>15</code>

<code>    </code><code>const</code> <code>command_rec *cmds;</code>

<code>16</code>

<code>    </code><code>void</code> <code>(*register_hooks) (apr_pool_t *p);</code>

<code>17</code>

<code>}</code>

上面的子產品結構與我們在mod_php5.c中所看到的結構有一點不同,這是由于STANDARD20_MODULE_STUFF的原因, 這個宏它包含了前面8個字段的定義。STANDARD20_MODULE_STUFF宏的定義如下:

<code>/** Use this in all standard modules */</code>

<code>2</code>

<code>#define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, \</code>

<code>3</code>

<code>                </code><code>MODULE_MAGIC_NUMBER_MINOR, \</code>

<code>4</code>

<code>                </code><code>-1, \</code>

<code>5</code>

<code>                </code><code>__FILE__, \</code>

<code>6</code>

<code>                </code><code>NULL, \</code>

<code>7</code>

<code>8</code>

<code>                </code><code>MODULE_MAGIC_COOKIE, \</code>

<code>9</code>

<code>                                </code><code>NULL      </code><code>/* rewrite args spot */</code>

在php5_module定義的結構中,php_dir_cmds是子產品定義的所有的指令集合,其定義的内容如下:

<code>const</code> <code>command_rec php_dir_cmds[] =</code>

<code>{</code>

<code>    </code><code>AP_INIT_TAKE2(</code><code>"php_value"</code><code>, php_apache_value_handler, NULL,</code>

<code>        </code><code>OR_OPTIONS, </code><code>"PHP Value Modifier"</code><code>),</code>

<code>    </code><code>AP_INIT_TAKE2(</code><code>"php_flag"</code><code>, php_apache_flag_handler, NULL,</code>

<code>        </code><code>OR_OPTIONS, </code><code>"PHP Flag Modifier"</code><code>),</code>

<code>    </code><code>AP_INIT_TAKE2(</code><code>"php_admin_value"</code><code>, php_apache_admin_value_handler,</code>

<code>        </code><code>NULL, ACCESS_CONF|RSRC_CONF, </code><code>"PHP Value Modifier (Admin)"</code><code>),</code>

<code>    </code><code>AP_INIT_TAKE2(</code><code>"php_admin_flag"</code><code>, php_apache_admin_flag_handler,</code>

<code>        </code><code>NULL, ACCESS_CONF|RSRC_CONF, </code><code>"PHP Flag Modifier (Admin)"</code><code>),</code>

<code>    </code><code>AP_INIT_TAKE1(</code><code>"PHPINIDir"</code><code>, php_apache_phpini_set, NULL,</code>

<code>        </code><code>RSRC_CONF, </code><code>"Directory containing the php.ini file"</code><code>),</code>

<code>    </code><code>{NULL}</code>

這是mod_php5子產品定義的指令表。它實際上是一個command_rec結構的數組。 當Apache遇到指令的時候将逐一周遊各個子產品中的指令表,查找是否有哪個子產品能夠處理該指令, 如果找到,則調用相應的處理函數,如果所有指令表中的子產品都不能處理該指令,那麼将報錯。 如上可見,mod_php5子產品僅提供php_value等5個指令。

php_ap2_register_hook函數的定義如下:

<code>void</code> <code>php_ap2_register_hook(apr_pool_t *p)</code>

<code>    </code><code>ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);</code>

<code>    </code><code>ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE);</code>

<code>    </code><code>ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);</code>

<code>    </code><code>ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);</code>

以上代碼聲明了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結構:

<code>static</code> <code>sapi_module_struct apache2_sapi_module = {</code>

<code>"apache2handler"</code><code>,</code>

<code>"Apache 2.0 Handler"</code><code>,</code>

<code> </code> 

<code>php_apache2_startup,                </code><code>/* startup */</code>

<code>php_module_shutdown_wrapper,            </code><code>/* shutdown */</code>

<code>NULL,                       </code><code>/* activate */</code>

<code>NULL,                       </code><code>/* deactivate */</code>

<code>php_apache_sapi_ub_write,           </code><code>/* unbuffered write */</code>

<code>php_apache_sapi_flush,              </code><code>/* flush */</code>

<code>php_apache_sapi_get_stat,           </code><code>/* get uid */</code>

<code>php_apache_sapi_getenv,             </code><code>/* getenv */</code>

<code>php_error,                  </code><code>/* error handler */</code>

<code>18</code>

<code>php_apache_sapi_header_handler,         </code><code>/* header handler */</code>

<code>19</code>

<code>php_apache_sapi_send_headers,           </code><code>/* send headers handler */</code>

<code>20</code>

<code>NULL,                       </code><code>/* send header handler */</code>

<code>21</code>

<code>22</code>

<code>php_apache_sapi_read_post,          </code><code>/* read POST data */</code>

<code>23</code>

<code>php_apache_sapi_read_cookies,           </code><code>/* read Cookies */</code>

<code>24</code>

<code>25</code>

<code>php_apache_sapi_register_variables,</code>

<code>26</code>

<code>php_apache_sapi_log_message,            </code><code>/* Log message */</code>

<code>27</code>

<code>php_apache_sapi_get_request_time,       </code><code>/* Request Time */</code>

<code>28</code>

<code>NULL,                       </code><code>/* Child Terminate */</code>

<code>29</code>

<code>30</code>

<code>STANDARD_SAPI_MODULE_PROPERTIES</code>

<code>31</code>

這些方法都專屬于Apache伺服器。以讀取cookie為例,當我們在Apache伺服器環境下,在PHP中調用讀取Cookie時, 最終擷取的資料的位置是在激活SAPI時。它所調用的方法是read_cookies。

<code>SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);</code>

對于每一個伺服器在加載時,我們都指定了sapi_module,而Apache的sapi_module是apache2_sapi_module。 其中對應read_cookies方法的是php_apache_sapi_read_cookies函數。 這也是定義SAPI結構的理由:統一接口,面向接口的程式設計,具有更好的擴充性和适應性。

繼續閱讀