谈谈你对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 相关文章推荐
php pack与unpack 摸板字符字符含义
Oct 29 PHP
PHP使用CURL实现对带有验证码的网站进行模拟登录的方法
Jul 23 PHP
利用PHP函数计算中英文字符串长度的方法
Nov 11 PHP
php强大的时间转换函数strtotime
Feb 18 PHP
php生成txt文件实例代码介绍
Apr 28 PHP
Yii2中关联查询简单用法示例
Aug 10 PHP
php实现当前页面点击下载文件的简单方法
Sep 22 PHP
PHP中call_user_func_array回调函数的用法示例
Nov 26 PHP
php PDO判断连接是否可用的实现方法
Apr 03 PHP
PHP实现查询手机归属地的方法详解
Apr 28 PHP
php获取微信共享收货地址的方法
Dec 21 PHP
如何重写Laravel异常处理类详解
Dec 20 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下将XML转换为数组
2010/01/01 PHP
php全角字符转换为半角函数
2014/02/07 PHP
PHP转换文本框内容为HTML格式的方法
2016/07/20 PHP
PHP中include()与require()的区别说明
2017/02/14 PHP
php微信公众号开发之关键词回复
2018/10/20 PHP
javascript FormatNumber函数实现方法
2008/12/30 Javascript
IE6图片加载的一个BUG解决方法
2010/07/13 Javascript
jQuery判断密码强度实现思路及代码
2013/04/24 Javascript
jquery复选框多选赋值给文本框的方法
2015/01/27 Javascript
JQUERY实现网页右下角固定位置展开关闭特效的方法
2015/07/27 Javascript
Angular2表单自定义验证器的实现
2016/10/19 Javascript
微信小程序 自动登陆PHP源码实例(源码下载)
2017/05/08 Javascript
详解vue-cli开发环境跨域问题解决方案
2017/06/06 Javascript
解决vue中对象属性改变视图不更新的问题
2018/02/23 Javascript
JavaScript实现仿Clock ISO时钟
2018/06/29 Javascript
jQuery实现模拟搜索引擎的智能提示功能简单示例
2019/01/27 jQuery
vue中的 $slot 获取插槽的节点实例
2019/11/12 Javascript
angular共享依赖的解决方案分享
2020/10/15 Javascript
解决VUE项目使用Element-ui 下拉组件的验证失效问题
2020/11/07 Javascript
Python中map和列表推导效率比较实例分析
2015/06/17 Python
python 接口_从协议到抽象基类详解
2017/08/24 Python
Python中Scrapy爬虫图片处理详解
2017/11/29 Python
Python中extend和append的区别讲解
2019/01/24 Python
python中使用while循环的实例
2019/08/05 Python
Python使用graphviz画流程图过程解析
2020/03/31 Python
实例讲解使用HTML5 Canvas绘制阴影效果的方法
2016/03/25 HTML / CSS
生日寿宴答谢词
2014/01/19 职场文书
小加工厂管理制度
2014/01/21 职场文书
自行车租赁公司创业计划书
2014/01/28 职场文书
合伙购房协议样本
2014/10/06 职场文书
个人剖析材料及整改措施
2014/10/07 职场文书
三严三实心得体会范文
2014/10/13 职场文书
专业技术职务聘任证明
2015/03/02 职场文书
给女朋友的道歉短信
2015/05/12 职场文书
暗恋桃花源观后感
2015/06/12 职场文书
《少年闰土》教学反思
2016/02/18 职场文书