SAPI: Server Application Programming Interface 服务器端应用编程端口。先看一张php模块图。
从图中可以看出,各种应用都是通过对应的SAPI与php进行交互的,SAPI相当于一个接口,使得php的核心实现不用关心各个应用交互的细节。虽然通过Web服务器和命令行程序执行脚本看起来很不一样,实际上它们的工作流程是一样的。
在php的源代码sapi目录下有多种sapi的具体实现,比如cgi、cli、apache、fpm等。SAPI中最重要的一个数据结构就是_sapi_module_struct
,定义在/main/SAPI.h中。
struct _sapi_module_struct {
char *name; //应用层的名称,比如cgi,apache等
char *pretty_name; //应用层更易读的名字
int (*startup)(struct _sapi_module_struct *sapi_module); //startup 函数指针, 当一个应用要调用PHP的时候,这个函数会被调用
int (*shutdown)(struct _sapi_module_struct *sapi_module); // shutdown 函数指针,
int (*activate)(TSRMLS_D); //active 函数指针,PHP会在每个request的时候,处理一些初始化,资源分配的事务。这部分就是activate字段要定义的
int (*deactivate)(TSRMLS_D); //deactivate函数指针,这个是对应与activate的函数,顾名思义,它会提供一个handler, 用来处理收尾工作
int (*ub_write)(const char *str, unsigned int str_length TSRMLS_DC); //这个hanlder告诉了php如何输出数据,比如cgi和fpm模式下输出数据方式肯定不一样
void (*flush)(void *server_context); //这个是提供给php的刷新缓存的函数指针
struct stat *(*get_stat)(TSRMLS_D); //这部分用来让php可以验证一个要执行脚本文件的state,从而判断文件是否据有执行权限等等
char *(*getenv)(char *name, size_t name_len TSRMLS_DC); //为Zend提供了一个根据name来查找环境变量的接口
void (*sapi_error)(int type, const char *error_msg, ...); //错误处理函数指针
int (*header_handler)(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC); // 这个函数会在我们调用PHP的header()函数的时候被调用
int (*send_headers)(sapi_headers_struct *sapi_headers TSRMLS_DC); //这个函数会在要真正发送header的时候被调用,一般来说,就是当有任何的输出要发送之前
void (*send_header)(sapi_header_struct *sapi_header, void *server_context TSRMLS_DC); //单独发送每一个header的函数指针
int (*read_post)(char *buffer, uint count_bytes TSRMLS_DC); //这个句函数指针明了如何获取POST的数据
char *(*read_cookies)(TSRMLS_D); //这个句函数指针明了如何获取COOKIE的数据
void (*register_server_variables)(zval *track_vars_array TSRMLS_DC); //这个函数给了一个接口,用以给$_SERVER变量中添加变量
void (*log_message)(char *message TSRMLS_DC); //用来输出错误信息的函数指针
double (*get_request_time)(TSRMLS_D); //获得请求时间的函数指针
void (*terminate_process)(TSRMLS_D);
char *php_ini_path_override;
void (*block_interruptions)(void);
void (*unblock_interruptions)(void);
void (*default_post_reader)(TSRMLS_D);
void (*treat_data)(int arg, char *str, zval *destArray TSRMLS_DC);
char *executable_location;
int php_ini_ignore;
int php_ini_ignore_cwd; /* don't look for php.ini in the current directory */
int (*get_fd)(int *fd TSRMLS_DC);
int (*force_http_10)(TSRMLS_D);
int (*get_target_uid)(uid_t * TSRMLS_DC);
int (*get_target_gid)(gid_t * TSRMLS_DC);
unsigned int (*input_filter)(int arg, char *var, char **val, unsigned int val_len, unsigned int *new_val_len TSRMLS_DC);
void (*ini_defaults)(HashTable *configuration_hash);
int phpinfo_as_text;
char *ini_entries;
const zend_function_entry *additional_functions;
unsigned int (*input_filter_init)(TSRMLS_D);
};
不同的SAPI就是用不同的参数实例化_sapi_module_struct来实习的,下面我们分别简单分析一些cgi SAPI和cli SAPI的源代码,力求对SAPI有些更深入的理解。
1.cgi模式
cgi模式下,_sapi_module_struct的实例定义在cgi_main.c中。
static sapi_module_struct cgi_sapi_module = {
"cgi-fcgi", /* name */
"CGI/FastCGI", /* pretty name */
php_cgi_startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */
sapi_cgi_activate, /* activate */
sapi_cgi_deactivate, /* deactivate */
sapi_cgi_ub_write, /* unbuffered write */
sapi_cgi_flush, /* flush */
NULL, /* get uid */
sapi_cgi_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 */
NULL, /* Child terminate */
STANDARD_SAPI_MODULE_PROPERTIES
};
下面分析 char *(*read_cookies)(TSRMLS_D)
在cgi模式下的实现:sapi_cgi_read_cookies。其中sapi_cgi_read_cookies的源码片段如下:
static char *sapi_cgi_read_cookies(TSRMLS_D)
{
return getenv("HTTP_COOKIE");
}
可以看到,cgi模式下的char *(*read_cookies)(TSRMLS_D)
最终为从环境变量中读取HTTP_COOKIE。
2.cli 模式
cgi模式下,_sapi_module_struct的实例定义在php_cli.c
static sapi_module_struct cli_sapi_module = {
"cli", /* name */
"Command Line Interface", /* pretty name */
php_cli_startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */
NULL, /* activate */
sapi_cli_deactivate, /* deactivate */
sapi_cli_ub_write, /* unbuffered write */
sapi_cli_flush, /* flush */
NULL, /* get uid */
NULL, /* getenv */
php_error, /* error handler */
sapi_cli_header_handler, /* header handler */
sapi_cli_send_headers, /* send headers handler */
sapi_cli_send_header, /* send header handler */
NULL, /* read POST data */
sapi_cli_read_cookies, /* read Cookies */
sapi_cli_register_variables, /* register server variables */
sapi_cli_log_message, /* Log message */
NULL, /* Get request time */
NULL, /* Child terminate */
STANDARD_SAPI_MODULE_PROPERTIES
};
下面分析 char *(*read_cookies)(TSRMLS_D)
在cli模式下的实现:sapi_cli_read_cookies。其中sapi_cli_read_cookies的源码片段如下:
static char* sapi_cli_read_cookies(TSRMLS_D)
{
return NULL;
}
可以看到,cgi模式下的char *(*read_cookies)(TSRMLS_D)
最终为直接返回NULL,因为cli模式下不存在用户cookies信息。
通过上面的cgi和cli模式下read_cookies的不同实现,可以看出sapi确实对下层php屏蔽了交互细节,当下层php核心要读取用户cookies时,只需要通过sapi_module_struct->read_cookies,而不需要关注上层应用的交互细节。
that right! 这就是SAPI的作用。