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的作用。