谈谈你对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 相关文章推荐
用phpmyadmin更改mysql5.0登录密码
Mar 25 PHP
php下通过IP获取地理位置的代码(小偷程序)
Jun 09 PHP
如何使用Linux的Crontab定时执行PHP脚本的方法
Dec 19 PHP
深入PHP中慎用双等于(==)的详解
Jun 06 PHP
PHP、Nginx、Apache中禁止网页被iframe引用的方法
Oct 01 PHP
php数组中删除元素之重新索引的方法
Sep 16 PHP
PHP7正式版测试,性能惊艳!
Dec 08 PHP
Zend Framework连接Mysql数据库实例分析
Mar 19 PHP
php根据数据id自动生成编号的实现方法
Oct 16 PHP
CI框架常用函数封装实例
Nov 21 PHP
PHP中字符串长度的截取用法示例
Jan 12 PHP
php从数据库读取数据,并以json格式返回数据的方法
Aug 21 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
PHP4与PHP5的时间格式问题
2008/02/17 PHP
理解和运用PHP中的多态性[译]
2011/08/02 PHP
PHP可变函数的使用详解
2013/06/14 PHP
smarty模板中使用get、post、request、cookies、session变量的方法
2014/04/24 PHP
PHP+redis实现微博的推模型案例分析
2019/07/10 PHP
JS版网站风格切换实例代码
2008/10/06 Javascript
javascript+iframe 实现无刷新载入整页的代码
2010/03/17 Javascript
js判读浏览器是否支持html5的canvas的代码
2013/11/18 Javascript
jquery实现的代替传统checkbox样式插件
2015/06/19 Javascript
JS定时器使用,定时定点,固定时刻,循环执行详解
2016/05/31 Javascript
JavaScript-定时器0~9抽奖系统详解(代码)
2017/08/16 Javascript
Layui多选只有最后一个值的解决方法
2019/09/02 Javascript
微信小程序左滑删除实现代码实例
2019/09/16 Javascript
解决vuex数据页面刷新后初始化操作
2020/07/26 Javascript
[03:22]DOTA2超级联赛专访单车:找到属于自己的英雄
2013/06/08 DOTA
python小技巧之批量抓取美女图片
2014/06/06 Python
Python 结巴分词实现关键词抽取分析
2017/10/21 Python
Ubuntu18.04下python版本完美切换的解决方法
2019/06/14 Python
Python3批量生成带logo的二维码方法
2019/06/24 Python
Python在cmd上打印彩色文字实现过程详解
2019/08/07 Python
django自带serializers序列化返回指定字段的方法
2019/08/21 Python
简单介绍一下pyinstaller打包以及安全性的实现
2020/06/02 Python
有关HTML5 Video对象的ontimeupdate事件(Chrome上无效)的问题
2013/07/19 HTML / CSS
June Jacobs尊积帕官网:知名的spa水疗护肤品牌
2019/03/21 全球购物
外贸学院会计专业应届生求职信
2013/11/14 职场文书
办护照工作证明范本
2014/01/14 职场文书
师德个人剖析材料
2014/02/02 职场文书
老总助理工作岗位职责
2014/02/06 职场文书
初中高效课堂实施方案
2014/02/26 职场文书
四年级学生评语大全
2014/04/21 职场文书
党支部特色活动方案
2014/08/20 职场文书
工作作风承诺书
2014/08/30 职场文书
四风个人对照检查材料思想汇报(办公室通用版)
2014/10/07 职场文书
开除员工通知
2015/04/22 职场文书
爸爸的三轮车观后感
2015/06/16 职场文书
Centos系统通过Docker安装并搭建MongoDB数据库
2022/04/12 MongoDB