第10章 HTTP框架的初始化

从本章开始将探讨事件消费模块的“大户”——HTTP模块。Nginx作为Web服务器,其HTTP模块的数量远超过了其他4类模块(核心模块、事件模块、配置模块、邮件模块),其代码规模也同样遥遥领先。

这些实现了丰富多样功能的HTTP模块是以一种什么样的方式组织起来的呢?它们各自功能的高度可定制性是如何实现的?共性在哪里?Nginx又是怎样把这些共性的内容提取出来,并以一个强大的HTTP框架帮助各个HTTP模块实现具体的功能呢?

在回答这些问题前,先来回顾一下本书的第二部分,因为第二部分始终在讲如何开发一个HTTP模块,这种应用级别的HTTP模块就是由HTTP框架定义和管理的。HTTP框架大致由1个核心模块(ngx_http_module)、两个HTTP模块(ngx_http_core_module、ngx_http_upstream_module)组成,它将负责调度其他HTTP模块来一起处理用户请求。下面先来弄清楚普通的HTTP模块和HTTP框架各自的关注点在哪里。

先来看第3章~第5章例子中的HTTP模块通常会做哪些工作:

1)处理已经解析完毕的HTTP请求(也就是第二部分中反复提到的填充好的ngx_http_request_t结构体)。

2)获取到nginx.conf里自己感兴趣的配置项,无论它们是否同时出现在不同的http{}配置块、server{}配置块或者location{}配置块下,都需要正确地解析出,以此决定针对不同的用户请求定制不同的功能。

3)调用HTTP框架提供的方法就可以发送HTTP响应,包括使用磁盘I/O读取数据并发送。

4)将一个请求分为顺序性的多个处理阶段,前一个阶段的结果会影响后一个阶段的处理。例如,ngx_http_access_module模块根据IP信息拒绝一个用户请求后,本应接着执行的其他HTTP模块将没有机会再处理这个请求。

5)异步接收HTTP请求中的包体,可以将网络数据保存到磁盘上。

6)异步访问第三方服务。

7)分解出多个子请求来构造处理复杂业务的能力,子请求间的处理仍然是异步化、非阻塞的。

以上只是一个简单粗略的总结,HTTP模块或多或少都会需要这些功能。以这些功能为例,我们来探讨一下HTTP框架至少要完成哪些基础性的工作。

1)处理所有http{}块内的配置项,管理每个HTTP模块感兴趣的配置项(允许同一个http{}下出现多个server{}、location{}等子配置块,允许同名的配置项同时出现在各种配置块中)。

2)HTTP框架要能够使用第9章介绍的事件模块监听Web端口,并处理新连接事件、可读事件、可写事件等。

3)HTTP框架需要有状态机来分析接收到的TCP字符流是否是完整的HTTP包。

4)HTTP框架能够根据接收到的HTTP请求中的URI和HTTP头部,并以nginx.conf中server_name和location等配置项为依据,将请求按照其所在阶段准确地分发到某一个HTTP模块,从而调用它的回调方法来处理该请求。

5)向HTTP模块提供必要的工具方法,可以处理网络I/O(读取HTTP包体、发送HTTP响应)和磁盘I/O。

6)提供upstream机制帮助HTTP模块访问第三方服务。

7)提供subrequest机制帮助HTTP模块实现子请求。

HTTP框架需要做的工作很多,实际上,HTTP的框架性代码也是极为庞大的,为了简便起见,本书以后的章节将专注在HTTP框架的流程代码中,完全不会涉及具体的HTTP功能模块,也不会涉及框架中不太重要的工具性的代码。

本章会完整地介绍ngx_http_module模块,其中涉及少量ngx_http_core_module模块的功能。因为构成HTTP框架的几个模块间的代码耦合性很高,所以对于HTTP框架的介绍并不会按照模块进行,而是从HTTP框架的功能和架构上进行,其中本章介绍Nginx启动过程中HTTP框架是怎样初始化的,第11章介绍Nginx运行过程中HTTP框架是怎样调度HTTP模块处理请求的,第12章讲述访问第三方服务的upstream机制是如何工作的。

10.1 HTTP框架概述

为了让读者对HTTP框架所要完成的工作有一个直观的认识,本章将依托一个贯穿始终的nginx.conf配置范例来说明框架的行为,如下所示。


http{

mytest_num 1;

server{

server_name A;

listen 127.0.0.1:8000;

listen 80;

mytest_num 2;

location/L1{

mytest_num 3;

……

}

location/L2{

mytest_num 4;

……

}

}

server{

server_name B;

listen 80;

listen 8080;

listen 173.39.160.51:8000;

mytest_num 5;

location/L1{

mytest_num 6;

……

}

location/L3{

mytest_num 7;

……

}

}

}


从上面这个简单的例子中,可以获取下列信息:

❑HTTP框架是支持在http{}块内拥有多个server{}、location{}配置块的。

❑选择使用哪一个server虚拟主机块是取决于server_name的。

❑任意的server块内都可以用listen来监听端口,在不同的server块内允许监听相同的端口。

❑选择使用哪一个location块是将用户请求URI与合适的server块内的所有location表达式做匹配后决定的。

❑同一个配置项可以出现在任意的http{}、server{}、location{}等配置块中。

HTTP框架如何实现上述的配置项特性呢?

HTTP框架的首要任务是通过调用ngx_http_module_t接口中的方法来管理所有HTTP模块的配置项,10.2节中会详细描述这一过程。在10.3节中,我们会探讨监听端口与server虚拟主机间的关系,包括它们是用何种数据结构关联在一起的。所有的server虚拟主机会以散列表的数据结构组织起来,以达到高效查询的目的,在10.4节中会介绍这一过程。所有的location表达式会以一个静态的二叉查找树组织起来,以达到高效查询的目的,在10.5节中会说明它。对于每一个HTTP请求,都会以流水线形式划分为多个阶段,以供HTTP模块插入到HTTP框架中来共同处理请求,10.6节中会说明这些阶段划分、实现的依据所在。在10.7节中,将会完整地说明在Nginx启动过程中,HTTP框架是如何初始化的。

下面开始介绍ngx_http_module_t接口的相关内容。

ngx_http_module核心模块定义了新的模块类型NGX_HTTP_MODULE。这样的HTTP模块对于ctx上下文使用了不同于核心模块、事件模块的新接口ngx_http_module_t,虽然第3章中曾经提到过ngx_http_module_t接口的定义,但那时我们介绍的角度是如何开发一个HTTP模块,现在探讨实现HTTP框架时,对ngx_http_module_t接口的解读就不同了。在重新解读ngx_http_module_t接口之前,先对不同级别的HTTP配置项做个缩写名词的定义:

❑直接隶属于http{}块内的配置项称为main配置项。

❑直接隶属于server{}块内的配置项称为srv配置项。

❑直接隶属于location{}块内的配置项称为loc配置项。

其他配置块本章不会涉及,因为它们与HTTP框架没有任何关系。

对于每一个HTTP模块,都必须实现ngx_http_module_t接口。下面将从HTTP框架的角度来进行重新解读,如下所示。


typedef struct{

//在解析http{……}内的配置项前回调

ngx_int_t(preconfiguration)(ngx_conf_tcf);

//解析完http{……}内的所有配置项后回调

ngx_int_t(postconfiguration)(ngx_conf_tcf);

/创建用于存储HTTP全局配置项的结构体,该结构体中的成员将保存直属于http{}块的配置项参数。它会在解析main配置项前调用/

voidcreate_main_conf)(ngx_conf_t*cf);

//解析完main配置项后回调

charinit_main_conf)(ngx_conf_tcf,voidconf);

/创建用于存储可同时出现在main、srv级别配置项的结构体,该结构体中的成员与server配置是相关联的/

voidcreate_srv_conf)(ngx_conf_t*cf);

/create_srv_conf产生的结构体所要解析的配置项,可能同时出现在main、srv级别中,merge_srv_conf方法可以把出现在main级别中的配置项值合并到srv级别配置项中/

charmerge_srv_conf)(ngx_conf_tcf,voidprev,void*conf);

/创建用于存储可同时出现在main、srv、loc级别配置项的结构体,该结构体中的成员与location配置是相关联的/

voidcreate_loc_conf)(ngx_conf_t*cf);

/create_loc_conf产生的结构体所要解析的配置项,可能同时出现在main、srv、loc级别中,merge_loc_conf方法可以分别把出现在main、srv级别的配置项值合并到loc级别的配置项中/

charmerge_loc_conf)(ngx_conf_tcf,voidprev,void*conf);}ngx_http_module_t;


可以看到,ngx_http_module_t接口完全是围绕着配置项来进行的,这与第8章提到过的可定制性、可扩展性等架构特性是一致的。每一个HTTP模块都将根据main、srv、loc这些不同级别的配置项来决定自己的行为。