PHP內核學習(一)SAPI


學習PHP-src之前,我准備了一份源文件:

GitHub下載->https://github.com/helingfeng/php-src

簡單分析一下源碼的目錄結構:

1. root根目錄下,包含項目的說明文件以及設計方案,大部分文件是必讀的。

2. build顧名思義,放置一些和源碼編譯相關的文件,比如編譯前腳本配置、環境監測等。

3. ext官方擴展,包含了絕大數PHP函數的定義和實現,包括date、pdo、ftp、curl等。

4. main 放置PHP核心文件,主要實現PHP的基礎設施,這里和Zend engine不一樣,Zend engine主要完成最核心的語言運行環境。

5. Zend 放置Zend engine實現文件,包含腳本語法解析,擴展機制的實現等。

6. pear PHP的擴展與應用倉庫

7. sapi 包含多種服務器的抽象層代碼,例如apache的mod_php、cgi、fcgi以及fpm等接口。

8. TSRM(Thread Safe Resource Manager) php的線程安全是構建在TSRM庫之上的。

9. tests php的測試腳本集合,包含各個模塊功能的測試文件。

10. win32 包含windows平台下的相關實現,比如socket的實現在windows與*Nix平台就不太相同,同時包含了在windows下編譯php的相關腳本。

 

講完php目錄結構,先來一張php架構圖:

從架構圖中,很清楚看出SAPI(Server Application Programming Interface)應用編程接口,是非常重要的東東。

今天,拿最簡單的CGI接口來學習SAPI。

先百度百科一下什么是CGI:Common Gateway Interface 是WWW技術中最重要的技術之一,有着不可替代的重要地位。CGI是外部應用程序(CGI程序)與Web服務器之間的接口標准,是在CGI程序和Web服務器之間傳遞信息的規程。CGI規范允許Web服務器執行外部程序,並將它們的輸出發送給Web瀏覽器,CGI將Web的一組簡單的靜態超媒體文檔變成一個完整的新的交互式媒體。

腳本執行的開始都是以SAPI接口實現開始的。只是不同的SAPI接口實現會完成他們特定的工作, 例如Apache的mod_php SAPI實現需要初始化從Apache獲取的一些信息,在輸出內容是將內容返回給Apache, 其他的SAPI實現也類似。

以CGI為例了解一下源碼結構:

cgi_main.c

定義SAPI:

 1 /* {{{ sapi_module_struct cgi_sapi_module
 2  */
 3 static sapi_module_struct cgi_sapi_module = {
 4     "cgi-fcgi",                        /* name */
 5     "CGI/FastCGI",                    /* pretty name */
 6 
 7     php_cgi_startup,                /* startup */
 8     php_module_shutdown_wrapper,    /* shutdown */
 9 
10     sapi_cgi_activate,                /* activate */
11     sapi_cgi_deactivate,            /* deactivate */
12 
13     sapi_cgi_ub_write,                /* unbuffered write */
14     sapi_cgi_flush,                    /* flush */
15     NULL,                            /* get uid */
16     sapi_cgi_getenv,                /* getenv */
17 
18     php_error,                        /* error handler */
19 
20     NULL,                            /* header handler */
21     sapi_cgi_send_headers,            /* send headers handler */
22     NULL,                            /* send header handler */
23 
24     sapi_cgi_read_post,                /* read POST data */
25     sapi_cgi_read_cookies,            /* read Cookies */
26 
27     sapi_cgi_register_variables,    /* register server variables */
28     sapi_cgi_log_message,            /* Log message */
29     NULL,                            /* Get request time */
30     NULL,                            /* Child terminate */
31 
32     STANDARD_SAPI_MODULE_PROPERTIES
33 };

定義了一些常量字符串,以及定義初始化函數、銷毀函數、以及一些函數指針,告訴Zend如何獲取與輸出數據。

例如:php_cgi_startup 調用php初始化。

1 static int php_cgi_startup(sapi_module_struct *sapi_module)
2 {
3     if (php_module_startup(sapi_module, &cgi_module_entry, 1) == FAILURE) {
4         return FAILURE;
5     }
6     return SUCCESS;
7 }

例如:php_module_shutdown_wrapper php關閉函數。

1 int php_module_shutdown_wrapper(sapi_module_struct *sapi_globals)
2 {
3     php_module_shutdown();
4     return SUCCESS;
5 }

例如:sapi_cgi_activate 處理request進行初始化。

 1 static int sapi_cgi_activate(void)
 2 {
 3     char *path, *doc_root, *server_name;
 4     size_t path_len, doc_root_len, server_name_len;
 5 
 6     /* PATH_TRANSLATED should be defined at this stage but better safe than sorry :) */
 7     if (!SG(request_info).path_translated) {
 8         return FAILURE;
 9     }
10 
11     if (php_ini_has_per_host_config()) {
12         /* Activate per-host-system-configuration defined in php.ini and stored into configuration_hash during startup */
13         if (fcgi_is_fastcgi()) {
14             fcgi_request *request = (fcgi_request*) SG(server_context);
15 
16             server_name = FCGI_GETENV(request, "SERVER_NAME");
17         } else {
18             server_name = getenv("SERVER_NAME");
19         }
20         /* SERVER_NAME should also be defined at this stage..but better check it anyway */
21         if (server_name) {
22             server_name_len = strlen(server_name);
23             server_name = estrndup(server_name, server_name_len);
24             zend_str_tolower(server_name, server_name_len);
25             php_ini_activate_per_host_config(server_name, server_name_len);
26             efree(server_name);
27         }
28     }
29 
30     if (php_ini_has_per_dir_config() ||
31         (PG(user_ini_filename) && *PG(user_ini_filename))
32     ) {
33         /* Prepare search path */
34         path_len = strlen(SG(request_info).path_translated);
35 
36         /* Make sure we have trailing slash! */
37         if (!IS_SLASH(SG(request_info).path_translated[path_len])) {
38             path = emalloc(path_len + 2);
39             memcpy(path, SG(request_info).path_translated, path_len + 1);
40             path_len = zend_dirname(path, path_len);
41             path[path_len++] = DEFAULT_SLASH;
42         } else {
43             path = estrndup(SG(request_info).path_translated, path_len);
44             path_len = zend_dirname(path, path_len);
45         }
46         path[path_len] = 0;
47 
48         /* Activate per-dir-system-configuration defined in php.ini and stored into configuration_hash during startup */
49         php_ini_activate_per_dir_config(path, path_len); /* Note: for global settings sake we check from root to path */
50 
51         /* Load and activate user ini files in path starting from DOCUMENT_ROOT */
52         if (PG(user_ini_filename) && *PG(user_ini_filename)) {
53             if (fcgi_is_fastcgi()) {
54                 fcgi_request *request = (fcgi_request*) SG(server_context);
55 
56                 doc_root = FCGI_GETENV(request, "DOCUMENT_ROOT");
57             } else {
58                 doc_root = getenv("DOCUMENT_ROOT");
59             }
60             /* DOCUMENT_ROOT should also be defined at this stage..but better check it anyway */
61             if (doc_root) {
62                 doc_root_len = strlen(doc_root);
63                 if (doc_root_len > 0 && IS_SLASH(doc_root[doc_root_len - 1])) {
64                     --doc_root_len;
65                 }
66 #ifdef PHP_WIN32
67                 /* paths on windows should be case-insensitive */
68                 doc_root = estrndup(doc_root, doc_root_len);
69                 zend_str_tolower(doc_root, doc_root_len);
70 #endif
71                 php_cgi_ini_activate_user_config(path, path_len, doc_root, doc_root_len, (doc_root_len > 0 && (doc_root_len - 1)));
72 
73 #ifdef PHP_WIN32
74                 efree(doc_root);
75 #endif
76             }
77         }
78 
79         efree(path);
80     }
81 
82     return SUCCESS;
83 }

例如:sapi_cgi_deactivate 處理完request關閉函數,與activate對應。

 1 static int sapi_cgi_deactivate(void)
 2 {
 3     /* flush only when SAPI was started. The reasons are:
 4         1. SAPI Deactivate is called from two places: module init and request shutdown
 5         2. When the first call occurs and the request is not set up, flush fails on FastCGI.
 6     */
 7     if (SG(sapi_started)) {
 8         if (fcgi_is_fastcgi()) {
 9             if (
10                 !parent &&
11                 !fcgi_finish_request((fcgi_request*)SG(server_context), 0)) {
12                 php_handle_aborted_connection();
13             }
14         } else {
15             sapi_cgi_flush(SG(server_context));
16         }
17     }
18     return SUCCESS;
19 }

例如:sapi_cgibin_ub_write 告訴Zend如何輸出數據。

 1 static size_t sapi_cgibin_ub_write(const char *str, size_t str_length) /* {{{ */
 2 {
 3     const char *ptr = str;
 4     uint remaining = str_length;
 5     size_t ret;
 6 
 7     while (remaining > 0) {
 8         ret = sapi_cgibin_single_write(ptr, remaining);
 9         if (!ret) {
10             php_handle_aborted_connection();
11             return str_length - remaining;
12         }
13         ptr += ret;
14         remaining -= ret;
15     }
16 
17     return str_length;
18 }

例如:sapi_cgi_flush Zend刷新緩存。

1 static void sapi_cgi_flush(void *server_context)
2 {
3     if (fflush(stdout) == EOF) {
4         php_handle_aborted_connection();
5     }
6 }

例如:sapi_cgi_getenv 獲取環境信息。

1 static char *sapi_cgi_getenv(char *name, size_t name_len)
2 {
3     return getenv(name);
4 }

例如:php_error 異常輸出。

CGI只是簡單的調用了PHP提供的錯誤處理函數

例如:sapi_cgi_read_post 讀取post data。

 1 static size_t sapi_cgi_read_post(char *buffer, size_t count_bytes)
 2 {
 3     size_t read_bytes = 0;
 4     int tmp_read_bytes;
 5     size_t remaining_bytes;
 6 
 7     assert(SG(request_info).content_length >= SG(read_post_bytes));
 8 
 9     remaining_bytes = (size_t)(SG(request_info).content_length - SG(read_post_bytes));
10 
11     count_bytes = MIN(count_bytes, remaining_bytes);
12     while (read_bytes < count_bytes) {
13 #ifdef PHP_WIN32
14         size_t diff = count_bytes - read_bytes;
15         unsigned int to_read = (diff > UINT_MAX) ? UINT_MAX : (unsigned int)diff;
16 
17         tmp_read_bytes = read(STDIN_FILENO, buffer + read_bytes, to_read);
18 #else
19         tmp_read_bytes = read(STDIN_FILENO, buffer + read_bytes, count_bytes - read_bytes);
20 #endif
21         if (tmp_read_bytes <= 0) {
22             break;
23         }
24         read_bytes += tmp_read_bytes;
25     }
26     return read_bytes;
27 }

......

在編譯php時,如果你需要fastcgi支持:

You must add '--enable-fastcgi' to the configure command on Linux or
OSX based systems to get fastcgi support in the php-cgi binary. You
also must not use '--enable-discard-path'.

如果在Apache中使用:

Using FastCGI PHP with Apache
=============================

#LoadModule php7_module /usr/lib/apache/2.0/libphp7.so

ScriptAlias /fcgi-bin/ /space/fcgi-bin/
<Location /fcgi-bin/>
Options ExecCGI
SetHandler fastcgi-script
</Location>

......

SAPI 服務器端抽象層代碼實現。

看完哪些C語言代碼我是頭暈的,每天進步一點點!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM