PHP內核之SAPI:Apache2 SAPI分析

1、首先概念普及:

SAPI: Server abstraction API,它提供了一個接口,使得PHP可以和其他應用進行交互數據,具體點說是提供了一個和外部通信的接口。常見的:給apache的mod_php5,CGI,給IIS的ISAPI,還有Shell的CLI

首先我們看個從鳥哥那挪來的PHP架構圖:


如果還感覺概念模糊的話 可以試着用wamp升級php版本來找下感覺

首先說下本篇以Apache sapi 爲例來介紹,對sapi的功能實現比較全嘛, 還常用

如果想看簡單的可從cgi入手,鳥哥有對此介紹:http://www.laruence.com/2008/08/12/180.html


2、接下來我們來看具體的實現:

要定義個SAPI,首先要定義個sapi_module_struct,查看源碼:php-5.5.12/sapi/apache2handler/sapi_apache2.c:

static sapi_module_struct apache2_sapi_module = {
	"apache2handler",					 							/* 輸出給php_info()使用 */
	"Apache 2.0 Handler",				 							/* pretty name */

	php_apache2_startup,				/* startup */				/* 當SAPI初始化時,首先會調用該函數 */
	php_module_shutdown_wrapper,			/* shutdown */ 			/*  關閉函數包裝器,它用來釋放所有的SAPI的數據結構、內存等,調用php_module_shutdown */

	NULL,						/* activate */						/* 此函數會在每個請求開始時調用,它會做初始化,資源分配 */
	NULL,						/* deactivate */					/* 此函數會在每個請求結束時調用,它用來確保所有的數據都得到釋放 */

	php_apache_sapi_ub_write,			/* unbuffered write */		/* 不緩存的寫操作(unbuffered write),它是用來向SAPI外部輸出數據 */
	php_apache_sapi_flush,				/* flush */					/* 刷新輸出,在CLI模式下通過使用C語言的庫函數fflush實現 */
	php_apache_sapi_get_stat,			/* get uid */				/* */
	php_apache_sapi_getenv,				/* getenv */				/* 根據name查找環境變量 */

	php_error,					/* error handler */					/* 註冊錯誤處理函數 */

	php_apache_sapi_header_handler,			/* header handler */	/* PHP調用header()時候被調用 */
	php_apache_sapi_send_headers,			/* send headers handler *//* 發送頭部信息 */
	NULL,						/* send header handler */			/* 發送一個單獨的頭部信息 */

	php_apache_sapi_read_post,			/* read POST data */		/* 當請求的方法是POST時,程序獲取POST數據,寫入$_POST數組 */
	php_apache_sapi_read_cookies,			/* read Cookies */		/* 獲取Cookie值 */

	php_apache_sapi_register_variables,								/* register server variables 給$_SERVER添加環境變量 */
	php_apache_sapi_log_message,			/* Log message */		/* 輸出錯誤信息 */
	php_apache_sapi_get_request_time,		/* Request Time */		/* */
	NULL,						/* Child Terminate */				/* */

	STANDARD_SAPI_MODULE_PROPERTIES
};

由上面代碼,再結合SAPI.h和SAPI.c,其實可以看出 PHP的SAPI像是 面向對象中基類,SAPI.h和SAPI.c包含的函數是抽象基類的聲明和定義,各個服務器用的SAPI模式,則是繼承了這個基類,並重新定義基類方法的子類。


1,php_apache2_startup:當通過apache調用PHP時,這個函數會被調用。該函數定義如下,主要是對PHP進行初始化。

static int php_apache2_startup(sapi_module_struct *sapi_module)
{
	if (php_module_startup(sapi_module, &php_apache_module, 1)==FAILURE) {
		return FAILURE;
	}
	return SUCCESS;
}

其中主要做得工作:

啓動

  • 初始化若干全局變量

這裏的初始化全局變量大多數情況下是將氣設置爲NULL,有一些除外,比如設置zuf(zend_utility_functions),

  • 初始化若干常量

         這裏的常量是PHP自己的常量

......省略若干.....
	/* Register constants */
	REGISTER_MAIN_STRINGL_CONSTANT("PHP_VERSION", PHP_VERSION, sizeof(PHP_VERSION)-1, CONST_PERSISTENT | CONST_CS);
	REGISTER_MAIN_LONG_CONSTANT("PHP_MAJOR_VERSION", PHP_MAJOR_VERSION, CONST_PERSISTENT | CONST_CS);
	REGISTER_MAIN_LONG_CONSTANT("PHP_MINOR_VERSION", PHP_MINOR_VERSION, CONST_PERSISTENT | CONST_CS);
	REGISTER_MAIN_LONG_CONSTANT("PHP_RELEASE_VERSION", PHP_RELEASE_VERSION, CONST_PERSISTENT | CONST_CS);
	REGISTER_MAIN_STRINGL_CONSTANT("PHP_EXTRA_VERSION", PHP_EXTRA_VERSION, sizeof(PHP_EXTRA_VERSION) - 1, CONST_PERSISTENT | CONST_CS);
	REGISTER_MAIN_LONG_CONSTANT("PHP_VERSION_ID", PHP_VERSION_ID, CONST_PERSISTENT | CONST_CS);
.....省略若干....

  • 初始化Zend引擎和核心組件

這裏的初始化主要有 

內存管理初始化,

全局使用的函數指針初始化,

 對PHP源文件進行詞法分析、語法分析、中間代碼執行的函數指針的賦值,

初始化若干HashTable,

爲ini文件解析做準備,

爲PHP源文件解析做準備,

註冊內置函數,

註冊變準常量,

註冊GLOBALS全局變量等

  • 解析php.ini
  • 全局操作函數的初始化
  • 啓動靜態構建的模塊
  • 啓動php.ini中需要加載的 共享模塊(MINIT)
  • 禁用函數和類

2,php_module_shutdown_wrapper :PHP的關閉函數。

3,PHP會在每個request的時候,處理一些初始化,資源分配的事務。這部分就是activate字段要定義的。

4,deactiveate,它會提供一個handler, 用來處理收尾工作。

5,php_apache_sapi_ub_write:提供一個向Response數據寫的接口。

static int
php_apache_sapi_ub_write(const char *str, uint str_length TSRMLS_DC)
{
	request_rec *r;
	php_struct *ctx;

	ctx = SG(server_context);
	r = ctx->r;

	if (ap_rwrite(str, str_length, r) < 0) {
		php_handle_aborted_connection();
	}

	return str_length; /* we always consume all the data passed to us. */
}

6,php_apache_sapi_flush:提供給zend刷新緩存的句柄。

static void
php_apache_sapi_flush(void *server_context)
{
	php_struct *ctx;
	request_rec *r;
	TSRMLS_FETCH();

	ctx = server_context;

	/* If we haven't registered a server_context yet,
	 * then don't bother flushing. */
	if (!server_context) {
		return;
	}

	r = ctx->r;

	sapi_send_headers(TSRMLS_C);

	r->status = SG(sapi_headers).http_response_code;
	SG(headers_sent) = 1;

	if (ap_rflush(r) < 0 || r->connection->aborted) {
		php_handle_aborted_connection();
	}
}

7,php_apache_sapi_get_stat:這部分用來讓Zend可以驗證一個要執行腳本文件的state,從而判斷文件是否據有執行權限等等。

static struct stat*
php_apache_sapi_get_stat(TSRMLS_D)
{
	php_struct *ctx = SG(server_context);

	ctx->finfo.st_uid = ctx->r->finfo.user;
	ctx->finfo.st_gid = ctx->r->finfo.group;
	ctx->finfo.st_dev = ctx->r->finfo.device;
	ctx->finfo.st_ino = ctx->r->finfo.inode;
#if defined(NETWARE) && defined(CLIB_STAT_PATCH)
	ctx->finfo.st_atime.tv_sec = apr_time_sec(ctx->r->finfo.atime);
	ctx->finfo.st_mtime.tv_sec = apr_time_sec(ctx->r->finfo.mtime);
	ctx->finfo.st_ctime.tv_sec = apr_time_sec(ctx->r->finfo.ctime);
#else
	ctx->finfo.st_atime = apr_time_sec(ctx->r->finfo.atime);
	ctx->finfo.st_mtime = apr_time_sec(ctx->r->finfo.mtime);
	ctx->finfo.st_ctime = apr_time_sec(ctx->r->finfo.ctime);
#endif

	ctx->finfo.st_size = ctx->r->finfo.size;
	ctx->finfo.st_nlink = ctx->r->finfo.nlink;

	return &ctx->finfo;
}

8,php_apache_sapi_getenv:爲Zend提供了一個根據name來查找環境變量的接口,當我們在腳本中調用getenv的時候,就會間接的調用這個句柄。

static char *
php_apache_sapi_getenv(char *name, size_t name_len TSRMLS_DC)
{
	php_struct *ctx = SG(server_context);
	const char *env_var;

	if (ctx == NULL) {
		return NULL;
	}

	env_var = apr_table_get(ctx->r->subprocess_env, name);

	return (char *) env_var;
}

9,php_error:錯誤處理函數,直接調用PHP錯誤處理函數。

10,php_apache_sapi_header_handler:在調用PHP的header()函數時,會調用這個函數。

static int
php_apache_sapi_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC)
{
	php_struct *ctx;
	char *val, *ptr;

	ctx = SG(server_context);

	switch (op) {
		case SAPI_HEADER_DELETE:
			apr_table_unset(ctx->r->headers_out, sapi_header->header);
			return 0;

		case SAPI_HEADER_DELETE_ALL:
			apr_table_clear(ctx->r->headers_out);
			return 0;

		case SAPI_HEADER_ADD:
		case SAPI_HEADER_REPLACE:
			val = strchr(sapi_header->header, ':');

			if (!val) {
				return 0;
			}
			ptr = val;

			*val = '\0';

			do {
				val++;
			} while (*val == ' ');

			if (!strcasecmp(sapi_header->header, "content-type")) {
				if (ctx->content_type) {
					efree(ctx->content_type);
				}
				ctx->content_type = estrdup(val);
			} else if (!strcasecmp(sapi_header->header, "content-length")) {
#ifdef PHP_WIN32
# ifdef APR_HAS_LARGE_FILES
				ap_set_content_length(ctx->r, (apr_off_t) _strtoui64(val, (char **)NULL, 10));
# else
				ap_set_content_length(ctx->r, (apr_off_t) strtol(val, (char **)NULL, 10));
# endif
#else
				ap_set_content_length(ctx->r, (apr_off_t) strtol(val, (char **)NULL, 10));
#endif
			} else if (op == SAPI_HEADER_REPLACE) {
				apr_table_set(ctx->r->headers_out, sapi_header->header, val);
			} else {
				apr_table_add(ctx->r->headers_out, sapi_header->header, val);
			}

			*ptr = ':';

			return SAPI_HEADER_ADD;

		default:
			return 0;
	}
}

11,php_apache_sapi_send_headers:當要真正發送header的時候,這個函數會被調用。

static int
php_apache_sapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{
	php_struct *ctx = SG(server_context);
	const char *sline = SG(sapi_headers).http_status_line;

	ctx->r->status = SG(sapi_headers).http_response_code;

	/* httpd requires that r->status_line is set to the first digit of
	 * the status-code: */
	if (sline && strlen(sline) > 12 && strncmp(sline, "HTTP/1.", 7) == 0 && sline[8] == ' ') {
		ctx->r->status_line = apr_pstrdup(ctx->r->pool, sline + 9);
		ctx->r->proto_num = 1000 + (sline[7]-'0');
		if ((sline[7]-'0') == 0) {
			apr_table_set(ctx->r->subprocess_env, "force-response-1.0", "true");
		}
	}

	/*	call ap_set_content_type only once, else each time we call it,
		configured output filters for that content type will be added */
	if (!ctx->content_type) {
		ctx->content_type = sapi_get_default_content_type(TSRMLS_C);
	}
	ap_set_content_type(ctx->r, apr_pstrdup(ctx->r->pool, ctx->content_type));
	efree(ctx->content_type);
	ctx->content_type = NULL;

	return SAPI_HEADER_SENT_SUCCESSFULLY;
}

12,在php_apache_sapi_send_headers指針下面有一個域,用來指明發送每一個單獨的header時調用。

13,php_apache_sapi_read_post:表示如何讀取POST數據。

static int
php_apache_sapi_read_post(char *buf, uint count_bytes TSRMLS_DC)
{
	apr_size_t len, tlen=0;
	php_struct *ctx = SG(server_context);
	request_rec *r;
	apr_bucket_brigade *brigade;

	r = ctx->r;
	brigade = ctx->brigade;
	len = count_bytes;

	/*
	 * This loop is needed because ap_get_brigade() can return us partial data
	 * which would cause premature termination of request read. Therefor we
	 * need to make sure that if data is available we fill the buffer completely.
	 */

	while (ap_get_brigade(r->input_filters, brigade, AP_MODE_READBYTES, APR_BLOCK_READ, len) == APR_SUCCESS) {
		apr_brigade_flatten(brigade, buf, &len);
		apr_brigade_cleanup(brigade);
		tlen += len;
		if (tlen == count_bytes || !len) {
			break;
		}
		buf += len;
		len = count_bytes - tlen;
	}

	return tlen;
}

14,php_apache_sapi_read_cookie:如何讀取cookie。

static char *
php_apache_sapi_read_cookies(TSRMLS_D)
{
	php_struct *ctx = SG(server_context);
	const char *http_cookie;

	http_cookie = apr_table_get(ctx->r->headers_in, "cookie");

	/* The SAPI interface should use 'const char *' */
	return (char *) http_cookie;
}

15,php_apache_sapi_register_variables:提供接口,用於給$_SERVER[]數組提供變量。

static void
php_apache_sapi_register_variables(zval *track_vars_array TSRMLS_DC)
{
	php_struct *ctx = SG(server_context);
	const apr_array_header_t *arr = apr_table_elts(ctx->r->subprocess_env);
	char *key, *val;
	int new_val_len;

	APR_ARRAY_FOREACH_OPEN(arr, key, val)
		if (!val) {
			val = "";
		}
		if (sapi_module.input_filter(PARSE_SERVER, key, &val, strlen(val), &new_val_len TSRMLS_CC)) {
			php_register_variable_safe(key, val, new_val_len, track_vars_array TSRMLS_CC);
		}
	APR_ARRAY_FOREACH_CLOSE()

	if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &ctx->r->uri, strlen(ctx->r->uri), &new_val_len TSRMLS_CC)) {
		php_register_variable_safe("PHP_SELF", ctx->r->uri, new_val_len, track_vars_array TSRMLS_CC);
	}
}

16,php_apache_sapi_log_message:輸出錯誤信息。

static void php_apache_sapi_log_message(char *msg TSRMLS_DC)
{
	php_struct *ctx;

	ctx = SG(server_context);

	if (ctx == NULL) { /* we haven't initialized our ctx yet, oh well */
		ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, "%s", msg);
	} else {
		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "%s", msg);
	}
}

17,php_apache_sapi_get_request_time:獲取請求時間。

static double php_apache_sapi_get_request_time(TSRMLS_D)
{
	php_struct *ctx = SG(server_context);
	return ((double) apr_time_as_msec(ctx->r->request_time)) / 1000.0;
}

這就完成了apache的SAPI定義【通過這個SAPI的分析 我也可以想象其他SAPI的實現機制

之後當用戶用URL請求apache服務,這些函數指針就會在適當的時候,發揮作用了(被調用)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章