标签归档:apache

Apache源码阅读笔记一:Content-MD5字段

Apache源码阅读笔记一:Content-MD5字段

通常在提供下载服务时,服务器都会预先提供一个MD5校验和,用户下载完文件以后,用MD5算法计算下载文件的MD5校验和,然后通过检查这两个校验和是否一致,就能判断下载的文件是否出错。而Content-MD5是HTTP协议中一个有类似功能的字段。

Content-MD5在RFC2616中的说明是用来提供实体主体(entity-body)的 MD5 摘要(digest),为的是提供 end-to-end 消息完整性检测(MIC,可以用来检测实体主体传输过程中的偶然性变动,但不一定能防范恶意攻击)。只有源服务器或客户端可生成 Content-MD5 头域;不得由代理和网关生成,否则会有悖于其作为端到端完整性检验的价值。

任何实体主体的接收者,包括代理和网关,都可以检查此头域里的摘要值与接收到的实体主体的摘要值是否相符。但是这个字段不能保证消息没有被篡改,所以不要将它作为一种安全手段,修改正文的人同样可以修改字段的内容。

在 Apache 中我们可以通过设置 ContentDigest On 打开 Content-MD5 的输出,详细说明猛击这里

那么,在 Apache 中是如何通过设置 ContentDigest 来开启 Content-MD5 字段的输出,此字段生成的算法是怎样的?

控制 Content-MD5 字段

前面我们有说过在配置文件中增加 ContentDigest On 可以打开 Apache 的 Content-MD5 的输出,在 Apache 内核中这个配置项是如何加载的?在生成内容时是根据哪些变量控制 Content-MD5 的输出?

我们知道 Apache 的模块中有一种叫预加载模块。这些模块是 Apache 运行非常重要的模块,我们今天所说的 Content-MD5 字段就包含这些预加载模块中的一个模块 core_module 中。虽然 Apache 针对不同的操作系统有不同的预加载模块列表,但是 core_module 都会作为第一个加载的模块放在列表的最前面。

Apache 在通过 ap_setup_prelinked_modules 加载完这些预加载的模块后,其运行的基本条件已经具备。在各种池初始化后, Apache 会进行配置文件解析,并针对配置文件中每一个有效项进行循环遍历,判断这些配置项与现有模块的指令(directive)是否匹配,如果匹配并且其参数设置为在读取配置时执行(EXEC_ON_READ,Content-MD5的此参数设置为OR_OPTIONS),则执行此字段的执行函数并将此项添加到配置项的指令集中。如果没有匹配,则直接将此节点添加到指令集中。

待所有的参数加载完后,Apache 内核会执行配置树(ap_process_config_tree)的所有执行函数。根据不同的指令参数,Apache 会调用每个指令的func(不同的参数使用不同的宏,虽然现在最终都是调用 func)。回到我们关注的内容,Content-MD5字段对应的是 set_content_md5 。此函数去掉验证输入,错误处理,就剩下一句:

 
 d->content_md5 = arg != 0;

这里的arg就是我们在配置文件中的 Off 和 On,Off的值为0, On的值为1。即当 ContentDigest On 时,d->content_md5的值为1。

生成Content-MD5字段的内容

前面有说提到 Content-MD5 字段最终是由 d->content_md5 控制。除此之外,此参数的输出还与输出过滤器相关,如果输出的过滤类型不是 AP_FTYPE_RESOURCE,则不会输出 Content-MD5 字段。

如果真输出 Content-MD5 字段,则 Apache 内核会调用 ap_md5digest(/server/util_md5.c文件) 函数。 Apache 实现的 MD5 算法与标准的算法步骤有一些出入。标准算法是按照如下5个步骤进行:

  1. Append Padding Bits: 信息计算前先要进行位补位
  2. Append Length
  3. Initialize MD Buffer: 用一个四个字的缓冲器(A,B,C,D)来计算报文摘要,A,B,C,D分别是32位的寄存器,初始化使用的是十六进制表示的数字。
  4. Process Message in 16-Word Blocks
  5. Output: 报文摘要的产生后的形式为:A,B,C,D。也就是低位字节A开始,高位字节D结束。

因为在大多数情况下我们都无法或很难提前计算出输入信息的长度。因此在具体实现时Append Padding Bits和Append Length这两步会放在后面,如下代码:

 
    AP_DECLARE(char *) ap_md5digest(apr_pool_t *p, apr_file_t *infile)
    {
        apr_md5_ctx_t context;
        unsigned char buf[4096]; /* keep this a multiple of 64 */
        apr_size_t nbytes;
        apr_off_t offset = 0L;
 
        apr_md5_init(&context);
        nbytes = sizeof(buf);
        while (apr_file_read(infile, buf, &nbytes) == APR_SUCCESS) {
            apr_md5_update(&context, buf, nbytes);
            nbytes = sizeof(buf);
        }
        apr_file_seek(infile, APR_SET, &offset);
        return ap_md5contextTo64(p, &context);
    }

apr_md5_init函数执行标准算法的第三步,初始化MD缓存,而标准算法的第一步、第二步和第四步都在 apr_md5_update 中体现。最后一步输出对应 ap_md5contextTo64 。

关于MD5算法的详细算法在后续的文章中介绍。

TIPI020201-PHP以模块方式注册到Apache

为了让Apache支持php,我们通常的做法是编译一个apche的php模块, 在配置中配置让mod_php来处理php文件的请求. php模块通过注册apache2的ap_hook_post_config挂钩, 在apache启动的时候启动php模块以接受php的请求.

下面介绍apache模块加载的基本知识以及PHP对于apache的实现

Apache模块加载机制简介


Apache的模块可以在运行的时候动态装载,这意味着对服务器可以进行功能扩展而不需要重新对源代码进行编译,甚至根本不需要停止服务器。 我们所需要做的仅仅是给服务器发送信号HUP或者AP_SIG_GRACEFUL通知服务器重新载入模块。 但是在动态加载之前,我们需要将模块编译成为动态链接库。此时的动态加载就是加载动态链接库。
Apache中对动态链接库的处理是通过模块mod_so来完成的,因此mod_so模块不能被动态加载, 它只能被静态编译进Apache的核心。这意味着它是随着Apache一起启动的。
比如我们要加载PHP模块,那么首先我们需要在httpd.conf文件中添加一行:

LoadModule php5_module modules/mod_php5.so

该命令的第一个参数是模块的名称,名称可以在模块实现的源码中找到。第二个选项是该模块所处的路径。 如果需要在服务器运行时加载模块,可以通过发送信号HUP或者AP_SIG_GRACEFUL给服务器,一旦接受到该信号,Apache将重新装载模块,而不需要重新启动服务器。

下面我们以PHP模块的加载为例,分析Apache的模块加载过程。在配置文件中添加了所上所示的指令后,Apache在加载模块时会根据模块名查找模块并加载, 对于每一个模块,Apache必须保证其文件名是以“mod_”开始的,如php的mod_php5.c。如果命名格式不对,Apache将认为此模块不合法。 module结构的name属性在最后是通过宏STANDARD20_MODULE_STUFF以__FILE__体现。 关于这点可以在后面介绍mod_php5模块时有看到。 通过之前指令中指定的路径找到相关的动态链接库文件,Apache通过内部的函数获取动态链接库中的内容,并将模块的内容加载到内存中的指定变量中。
在真正激活模块之前,Apache会检查所加载的模块是否为真正的Apache模块,这个检测是通过检查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中所有的被激活的模块,包括默认的激活模块和激活的第三方模块。)

Apache2的mod_php5模块说明


Apache2的mod_php5模块包括sapi/apache2handler和sapi/apache2filter两个目录 在apache2_handle/mod_php5.c文件中,模块定义的相关代码如下:

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_开头的函数在一次请求处理过程中对于指定的步骤注册钩子 */
};

它所对应的是apache的module结构,module的结构定义如下:

typedef struct module_struct module;
struct module_struct {
    int version;
    int minor_version;
    int module_index;
    const char *name;
    void *dynamic_load_handle;
    struct module_struct *next;
    unsigned long magic;
    void (*rewrite_args) (process_rec *process);
    void *(*create_dir_config) (apr_pool_t *p, char *dir);
    void *(*merge_dir_config) (apr_pool_t *p, void *base_conf, void *new_conf);
    void *(*create_server_config) (apr_pool_t *p, server_rec *s);
    void *(*merge_server_config) (apr_pool_t *p, void *base_conf, void *new_conf);
    const command_rec *cmds;
    void (*register_hooks) (apr_pool_t *p);
}

上面的模块结构与我们在mod_php5.c中所看到的结构有一点不同,这是由于STANDARD20_MODULE_STUFF的原因,这个宏它包含了前面8个字段的定义。 STANDARD20_MODULE_STUFF宏的定义如下:

/** Use this in all standard modules */
#define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, \
                MODULE_MAGIC_NUMBER_MINOR, \
                -1, \
                __FILE__, \
                NULL, \
                NULL, \
                MODULE_MAGIC_COOKIE, \
                                NULL      /* rewrite args spot */

php_dir_cmds所定义的内容如下:

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}
};

以上为php模块定义的指令表。它实际上是一个command_rec结构的数组。 当Apache遇到指令的时候将逐一遍历各个模块中的指令表,查找是否有哪个模块能够处理该指令, 如果找到,则调用相应的处理函数,如果所有指令表中的模块都不能处理该指令,那么将报错。 如上可见,php模块仅提供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的运行过程


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的内部体现。 它在客户端连接到服务器时创建,在连接断开时释放。

参考资料


《The Apache Modules Book–Application Development with Apache》

作者:TIPI团队

Windows下PHP环境配置的问题

         自从Windows7发布测试版后,一直作为小白鼠在用着,界面和用户体验都不错,但是一段时间后发现使用Xampp搭建的PHP环境运行起来暴慢,今天终于不能再忍受了,于是把Xampp给卸载了,使用手工安装的环境,但是“涛声依旧”。Google了一上,说是32位机与64位的问题,貌似系统是32的w7,奇怪了!

        最后不得已,重新安装了XP,手动安装PHP环境,但是在安装的过程中出现了如下几个问题:

        1、  关于短符号,即是否允许使用<? ?>,在现在使用的模块类中,生成的代码是以短符号包含PHP代码的,导致无法加载文件。

        2、  关于扩展地址,即extension_dir,默认情况下是”./”,这个是必须要改的。

        3、  php_mysql.dll扩展,在扩展地址和apache配置安装完成后,可以运行PHP了,但是发现在apache的错误日志显示PHP Warning:  PHP Startup: Unable to load dynamic library ‘D:/work/php/ext/php_mysql.dll,在phpinfo()显示的扩展中并没有mysql,google下发现是某些扩展需要一些在system32中添加动态链接库,mysql和mysqli在PHP >= 5.0.0 需要 libmysql.dll

        4、  与3类似,显示PHP Warning:  PHP Startup: Unable to load dynamic library ‘D:/work/php/ext/php_curl.dll’ 。curl函数库需要libeay32.dll,ssleay32.dll,解决方法:把php目录下的这两个文件拷贝到system32下即可。其它详细信息请移步Windows 下安装扩展库