深入理解Zend SAPIs(Zend SAPI Internals)

来源:互联网 发布:中国保险网络大学注册 编辑:程序博客网 时间:2024/04/30 13:56

·        作者Laruence(   )

·        本文地址http://www.laruence.com/2008/08/12/180.html

·        转载请注明出处

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

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

PHP架构图



1 PHP Architecture


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

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

1.        */

2.       static sapi_module_structcgi_sapi_module = {

3.       #if PHP_FASTCGI

4.           "cgi-fcgi",                    /* name */

5.           "CGI/FastCGI",                 /* pretty name */

6.       #else

7.           "cgi",                         /* name */

8.           "CGI",                         /* prettyname */

9.       #endif

10.     

11.        php_cgi_startup,               /* startup */

12.        php_module_shutdown_wrapper,   /* shutdown */

13.     

14.        NULL,                          /* activate */

15.        sapi_cgi_deactivate,           /* deactivate */

16.     

17.        sapi_cgibin_ub_write,          /* unbuffered write */

18.        sapi_cgibin_flush,             /* flush */

19.        NULL,                          /* get uid */

20.        sapi_cgibin_getenv,            /* getenv */

21.     

22.        php_error,                     /* error handler */

23.     

24.        NULL,                           /* header handler */

25.        sapi_cgi_send_headers,         /* send headers handler */

26.        NULL,                          /* send header handler */

27.     

28.        sapi_cgi_read_post,            /* read POST data */

29.        sapi_cgi_read_cookies,         /* read Cookies */

30.     

31.        sapi_cgi_register_variables,   /* register server variables */

32.        sapi_cgi_log_message,          /* Log message */

33.        NULL,                          /* Get request time */

34.     

35.        STANDARD_SAPI_MODULE_PROPERTIES

36.    };

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

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

1.          staticintphp_cgi_startup(sapi_module_struct *sapi_module)

2.       {

3.           if (php_module_startup(sapi_module, NULL, 0) == FAILURE) {

4.               return FAILURE;

5.           }

6.           return SUCCESS;

7.       }

8.         

·  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关闭前得到所有的输出数据:

1.         static intsapi_cgi_deactivate(TSRMLS_D)

2.       {

3.           /* flush only when SAPI was started. Thereasons are:

4.               1. SAPIDeactivate is called from two places: module init and request shutdown

5.               2. When thefirst call occurs and the request is not set up, flush fails on

6.                   FastCGI.

7.           */

8.           if (SG(sapi_started)) {

9.               sapi_cgibin_flush(SG(server_context));

10.        }

11.        return SUCCESS;

12.    }

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

1.        

2.       static inline size_tsapi_cgibin_single_write(const char *str, uint str_length TSRMLS_DC)

3.       {

4.       #ifdef PHP_WRITE_STDOUT

5.           long ret;

6.       #else

7.           size_t ret;

8.       #endif

9.        

10.    #if PHP_FASTCGI

11.        if (fcgi_is_fastcgi()) {

12.            fcgi_request*request = (fcgi_request*)SG(server_context);

13.            long ret =fcgi_write(request, FCGI_STDOUT, str, str_length);

14.            if (ret <=0) {

15.                return0;

16.            }

17.            return ret;

18.        }

19.    #endif

20.    #ifdef PHP_WRITE_STDOUT

21.        ret =write(STDOUT_FILENO, str, str_length);

22.        if (ret <=0)return0;

23.        return ret;

24.    #else

25.        ret =fwrite(str,1,MIN(str_length,16384), stdout);

26.        return ret;

27.    #endif

28.    }

29.     

30.    static intsapi_cgibin_ub_write(const char *str, uint str_length TSRMLS_DC)

31.    {

32.        const char *ptr = str;

33.        uint remaining = str_length;

34.        size_t ret;

35.     

36.        while (remaining >0) {

37.            ret =sapi_cgibin_single_write(ptr, remaining TSRMLS_CC);

38.            if (!ret) {

39.                php_handle_aborted_connection();

40.                return str_length - remaining;

41.            }

42.            ptr += ret;

43.            remaining-= ret;

44.        }

45.     

46.        return str_length;

47.    }

把真正的写的逻辑剥离出来,就是为了简单实现兼容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:

1.       static char *sapi_cgibin_getenv(char *name, size_t name_len TSRMLS_DC)

2.       {

3.       #if PHP_FASTCGI

4.           /* when php is started by mod_fastcgi, noregular environment

5.              is providedto PHP.  It is always sent to PHP at thestart

6.              of arequest.  So we have to do our own lookupto get env

7.              vars.  This could probably be faster somehow.  */

8.           if (fcgi_is_fastcgi()) {

9.               fcgi_request*request = (fcgi_request*)SG(server_context);

10.            returnfcgi_getenv(request, name, name_len);

11.        }

12.    #endif

13.        /*  ifcgi, or fastcgi and not found in fcgi env

14.            check theregular environment */

15.        returngetenv(name);

16.    }

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

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

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

1.       static intsapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)

2.       {

3.           char buf[SAPI_CGI_MAX_HEADER_LENGTH];

4.           sapi_header_struct *h;

5.           zend_llist_position pos;

6.        

7.           if (SG(request_info).no_headers == 1) {

8.               return SAPI_HEADER_SENT_SUCCESSFULLY;

9.           }

10.     

11.        if (cgi_nph ||SG(sapi_headers).http_response_code !=200)

12.        {

13.            int len;

14.     

15.            if (rfc2616_headers &&SG(sapi_headers).http_status_line) {

16.                len=snprintf(buf, SAPI_CGI_MAX_HEADER_LENGTH,

17.                               "%s\r\n",SG(sapi_headers).http_status_line);

18.     

19.                if (len >SAPI_CGI_MAX_HEADER_LENGTH) {

20.                    len= SAPI_CGI_MAX_HEADER_LENGTH;

21.                }

22.     

23.            }else {

24.                len=sprintf(buf,"Status: %d\r\n",SG(sapi_headers).http_response_code);

25.            }

26.     

27.            PHPWRITE_H(buf, len);

28.        }

29.     

30.        h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);

31.        while (h) {

32.            /* prevent CRLFCRLF */

33.            if (h->header_len) {

34.                PHPWRITE_H(h->header, h->header_len);

35.                PHPWRITE_H("\r\n",2);

36.            }

37.            h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);

38.        }

39.        PHPWRITE_H("\r\n",2);

40.     

41.        return SAPI_HEADER_SENT_SUCCESSFULLY;

42.       }

43.     

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

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

1.       static intsapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC)

2.       {

3.           uint read_bytes=0, tmp_read_bytes;

4.       #if PHP_FASTCGI

5.           char *pos = buffer;

6.       #endif

7.        

8.           count_bytes =MIN(count_bytes, (uint)SG(request_info).content_length -SG(read_post_bytes));

9.           while (read_bytes < count_bytes) {

10.    #if PHP_FASTCGI

11.            if (fcgi_is_fastcgi()) {

12.                fcgi_request*request = (fcgi_request*)SG(server_context);

13.                tmp_read_bytes=fcgi_read(request, pos, count_bytes - read_bytes);

14.                pos+= tmp_read_bytes;

15.            }else {

16.                tmp_read_bytes=read(0, buffer + read_bytes, count_bytes - read_bytes);

17.            }

18.    #else

19.            tmp_read_bytes=read(0, buffer + read_bytes, count_bytes - read_bytes);

20.    #endif

21.     

22.            if (tmp_read_bytes <=0) {

23.                break;

24.            }

25.            read_bytes+= tmp_read_bytes;

26.        }

27.        return read_bytes;

28.    }

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

1.       static char *sapi_cgi_read_cookies(TSRMLS_D)

2.       {

3.           returnsapi_cgibin_getenv((char *) "HTTP_COOKIE",sizeof("HTTP_COOKIE")-1 TSRMLS_CC);

4.       }

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

1.       static voidsapi_cgi_register_variables(zval *track_vars_arrayTSRMLS_DC)

2.       {

3.           /* In CGI mode, we consider the environmentto be a part of the server

4.            * variables

5.            */

6.           php_import_environment_variables(track_vars_array TSRMLS_CC);

7.           /* Build the special-case PHP_SELF variablefor the CGI version */

8.           php_register_variable("PHP_SELF", (SG(request_info).request_uri ?SG(request_info).request_uri :""), track_vars_array TSRMLS_CC);

9.       }

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

1.       static voidsapi_cgi_log_message(char *message)

2.       {

3.       #if PHP_FASTCGI

4.           if (fcgi_is_fastcgi() && fcgi_logging) {

5.               fcgi_request*request;

6.               TSRMLS_FETCH();

7.        

8.               request = (fcgi_request*)SG(server_context);

9.               if (request) {

10.                intlen =strlen(message);

11.                char*buf =malloc(len+2);

12.     

13.                memcpy(buf, message, len);

14.                memcpy(buf + len,"\n",sizeof("\n"));

15.                fcgi_write(request, FCGI_STDERR, buf, len+1);

16.                free(buf);

17.            }else {

18.                fprintf(stderr,"%s\n", message);

19.            }

20.            /* ignore return code */

21.        }else

22.    #endif /* PHP_FASTCGI */

23.        fprintf(stderr,"%s\n", message);

24.    }

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

0 0