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)
- 禁用函数和类
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服务,这些函数指针就会在适当的时候,发挥作用了(被调用)。