php源码之路第二章第二节(SAPI概述之Apache模块)

生命周期的各个阶段,SAPI接口关系到很多服务的操作的实现,这些内置实现的物理位置在PHP源码的SAPI目录。这个目录存放了PHP对各个服务器抽象层的代码,例如命令行程序的实现,Apache的mod_php模块实现以及fastcgi的实现等等。

各个服务器抽象层之间遵守着相同的协议,我们称之为SAPI的接口。

php源码之路第二章第二节(SAPI概述之Apache模块)_第1张图片

以cgi模式和apache2服务器为例,它们的启动方法如下:

cgi_sapi_module.startup(&cgi_sapi_module)   //  cgi模式 cgi/cgi_main.c文件

apache2_sapi_module.startup(&apache2_sapi_module);
 //  apache2服务器  apache2handler/sapi_apache2.c文件
这里的cgi_sapi_module是sapi_module_struct结构体的静态变量。它的startup方法指向php_cgi_startup函数指针。在这个结构体中除了startup函数指针,还有许多其它方法或字段。
  1. startup 当SAPI初始化时,首先会调用该函数。如果服务器处理多个请求时,该函数只会调用一次。比如Apache的SAPI,它是以mod_php5的Apache模块的形式加载到Apache中的,在这个SAPI中,startup函数只在父进程中创建一次,在其fork的子进程中不会调用。

  2. activate 此函数会在每个请求开始时调用,它会再次初始化每个请求前的数据结构。

  3. deactivate 此函数会在每个请求结束时调用,它用来确保所有的数据都,以及释放在activate中初始化的数据结构。

  4. shutdown 关闭函数,它用来释放所有的SAPI的数据结构、内存等。

  5. ub_write 不缓存的写操作(unbuffered write),它是用来将PHP的数据输出给客户端,如在CLI模式下,其最终是调用fwrite实现向标准输出输出内容;在Apache模块中,它最终是调用Apache提供的方法rwrite。

  6. sapi_error 报告错误用,大多数的SAPI都是使用的PHP的默认实现php_error。

  7. flush 刷新输出,在CLI模式下通过使用C语言的库函数fflush实现,在php_mode5模式下,使用Apache的提供的函数函数rflush实现。

  8. read_cookie 在SAPI激活时,程序会调用此函数,并且将此函数获取的值赋值给SG(request_info).cookie_data。在CLI模式下,此函数会返回NULL。

  9. read_post 此函数和read_cookie一样也是在SAPI激活时调用,它与请求的方法相关,当请求的方法是POST时,程序会操作 POST HTTP_RAW_POST_DATA等变量。

  10. send_header 发送头部信息,此方法一般的SAPI都会定制,其所不同的是,有些的会调服务器自带的(如Apache),有些的需要你自己实现(如 FastCGI)。

以上的这些结构在各服务器的接口实现中都有定义。如Apache2的定义:

static sapi_module_struct apache2_sapi_module = {    "apache2handler",    "Apache 2.0 Handler",     php_apache2_startup,                /* startup */
    php_module_shutdown_wrapper,            /* shutdown */     ...}
目前PHP内置的很多SAPI实现都已不再维护或者变的有些非主流了,PHP社区目前正在考虑将一些SAPI移出代码库。 社区对很多功能的考虑是除非真的非常必要,或者某些功能已近非常通用了,否则就在PECL库中, 例如非常流行的APC缓存扩展将进入核心代码库中。

    整个SAPI类似于一个面向对象中的模板方法模式的应用。 SAPI.c和SAPI.h文件所包含的一些函数就是模板方法模式中的抽象模板,各个服务器对于sapi_module的定义及相关实现则是一个个具体的模板。

    这样的结构在PHP的源码中有多处使用,比如在PHP扩展开发中,每个扩展都需要定义一个zend_module_entry结构体。这个结构体的作用与sapi_module_struct结构体类似,都是一个类似模板方法模式的应用。在PHP的生命周期中如果需要调用某个扩展,其调用的方法都是zend_module_entry结构体中指定的方法,如在上一小节中提到的在执行各个扩展的请求初始化时,都是统一调用request_startup_func方法,而在每个扩展的定义时,都通过宏PHP_RINIT指定request_startup_func对应的函数。以VLD扩展为例:其请求初始化为PHP_RINIT(vld),与之对应在扩展中需要有这个函数的实现:
PHP_RINIT_FUNCTION(vld) {
}
所以, 我们在写扩展时也需要实现扩展的这些接口,同样,当实现各服务器接口时也需要实现其对应的SAPI。

然后我们说一下下面分布的三个部分,apache模块,嵌入式,和FastCGI

首先是Apache模块,apache的模块是怎么加载的呢?我们以之前提到的mod_php5模块为例子,我们首先需要更改apache的配置文件http.conf

LoadModule php5_module modules/mod_php5.so
    这里我们使用LoadModule命令,第一个参数是模块名称,第二个参数是模块路径。模块的名称我们可以在php的源码中,当需要调用服务的时候可以发送HUP或者AP_SIG_GRACEFUL给服务器,一旦接收到HUP或者AP_SIG_GRACEFUL信号,Apache将重新加载模块,而不是重新启动服务器。

在配置文件中添加上述命令以后,Apache会按照模块名来加载模块,Apache必须保证其文件名是以“mod_”开始的,如PHP的mod_php5.c。如果命名格式不对,Apache将认为此模块不合法。Apache的每一个模块都是以module结构体的形式存在。
在真正激活模块之前,Apache会检查所加载的模块是否为真正的Apache模块,这个检测是通过检查module结构体中的magic字段实现的。而magic字段是通过宏STANDARD20_MODULE_STUFF体现,在这个宏中magic的值为MODULE_MAGIC_COOKIE, MODULE_MAGIC_COOKIE定义如下:
#define MODULE_MAGIC_COOKIE 0x41503232UL /* "AP22" */
最后Apache会调用相关函数(ap_add_loaded_module)将模块激活,此处的激活就是将模块放入相应的链表中(ap_top_modules链表: ap_top_modules链表用来保存Apache中所有的被激活的模块,包括默认的激活模块和激活的第三方模块。)

Apache加载的是PHP模块,那么这个模块是如何实现的呢到我们以Apache2的mod_php5模块为例进行说明。

Apache2的mod_php5模块说明

AP_MODULE_DECLARE_DATA module php5_module = {
    STANDARD20_MODULE_STUFF,
        /* 宏,包括版本,小版本,模块索引,模块名,下一个模块指针等信息,其中模块名以__FILE__体现 
        */ create_php_config, /* create per-directory config structure */
    merge_php_config, /* merge per-directory config structures */
    NULL,  /* create per-server config structure */
    NULL,   /* merge per-server config structures */
    php_dir_cmds,  /* 模块定义的所有的指令 */
    php_ap2_register_hook
   /* 注册钩子,此函数通过ap_hoo_开头的函数在一次请求处理过程中对于指定的步骤注册钩子 */
};
const command_rec php_dir_cmds[] ={
    AP_INIT_TAKE2("php_value", php_apache_value_handler, NULL,        OR_OPTIONS, "PHP Value Modifier"),
    AP_INIT_TAKE2("php_flag", php_apache_flag_handler, NULL,        OR_OPTIONS, "PHP Flag Modifier"),
    AP_INIT_TAKE2("php_admin_value", php_apache_admin_value_handler,        NULL, ACCESS_CONF|RSRC_CONF, "PHP Value Modifier (Admin)"),
    AP_INIT_TAKE2("php_admin_flag", php_apache_admin_flag_handler,
        NULL, ACCESS_CONF|RSRC_CONF, "PHP Flag Modifier (Admin)"),
    AP_INIT_TAKE1("PHPINIDir", php_apache_phpini_set, NULL,        RSRC_CONF, "Directory containing the php.ini file"),    {NULL}};
这是mod_php5模块定义的指令表。它实际上是一个command_rec结构的数组。当Apache遇到指令的时候将逐一遍历各个模块中的指令表,查找是否有哪个模块能够处理该指令,如果找到,则调用相应的处理函数,如果所有指令表中的模块都不能处理该指令,那么将报错。如上可见,mod_php5模块仅提供php_value等5个指令。

php_ap2_register_hook函数的定义如下:

void php_ap2_register_hook(apr_pool_t *p){
    ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);}

        以上代码声明了pre_config,post_config,handler和child_init 4个挂钩以及对应的处理函数。其中pre_config,post_config,child_init是启动挂钩,它们在服务器启动时调用。

     handler挂钩是请求挂钩,它在服务器处理请求时调用。其中在post_config挂钩中启动php。它通过php_apache_server_startup函数实现。
php_apache_server_startup函数通过调用sapi_startup启动sapi,并通过调用php_apache2_startup来注册sapi module struct(此结构在本节开头中有说明),最后调用php_module_startup来初始化PHP, 其中又会初始化Zend引擎,以及填充zend_module_struct中的treat_data成员(通过php_startup_sapi_content_types)等。

到这里,我们知道了Apache加载mod_php5模块的整个过程,可是这个过程与我们的SAPI有什么关系呢? mod_php5也定义了属于Apache的sapi_module_struct结构:
static sapi_module_struct apache2_sapi_module = {
"apache2handler",
"Apache 2.0 Handler",

php_apache2_startup,                /* startup */
php_module_shutdown_wrapper,            /* shutdown */

NULL,                       /* activate */
NULL,                       /* deactivate */

php_apache_sapi_ub_write,           /* unbuffered write */
php_apache_sapi_flush,              /* flush */
php_apache_sapi_get_stat,           /* get uid */
php_apache_sapi_getenv,             /* getenv */

php_error,                  /* error handler */

php_apache_sapi_header_handler,         /* header handler */
php_apache_sapi_send_headers,           /* send headers handler */
NULL,                       /* send header handler */

php_apache_sapi_read_post,          /* read POST data */
php_apache_sapi_read_cookies,           /* read Cookies */

php_apache_sapi_register_variables,
php_apache_sapi_log_message,            /* Log message */
php_apache_sapi_get_request_time,       /* Request Time */
NULL,                       /* Child Terminate */

STANDARD_SAPI_MODULE_PROPERTIES
};
这些方法都专属于Apache服务器。以读取cookie为例,当我们在Apache服务器环境下,在PHP中调用读取Cookie时,最终获取的数据的位置是在激活SAPI时。它所调用的方法是read_cookies。
SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);
对于每一个服务器在加载时,我们都指定了sapi_module,而Apache的sapi_module是apache2_sapi_module。其中对应read_cookies方法的是php_apache_sapi_read_cookies函数。

又如flush函数,在ext/standard/basic_functions.c文件中,其实现为sapi_flush:
SAPI_API int sapi_flush(TSRMLS_D)
{
    if (sapi_module.flush) {
        sapi_module.flush(SG(server_context));
        return SUCCESS;
    } else {
        return FAILURE;
    }
}
    如果我们定义了此前服务器接口的flush函数,则直接调用flush对应的函数,返回成功,否则返回失败。对于我们当前的Apache模块,其实现为php_apache_sapi_flush函数,最终会调用Apache的ap_rflush,刷新apache的输出缓冲区。当然,flush的操作有时也不会生效,因为当PHP执行flush函数时,其所有的行为完全依赖于Apache的行为,而自身却做不了什么,比如启用了Apache的压缩功能,当没有达到预定的输出大小时,即使使用了flush函数,Apache也不会向客户端输出对应的内容。

Apache的运行过程:

Apache的运行分为启动阶段和运行阶段。在启动阶段,Apache为了获得系统资源最大的使用权限,将以特权用户root(*nix系统)或超级管理员Administrator(Windows系统)完成启动,并且整个过程处于一个单进程单线程的环境中。这个阶段包括配置文件解析(如http.conf文件)、模块加载(如mod_php,mod_perl)和系统资源初始化(例如日志文件、共享内存段、数据库连接等)等工作。

Apache的启动阶段执行了大量的初始化操作,并且将许多比较慢或者花费比较高的操作都集中在这个阶段完成,以减少了后面处理请求服务的压力。

在运行阶段,Apache主要工作是处理用户的服务请求。在这个阶段,Apache放弃特权用户级别,使用普通权限,这主要是基于安全性的考虑,防止由于代码的缺陷引起的安全漏洞。 Apache对HTTP的请求可以分为连接、处理和断开连接三个大的阶段。同时也可以分为11个小的阶段,依次为:

 Post-Read-Request,
 URI Translation,
 Header Parsing,
 Access Control,
 Authentication,
 Authorization,
 MIME Type Checking,
 FixUp,
 Response,
 Logging,
 CleanUp

Apache Hook机制

Apache 的Hook机制是指:Apache 允许模块(包括内部模块和外部模块,例如mod_php5.so,mod_perl.so等)将自定义的函数注入到请求处理循环中。换句话说,模块可以在Apache的任何一个处理阶段中挂接(Hook)上自己的处理函数,从而参与Apache的请求处理过程。
 mod_php5.so/ php5apache2.dll就是将所包含的自定义函数,通过Hook机制注入到Apache中,在Apache处理流程的各个阶段负责处理php请求。关于Hook机制在Windows系统开发也经常遇到,在Windows开发既有系统级的钩子,又有应用级的钩子。

以上介绍了apache的加载机制,hook机制,apache的运行过程以及php5模块的相关知识,下面简单的说明在查看源码中的一些常用对象。

Apache常用对象

在说到Apache的常用对象时,我们不得不先说下httpd.h文件。httpd.h文件包含了Apache的所有模块都需要的核心API。它定义了许多系统常量。但是更重要的是它包含了下面一些对象的定义。

request_rec对象 当一个客户端请求到达Apache时,就会创建一个request_rec对象,当Apache处理完一个请求后,与这个请求对应的request_rec对象也会随之被释放。 request_rec对象包括与一个HTTP请求相关的所有数据,并且还包含一些Apache自己要用到的状态和客户端的内部字段。

server_rec对象 server_rec定义了一个逻辑上的WEB服务器。如果有定义虚拟主机,每一个虚拟主机拥有自己的server_rec对象。 server_rec对象在Apache启动时创建,当整个httpd关闭时才会被释放。它包括服务器名称,连接信息,日志信息,针对服务器的配置,事务处理相关信息等 server_rec对象是继request_rec对象之后第二重要的对象。

conn_rec对象 conn_rec对象是TCP连接在Apache的内部实现。它在客户端连接到服务器时创建,在连接断开时释放。

你可能感兴趣的:(php,高级篇,源码,cgi,apache,php,服务器)