SAPI:Server Application Programming Interface 服務器端應用編程端口。研究過PHP架構的同學應該知道這個東東的重要性,它提供了一個接口,使得PHP可以和其他應用進行交互數據。 本文不會詳細介紹每個PHP的SAPI,只是針對最簡單的CGI SAPI,來說明SAPI的機制。
我們先來看看PHP的架構圖:
SAPI指的是PHP具體應用的編程接口, 就像PC一樣,無論安裝哪些操作系統,只要滿足了PC的接口規範都可以在PC上正常運行, PHP腳本要執行有很多種方式,通過Web服務器,或者直接在命令行下,也可以嵌入在其他程序中。
通常,我們使用Apache或者Nginx這類Web服務器來測試PHP腳本,或者在命令行下通過PHP解釋器程序來執行。 腳本執行完後,Web服務器應答,瀏覽器顯示應答信息,或者在命令行標準輸出上顯示內容。
我們很少關心PHP解釋器在哪裏。雖然通過Web服務器和命令行程序執行腳本看起來很不一樣, 實際上它們的工作流程是一樣的。命令行參數傳遞給PHP解釋器要執行的腳本, 相當於通過url請求一個PHP頁面。腳本執行完成後返回響應結果,只不過命令行的響應結果是顯示在終端上。
腳本執行的開始都是以SAPI接口實現開始的。只是不同的SAPI接口實現會完成他們特定的工作, 例如Apache的mod_php SAPI實現需要初始化從Apache獲取的一些信息,在輸出內容是將內容返回給Apache, 其他的SAPI實現也類似。
SAPI提供了一個和外部通信的接口, 對於PHP5.2,默認提供了很多種SAPI, 常見的給apache的mod_php5,CGI,給IIS的ISAPI,還有Shell的CLI,本文就從CGI SAPI入手 ,介紹SAPI的機制。 雖然CGI簡單,但是不用擔心,它包含了絕大部分內容,足以讓你深刻理解SAPI的工作原理。
要定義個SAPI,首先要定義個sapi_module_struct, 查看 PHP-SRC/sapi/cgi/cgi_main.c:
*/
static sapi_module_struct cgi_sapi_module = {
#if PHP_FASTCGI
"cgi-fcgi", /* name */
"CGI/FastCGI", /* pretty name */
#else
"cgi", /* name */
"CGI", /* pretty name */
#endif
php_cgi_startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */
NULL, /* activate */
sapi_cgi_deactivate, /* deactivate */
sapi_cgibin_ub_write, /* unbuffered write */
sapi_cgibin_flush, /* flush */
NULL, /* get uid */
sapi_cgibin_getenv, /* getenv */
php_error, /* error handler */
NULL, /* header handler */
sapi_cgi_send_headers, /* send headers handler */
NULL, /* send header handler */
sapi_cgi_read_post, /* read POST data */
sapi_cgi_read_cookies, /* read Cookies */
sapi_cgi_register_variables, /* register server variables */
sapi_cgi_log_message, /* Log message */
NULL, /* Get request time */
STANDARD_SAPI_MODULE_PROPERTIES
};
這個結構,包含了一些常量,比如name, 這個會在我們調用php_info()的時候被使用。一些初始化,收尾函數,以及一些函數指針,用來告訴Zend,如何獲取,和輸出數據。
1. php_cgi_startup, 當一個應用要調用PHP的時候,這個函數會被調用,對於CGI來說,它只是簡單的調用了PHP的初始化函數:
1 |
static int php_cgi_startup(sapi_module_struct
*sapi_module) |
3 |
if (php_module_startup(sapi_module,
NULL, 0) == FAILURE) { |
2. php_module_shutdown_wrapper , 一個對PHP關閉函數的簡單包裝。只是簡單的調用php_module_shutdown;
3. PHP會在每個request的時候,處理一些初始化,資源分配的事務。這部分就是activate字段要定義的,從上面的結構我們可以看出,對於CGI來說,它並沒有提供初始化處理句柄。對於mod_php來說,那就不同了,他要在apache的pool中註冊資源析構函數, 申請空間, 初始化環境變量,等等。
4. sapi_cgi_deactivate, 這個是對應與activate的函數,顧名思義,它會提供一個handler, 用來處理收尾工作,對於CGI來說,他只是簡單的刷新緩衝區,用以保證用戶在Zend關閉前得到所有的輸出數據:
01 |
static int sapi_cgi_deactivate(TSRMLS_D) |
08 |
if (SG(sapi_started))
{ |
09 |
sapi_cgibin_flush(SG(server_context)); |
5. sapi_cgibin_ub_write, 這個hanlder告訴了Zend,如何輸出數據,對於mod_php來說,這個函數提供了一個向response數據寫的接口,而對於CGI來說,只是簡單的寫到stdout:
01 |
static inline size_t sapi_cgibin_single_write( const char *str,
uint str_length TSRMLS_DC) |
03 |
#ifdef
PHP_WRITE_STDOUT |
10 |
if (fcgi_is_fastcgi())
{ |
11 |
fcgi_request
*request = (fcgi_request*) SG(server_context); |
12 |
long ret
= fcgi_write(request, FCGI_STDOUT, str, str_length); |
19 |
#ifdef
PHP_WRITE_STDOUT |
20 |
ret
= write(STDOUT_FILENO, str, str_length); |
21 |
if (ret
<= 0) return 0; |
24 |
ret
= fwrite (str,
1, MIN(str_length, 16384), stdout); |
29 |
static int sapi_cgibin_ub_write( const char *str,
uint str_length TSRMLS_DC) |
31 |
const char *ptr
= str; |
32 |
uint
remaining = str_length; |
35 |
while (remaining
> 0) { |
36 |
ret
= sapi_cgibin_single_write(ptr, remaining TSRMLS_CC); |
38 |
php_handle_aborted_connection(); |
39 |
return str_length
- remaining; |
把真正的寫的邏輯剝離出來,就是爲了簡單實現兼容fastcgi的寫方式。
6. sapi_cgibin_flush, 這個是提供給zend的刷新緩存的函數句柄,對於CGI來說,只是簡單的調用系統提供的fflush;
7.NULL, 這部分用來讓Zend可以驗證一個要執行腳本文件的state,從而判斷文件是否據有執行權限等等,CGI沒有提供。
8. sapi_cgibin_getenv, 爲Zend提供了一個根據name來查找環境變量的接口,對於mod_php5來說,當我們在腳本中調用getenv的時候,就會間接的調用這個句柄。而對於CGI來說,因爲他的運行機制和CLI很類似,直接調用父級是Shell, 所以,只是簡單的調用了系統提供的genenv:
01 |
static char *sapi_cgibin_getenv( char *name, size_t name_len
TSRMLS_DC) |
08 |
if (fcgi_is_fastcgi())
{ |
09 |
fcgi_request
*request = (fcgi_request*) SG(server_context); |
10 |
return fcgi_getenv(request,
name, name_len); |
9. php_error, 錯誤處理函數, 到這裏,說幾句題外話,上次看到php maillist 提到的使得PHP的錯誤處理機制完全OO化, 也就是,改寫這個函數句柄,使得每當有錯誤發生的時候,都throw一個異常。而CGI只是簡單的調用了PHP提供的錯誤處理函數。
10. 這個函數會在我們調用PHP的header()函數的時候被調用,對於CGI來說,不提供。
11. sapi_cgi_send_headers, 這個函數會在要真正發送header的時候被調用,一般來說,就是當有任何的輸出要發送之前:
01 |
static int sapi_cgi_send_headers(sapi_headers_struct
*sapi_headers TSRMLS_DC) |
03 |
char buf[SAPI_CGI_MAX_HEADER_LENGTH]; |
04 |
sapi_header_struct
*h; |
05 |
zend_llist_position
pos; |
07 |
if (SG(request_info).no_headers
== 1) { |
08 |
return SAPI_HEADER_SENT_SUCCESSFULLY; |
11 |
if (cgi_nph
|| SG(sapi_headers).http_response_code != 200) |
15 |
if (rfc2616_headers
&& SG(sapi_headers).http_status_line) { |
16 |
len
= snprintf(buf, SAPI_CGI_MAX_HEADER_LENGTH, |
17 |
"%s\r\n" ,
SG(sapi_headers).http_status_line); |
19 |
if (len
> SAPI_CGI_MAX_HEADER_LENGTH) { |
20 |
len
= SAPI_CGI_MAX_HEADER_LENGTH; |
24 |
len
= sprintf (buf, "Status:
%d\r\n" ,
SG(sapi_headers).http_response_code); |
30 |
h
= (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos); |
34 |
PHPWRITE_H(h->header,
h->header_len); |
35 |
PHPWRITE_H( "\r\n" ,
2); |
37 |
h
= (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); |
39 |
PHPWRITE_H( "\r\n" ,
2); |
41 |
return SAPI_HEADER_SENT_SUCCESSFULLY; |
12. NULL, 這個用來單獨發送每一個header, CGI沒有提供
13. sapi_cgi_read_post, 這個句柄指明瞭如何獲取POST的數據,如果做過CGI編程的話,我們就知道CGI是從stdin中讀取POST DATA的:
01 |
static int sapi_cgi_read_post( char *buffer,
uint count_bytes TSRMLS_DC) |
03 |
uint
read_bytes=0, tmp_read_bytes; |
08 |
count_bytes
= MIN(count_bytes, (uint) SG(request_info).content_length - SG(read_post_bytes)); |
09 |
while (read_bytes
< count_bytes) { |
11 |
if (fcgi_is_fastcgi())
{ |
12 |
fcgi_request
*request = (fcgi_request*) SG(server_context); |
13 |
tmp_read_bytes
= fcgi_read(request, pos, count_bytes - read_bytes); |
14 |
pos
+= tmp_read_bytes; |
16 |
tmp_read_bytes
= read(0, buffer + read_bytes, count_bytes - read_bytes); |
19 |
tmp_read_bytes
= read(0, buffer + read_bytes, count_bytes - read_bytes); |
22 |
if (tmp_read_bytes
<= 0) { |
25 |
read_bytes
+= tmp_read_bytes; |
14. sapi_cgi_read_cookies, 這個和上面的函數一樣,只不過是去獲取cookie值:
1 |
static char *sapi_cgi_read_cookies(TSRMLS_D) |
3 |
return sapi_cgibin_getenv(( char *) "HTTP_COOKIE" , sizeof ( "HTTP_COOKIE" )-1
TSRMLS_CC); |
15. sapi_cgi_register_variables, 這個函數給了一個接口,用以給$_SERVER變量中添加變量,對於CGI來說,註冊了一個PHP_SELF,這樣我們就可以在腳本中訪問$_SERVER['PHP_SELF']來獲取本次的request_uri:
1 |
static void sapi_cgi_register_variables(zval
*track_vars_array TSRMLS_DC) |
6 |
php_import_environment_variables(track_vars_array
TSRMLS_CC); |
8 |
php_register_variable( "PHP_SELF" ,
(SG(request_info).request_uri ? SG(request_info).request_uri : "" ),
track_vars_array TSRMLS_CC); |
16. sapi_cgi_log_message ,用來輸出錯誤信息,對於CGI來說,只是簡單的輸出到stderr:
01 |
static void sapi_cgi_log_message( char *message) |
04 |
if (fcgi_is_fastcgi()
&& fcgi_logging) { |
05 |
fcgi_request
*request; |
08 |
request
= (fcgi_request*) SG(server_context); |
10 |
int len
= strlen (message); |
11 |
char *buf
= malloc (len+2); |
13 |
memcpy (buf,
message, len); |
14 |
memcpy (buf
+ len, "\n" , sizeof ( "\n" )); |
15 |
fcgi_write(request,
FCGI_STDERR, buf, len+1); |
18 |
fprintf (stderr, "%s\n" ,
message); |
22 |
#endif
/* PHP_FASTCGI */ |
23 |
fprintf (stderr, "%s\n" ,
message); |
經過分析,我們已經瞭解了一個SAPI是如何實現的了, 分析過CGI以後,我們也就可以想象mod_php, embed等SAPI的實現機制。
原文地址:http://www.nowamagic.net/librarys/veda/detail/1285