谈谈你对Zend SAPIs(Zend SAPI Internals)的理解


Posted in PHP onNovember 10, 2015

SAPI: Server abstraction API,研究过PHP架构的同学应该知道这个东东的重要性,它提供了一个接口,使得PHP可以和其他应用进行交互数据。 本文不会详细介绍每个PHP的SAPI,只是针对最简单的CGI SAPI,来说明SAPI的机制。

首先,我们来看看PHP的架构图:

谈谈你对Zend SAPIs(Zend SAPI Internals)的理解

图1 PHP Architecture

SAPI提供了一个和外部通信的接口, 对于PHP5.2,默认提供了很多种SAPI, 常见的给apache的mod_php5,CGI,给IIS的ISAPI,还有Shell的CLI,本文就从CGI SAPI入手 ,介绍SAPI的机制。 虽然CGI简单,但是不用担心,它包含了绝大部分内容,足以让你深刻理解SAPI的工作原理。

要定义个SAPI,首先要定义个sapi_module_struct, 查看 PHP-SRC/sapi/cgi/cgi_main.c:

*/
static sapi_module_struct cgi_sapi_module = {
#if PHP_FASTCGI
 "cgi-fcgi",      /* name */
 "CGI/FastCGI",     /* pretty name */
#else
 "cgi",       /* name */
 "CGI",       /* pretty name */
#endif
 
 php_cgi_startup,    /* startup */
 php_module_shutdown_wrapper, /* shutdown */
 
 NULL,       /* activate */
 sapi_cgi_deactivate,   /* deactivate */
 
 sapi_cgibin_ub_write,   /* unbuffered write */
 sapi_cgibin_flush,    /* flush */
 NULL,       /* get uid */
 sapi_cgibin_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 */
 
 STANDARD_SAPI_MODULE_PROPERTIES
};

这个结构,包含了一些常量,比如name, 这个会在我们调用php_info()的时候被使用。一些初始化,收尾函数,以及一些函数指针,用来告诉Zend,如何获取,和输出数据。

1. php_cgi_startup, 当一个应用要调用PHP的时候,这个函数会被调用,对于CGI来说,它只是简单的调用了PHP的初始化函数:

static int php_cgi_startup(sapi_module_struct *sapi_module)
{
 if (php_module_startup(sapi_module, NULL, 0) == FAILURE) {
  return FAILURE;
 }
 return SUCCESS;
}

2. php_module_shutdown_wrapper , 一个对PHP关闭函数的简单包装。只是简单的调用php_module_shutdown;

3. PHP会在每个request的时候,处理一些初始化,资源分配的事务。这部分就是activate字段要定义的,从上面的结构我们可以看出,对于CGI来说,它并没有提供初始化处理句柄。对于mod_php来说,那就不同了,他要在apache的pool中注册资源析构函数, 申请空间, 初始化环境变量,等等等等。

4. sapi_cgi_deactivate, 这个是对应与activate的函数,顾名思义,它会提供一个handler, 用来处理收尾工作,对于CGI来说,他只是简单的刷新缓冲区,用以保证用户在Zend关闭前得到所有的输出数据:

static int sapi_cgi_deactivate(TSRMLS_D)
{
 /* flush only when SAPI was started. The reasons are:
  1. SAPI Deactivate is called from two places: module init and request shutdown
  2. When the first call occurs and the request is not set up, flush fails on
   FastCGI.
 */
 if (SG(sapi_started)) {
  sapi_cgibin_flush(SG(server_context));
 }
 return SUCCESS;
}

5. sapi_cgibin_ub_write, 这个hanlder告诉了Zend,如何输出数据,对于mod_php来说,这个函数提供了一个向response数据写的接口,而对于CGI来说,只是简单的写到stdout:

static inline size_t sapi_cgibin_single_write(const char *str, uint str_length TSRMLS_DC)
{
#ifdef PHP_WRITE_STDOUT
 long ret;
#else
 size_t ret;
#endif
#if PHP_FASTCGI
 if (fcgi_is_fastcgi()) {
  fcgi_request *request = (fcgi_request*) SG(server_context);
  long ret = fcgi_write(request, FCGI_STDOUT, str, str_length);
  if (ret <= 0) {
   return 0;
  }
  return ret;
 }
#endif
#ifdef PHP_WRITE_STDOUT
 ret = write(STDOUT_FILENO, str, str_length);
 if (ret <= 0) return 0;
 return ret;
#else
 ret = fwrite(str, 1, MIN(str_length, 16384), stdout);
 return ret;
#endif
}
static int sapi_cgibin_ub_write(const char *str, uint str_length TSRMLS_DC)
{
 const char *ptr = str;
 uint remaining = str_length;
 size_t ret;
 while (remaining > 0) {
  ret = sapi_cgibin_single_write(ptr, remaining TSRMLS_CC);
  if (!ret) {
   php_handle_aborted_connection();
   return str_length - remaining;
  }
  ptr += ret;
  remaining -= ret;
 }
 return str_length;
}

把真正的写的逻辑剥离出来,就是为了简单实现兼容fastcgi的写方式。

6. sapi_cgibin_flush, 这个是提供给zend的刷新缓存的函数句柄,对于CGI来说,只是简单的调用系统提供的fflush;

7.NULL, 这部分用来让Zend可以验证一个要执行脚本文件的state,从而判断文件是否据有执行权限等等,CGI没有提供。

8. sapi_cgibin_getenv, 为Zend提供了一个根据name来查找环境变量的接口,对于mod_php5来说,当我们在脚本中调用getenv的时候,就会间接的调用这个句柄。而对于CGI来说,因为他的运行机制和CLI很类似,直接调用父级是Shell, 所以,只是简单的调用了系统提供的genenv:

static char *sapi_cgibin_getenv(char *name, size_t name_len TSRMLS_DC)
{
#if PHP_FASTCGI
 /* when php is started by mod_fastcgi, no regular environment
  is provided to PHP. It is always sent to PHP at the start
  of a request. So we have to do our own lookup to get env
  vars. This could probably be faster somehow. */
 if (fcgi_is_fastcgi()) {
  fcgi_request *request = (fcgi_request*) SG(server_context);
  return fcgi_getenv(request, name, name_len);
 }
#endif
 /* if cgi, or fastcgi and not found in fcgi env
  check the regular environment */
 return getenv(name);
}

9. php_error, 错误处理函数, 到这里,说几句题外话,上次看到php maillist 提到的使得PHP的错误处理机制完全OO化, 也就是,改写这个函数句柄,使得每当有错误发生的时候,都throw一个异常。而CGI只是简单的调用了PHP提供的错误处理函数。

10. 这个函数会在我们调用PHP的header()函数的时候被调用,对于CGI来说,不提供。

11. sapi_cgi_send_headers, 这个函数会在要真正发送header的时候被调用,一般来说,就是当有任何的输出要发送之前:

static int sapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{
 char buf[SAPI_CGI_MAX_HEADER_LENGTH];
 sapi_header_struct *h;
 zend_llist_position pos;
 if (SG(request_info).no_headers == 1) {
  return SAPI_HEADER_SENT_SUCCESSFULLY;
 }
 if (cgi_nph || SG(sapi_headers).http_response_code != 200)
 {
  int len;
  if (rfc2616_headers && SG(sapi_headers).http_status_line) {
   len = snprintf(buf, SAPI_CGI_MAX_HEADER_LENGTH,
       "%s\r\n", SG(sapi_headers).http_status_line);
   if (len > SAPI_CGI_MAX_HEADER_LENGTH) {
    len = SAPI_CGI_MAX_HEADER_LENGTH;
   }
  } else {
   len = sprintf(buf, "Status: %d\r\n", SG(sapi_headers).http_response_code);
  }
  PHPWRITE_H(buf, len);
 }
 h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);
 while (h) {
  /* prevent CRLFCRLF */
  if (h->header_len) {
   PHPWRITE_H(h->header, h->header_len);
   PHPWRITE_H("\r\n", 2);
  }
  h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
 }
 PHPWRITE_H("\r\n", 2);
 return SAPI_HEADER_SENT_SUCCESSFULLY;
 }

 12. NULL, 这个用来单独发送每一个header, CGI没有提供

13. sapi_cgi_read_post, 这个句柄指明了如何获取POST的数据,如果做过CGI编程的话,我们就知道CGI是从stdin中读取POST DATA的,

static int sapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC)
{
 uint read_bytes=0, tmp_read_bytes;
#if PHP_FASTCGI
 char *pos = buffer;
#endif
 count_bytes = MIN(count_bytes, (uint) SG(request_info).content_length - SG(read_post_bytes));
 while (read_bytes < count_bytes) {
#if PHP_FASTCGI
  if (fcgi_is_fastcgi()) {
   fcgi_request *request = (fcgi_request*) SG(server_context);
   tmp_read_bytes = fcgi_read(request, pos, count_bytes - read_bytes);
   pos += tmp_read_bytes;
  } else {
   tmp_read_bytes = read(0, buffer + read_bytes, count_bytes - read_bytes);
  }
#else
  tmp_read_bytes = read(0, buffer + read_bytes, count_bytes - read_bytes);
#endif
  if (tmp_read_bytes <= 0) {
   break;
  }
  read_bytes += tmp_read_bytes;
 }
 return read_bytes;
}

14. sapi_cgi_read_cookies, 这个和上面的函数一样,只不过是去获取cookie值:

static char *sapi_cgi_read_cookies(TSRMLS_D)
{
 return sapi_cgibin_getenv((char *) "HTTP_COOKIE", sizeof("HTTP_COOKIE")-1 TSRMLS_CC);
}

15. sapi_cgi_register_variables, 这个函数给了一个接口,用以给$_SERVER变量中添加变量,对于CGI来说,注册了一个PHP_SELF,这样我们就可以在脚本中访问$_SERVER['PHP_SELF']来获取

本次的request_uri:

static void sapi_cgi_register_variables(zval *track_vars_array TSRMLS_DC)
{
 /* In CGI mode, we consider the environment to be a part of the server
  * variables
  */
 php_import_environment_variables(track_vars_array TSRMLS_CC);
 /* Build the special-case PHP_SELF variable for the CGI version */
 php_register_variable("PHP_SELF", (SG(request_info).request_uri ? SG(request_info).request_uri : ""), track_vars_array TSRMLS_CC);
}

16. sapi_cgi_log_message ,用来输出错误信息,对于CGI来说,只是简单的输出到stderr:

static void sapi_cgi_log_message(char *message)
{
#if PHP_FASTCGI
 if (fcgi_is_fastcgi() && fcgi_logging) {
  fcgi_request *request;
  TSRMLS_FETCH();
  request = (fcgi_request*) SG(server_context);
  if (request) {
   int len = strlen(message);
   char *buf = malloc(len+2);
   memcpy(buf, message, len);
   memcpy(buf + len, "\n", sizeof("\n"));
   fcgi_write(request, FCGI_STDERR, buf, len+1);
   free(buf);
  } else {
   fprintf(stderr, "%s\n", message);
  }
  /* ignore return code */
 } else
#endif /* PHP_FASTCGI */
 fprintf(stderr, "%s\n", message);
}

经过分析,我们已经了解了一个SAPI是如何实现的了, 分析过CGI以后,我们也就可以想象mod_php, embed等SAPI的实现机制。 :)

怎么样,本文介绍的是不是非常详细,希望大家喜欢。

PHP 相关文章推荐
第十二节--类的自动加载
Nov 16 PHP
PHP类的使用 实例代码讲解
Dec 28 PHP
PHP5中Cookie与 Session使用详解
Apr 30 PHP
CodeIgniter中实现泛域名解析
Jul 19 PHP
PHP中通过fopen()函数访问远程文件示例
Nov 18 PHP
php专用数组排序类ArraySortUtil用法实例
Apr 03 PHP
PHP利用APC模块实现大文件上传进度条的方法
Oct 29 PHP
PHP实现的随机IP函数【国内IP段】
Jul 20 PHP
PHP编辑器PhpStrom运行缓慢问题
Feb 21 PHP
PHP简单实现记录网站访问量功能示例
Jun 06 PHP
PHP压缩图片功能的介绍
Mar 21 PHP
PHP7 整型处理机制修改
Mar 09 PHP
php实现可运算的验证码
Nov 10 #PHP
如何使用PHP Embed SAPI实现Opcodes查看器
Nov 10 #PHP
深入理解PHP内核(二)之SAPI探究
Nov 10 #PHP
深入理解PHP内核(一)
Nov 10 #PHP
在PHP中使用FastCGI解析漏洞及修复方案
Nov 10 #PHP
PHP中使用GD库绘制折线图 折线统计图的绘制方法
Nov 09 #PHP
再推荐十款免费的php开发工具
Nov 09 #PHP
You might like
php 中英文语言转换类代码
2011/08/11 PHP
php在线代理转向代码
2012/05/05 PHP
php递归方法实现无限分类实例代码
2014/02/28 PHP
Codeigniter购物车类不能添加中文的解决方法
2014/11/29 PHP
PHP实现基于PDO扩展连接PostgreSQL对象关系数据库示例
2018/03/31 PHP
flash javascript之间的通讯方法小结
2008/12/20 Javascript
Javascript实现DIV滚动自动滚动到底部的代码
2012/03/01 Javascript
JS localStorage实现本地缓存的方法
2013/06/22 Javascript
javascript对JSON数据排序的3个例子
2014/04/12 Javascript
JavaScript中伪协议 javascript:使用探讨
2014/07/18 Javascript
jQuery实现列表自动滚动循环滚动展示新闻
2014/08/22 Javascript
JQuery显示隐藏DIV的方法及代码实例
2015/04/16 Javascript
JS正则匹配中文的方法示例
2017/01/06 Javascript
JS复制对应id的内容到粘贴板(Ctrl+C效果)
2017/01/23 Javascript
折叠菜单及选择器的运用
2017/02/03 Javascript
浅谈vue引入css,less遇到的坑和解决方法
2018/01/20 Javascript
js数组去重的N种方法(小结)
2018/06/07 Javascript
Angular 中使用 FineReport不显示报表直接打印预览
2019/08/21 Javascript
微信小程序 函数防抖 解决重复点击消耗性能问题实现代码
2019/09/12 Javascript
JS如何生成随机验证码
2020/03/02 Javascript
Python中django学习心得
2017/12/06 Python
详解Python中的正则表达式
2018/07/08 Python
Python中矩阵创建和矩阵运算方法
2018/08/04 Python
Python图像处理之简单画板实现方法示例
2018/08/30 Python
对python mayavi三维绘图的实现详解
2019/01/08 Python
Python爬虫 批量爬取下载抖音视频代码实例
2019/08/16 Python
超全Python图像处理讲解(多模块实现)
2020/04/13 Python
基于python计算滚动方差(标准差)talib和pd.rolling函数差异详解
2020/06/08 Python
css3媒体查询中device-width和width的区别详解
2020/03/27 HTML / CSS
免费获得微软MCSD证书赶快行动吧!
2012/11/13 HTML / CSS
党员承诺书内容
2014/03/26 职场文书
老干部工作先进事迹
2014/08/17 职场文书
2014镇党委书记党建工作汇报材料
2014/11/02 职场文书
小学班主任评语
2014/12/29 职场文书
《追风筝的人》:人心中的成见是座大山,但请不忘初心
2019/11/15 职场文书
Mysql官方性能测试工具mysqlslap的使用简介
2021/05/21 MySQL