4.3.2 HTTP配置模型的内存布局

了解内存布局,会帮助理解使用create_main_conf、create_srv_conf、create_loc_conf等方法在内存中创建了多少个存放配置项的结构体,以及最终处理请求时,使用到的是哪个结构体。我们已经看到,http{}块下有1个ngx_http_conf_ctx_t结构,而每一个server{}块下也有1个ngx_http_conf_ctx_t结构,它们的关系如图4-2所示。

4.3.2 HTTP配置模型的内存布局 - 图1

图 4-2 http块与server块下的ngx_http_conf_ctx_t所指向的内存间的关系

图4-2描述了http块与某个server块下存储配置项参数的结构体间的关系。某个server块下ngx_http_conf_ctx_t结构中的main_conf数组将通过直接指向来复用所属的http块下的main_conf数组(其实是说server块下没有main级别配置,这是显然的)。

可以看到,ngx_http_conf_ctx_t结构中的main_conf、srv_conf、loc_conf数组保存了所有HTTP模块使用create_main_conf、create_srv_conf、create_loc_conf方法分配的结构体地址。每个HTTP模块都有自己的序号,如第1个HTTP模块就是ngx_http_core_module模块。当在http{……}内遍历到第2个HTTP模块时,这个HTTP模块已经使用create_main_conf、create_srv_conf、create_loc_conf方法在内存中创建了3个结构体,并把地址放到了ngx_http_conf_ctx_t内3个数组的第2个成员中。在解析server{……}块时遍历到第2个HTTP模块时,除了不调用create_main_conf方法外,其他完全与http{……}内的处理一致。

当解析到location{……}块时,也会生成1个ngx_http_conf_ctx_t结构,其中的3个指针数组与server{……}、http{……}块内ngx_http_conf_ctx_t结构的关系如图4-3所示.

4.3.2 HTTP配置模型的内存布局 - 图2

图 4-3 location块与http块、server块下分配的内存关系

从图4-3可以看出,在解析location{……}块时只会调用每个HTTP模块的create_loc_conf方法创建存储配置项参数的内存,ngx_http_conf_ctx_t结构的main_conf和srv_conf都直接引用其所属的server块下的ngx_http_conf_ctx_t结构。这也是显然的,因为location{……}块中当然没有main级别和srv级别的配置项,所以不需要调用各个HTTP模块的create_main_conf、create_srv_conf方法生成结构体存放main、srv配置项。

图4-2和图4-3说明了一个事实:在解析nginx.conf配置文件时,一旦解析到http{}块,将会调用所有HTTP模块的create_main_conf、create_srv_conf、create_loc_conf方法创建3组结构体,以便存放各个HTTP模块感兴趣的main级别配置项;在解析到任何一个server{}块时,又会调用所有HTTP模块的create_srv_conf、create_loc_conf方法创建两组结构体,以存放各个HTTP模块感兴趣的srv级别配置项;在解析到任何一个location{}块时,则会调用所有HTTP模块的create_loc_conf方法创建1组结构体,用于存放各个HTTP模块感兴趣的loc级别配置项。

这个事实告诉我们,在nginx.conf配置文件中http{}、server{}、location{}块的总个数有多少,我们开发的HTTP模块中create_loc_conf方法(如果实现的话)就会被调用多少次;http{}、server{}块的总个数有多少,create_srv_conf方法(如果实现的话)就会被调用多少次;由于只有一个http{},所以create_main_conf方法只会被调用一次。这3个方法每被调用一次,就会生成一个结构体,Nginx的HTTP框架居然创建了如此多的结构体来存放配置项,怎样理解呢?很简单,就是为了解决同名配置项的合并问题。

如果实现了create_main_conf方法,它所创建的结构体只会存放直接出现在http{}块下的配置项,那么create_main_conf只会被调用一次。

如果实现了create_srv_conf方法,那么它所创建的结构体既会存放直接出现在http{}块下的配置项,也会存放直接出现在server{}块下的配置项。为什么呢?这其实是HTTP框架的一种优秀设计。例如,虽然某个配置项是针对于server虚拟主机才生效的,但http{}下面可能有多个server{}块,对于用户来说,如果希望在http{}下面写入了这个配置项后对所有的server{}块都生效,这应当是允许的,因为它减少了用户的工作量。而对于HTTP框架而言,就需要在解析直属于http{}块内的配置项时,调用create_srv_conf方法产生一个结构体存放配置,解析到一个server{}块时再调用create_srv_conf方法产生一个结构体存放配置,最后通过把这两个结构体合并解决两个问题:有一个配置项在http{}块内出现了,在server{}块内却没有出现,这时以http块内的配置项为准;可如果这个配置项同时在http{}块、server{}块内出现了,它们的值又不一样,此时应当由对它感兴趣的HTTP模块来决定配置项以哪个为准。

如果实现了create_loc_conf方法,那么它所创建的结构体将会出现在http{}、server{}、location{}块中,理由同上。这是一种非常人性化的设计,充分考虑到nginx.conf文件中高级别的配置可以对所包含的低级别配置起作用,同时也给出了不同级别下同名配置项冲突时的解决方案(可以由HTTP模块自行决定其行为)。4.3.3节中将讨论HTTP框架如何合并可能出现的冲突配置项。在10.2节会详细讨论HTTP框架怎样管理HTTP模块产生的如此多的结构体,以及每个HTTP模块在处理请求时,HTTP框架又是怎样把正确的配置结构体告诉它的。