nginx源代码分析--nginx模块解析

时间:2022-04-28 12:06:21

nginx的模块很之多。能够觉得全部代码都是以模块的形式组织。这包含核心模块和功能模块,针对不同的应用场合。并不是全部的功能模块都要被用到,附录A给出的是默认configure(即简单的httpserver应用)下被连接的模块,这里虽说是模块连接。但nginx不会像apache或lighttpd那样在编译时生成so动态库而在程序运行时再进行动态载入。nginx模块源文件会在生成nginx时就直接被编译到其二进制运行文件里。所以假设要选用不同的功能模块。必须对nginx做又一次配置和编译。对于功能模块的选择。假设要改动默认值,须要在进行configure时进行指定,比方新增http_flv功能模块(默认是没有这个功能的,各个选项的默认值能够在文件auto/options内看到)

[root@localhost nginx-1.2.0]# ./configure --with-http_flv_module

运行后。生成的objs/ngx_modules.c文件内就包括有对ngx_http_flv_module模块的引用了,要再去掉http_flv功能模块。则须要又一次configure,即不带--with-http_flv_module配置后再编译生成新的nginx运行程序。通过运行./configure –help。我们能够看到很多其它的配置选项。

尽管nginx的模块的非常多。而且某个模块的功能各不同样,可是能够依据功能特性,我们大致能够分为四类:

1) handlers : 处理client请求并产生响应内容,比方ngx_http_static_moudle模块。负责client的静态页面请求。并将相应的静态磁盘文件作为响应内容输出.

2)filters : 对handlers产生的响应内容做各种过滤处理(即增。删,改),比方 ngx_http_not_modify_filter_moudle,假设通过时间推断前后2次请求的响应内容没有发生不论什么改变,那么能够直接响应"304 Not Modified"状态标识,让client使用缓存就可以,而原本发送的响应内容将被清除掉.

3)upstream : 假设存在后端真实的server,nginx 能够利用upstream模块充当反向代理的角色,对client的请求仅仅负责转发到后端的真实server,如ngx_http_proxy_moudle模块.

4)load-balance : 在nginx充其中间代理时,因为后端真实server往往多于一个,对于某一次client的请求,怎样选择相应的后端真实server来进行处理。这就有类似于ngx_http_upstream_ip_hash_module这种模块来实现不同的负载均衡算法(Load Balance)。

在此,我们先来了解一些数据结构:

nginx源代码分析--nginx模块解析
struct ngx_module_s {
ngx_uint_t ctx_index; //在同类模块中的序号
ngx_uint_t index; //在全部模块中序号 ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3; ngx_uint_t version; //当前模块的版本 void *ctx; //指向当前模块特有的数据
ngx_command_t *commands; //指向当前模块配置项解析数组
ngx_uint_t type; //模块的类型
//回调函数
ngx_int_t (*init_master)(ngx_log_t *log); ngx_int_t (*init_module)(ngx_cycle_t *cycle); ngx_int_t (*init_process)(ngx_cycle_t *cycle);
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t *cycle);
void (*exit_process)(ngx_cycle_t *cycle); void (*exit_master)(ngx_cycle_t *cycle);
//保留字
uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
};
nginx源代码分析--nginx模块解析

结构体ngx_module_s中值得注意的几个字段 ctx , commands, type,当中commands字段表示当前模块的能够解析的配置项目。表示模块类型的type值仅仅有5种,而同一类型的模块的ctx数据类型都是同样的。

序号

type值

ctx指向数据类型

1

NGX_CORE_MODULE

ngx_core_module_t

2

NGX_EVENT_MODULE

ngx_event_module_t

3

NGX_CONF_MODULE

NULL

4

NGX_HTTP_MODULE

ngx_http_module_t

5

NGX_MAIL_MODULE

ngx_mail_module_t

nginx源代码分析--nginx模块解析
typedef struct {
ngx_str_t name;
void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;
............................
typedef struct {
ngx_str_t *name; void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf); ngx_event_actions_t actions;
} ngx_event_module_t;
........................ typedef struct {
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
ngx_int_t (*postconfiguration)(ngx_conf_t *cf); void *(*create_main_conf)(ngx_conf_t *cf);
char *(*init_main_conf)(ngx_conf_t *cf, void *conf); void *(*create_srv_conf)(ngx_conf_t *cf);
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); void *(*create_loc_conf)(ngx_conf_t *cf);
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;
nginx源代码分析--nginx模块解析

上面表中的第3列的数据类型比較重要。它的字段基本上都是一些回调函数,这些回调函数会在其模块相应的配置文件解析过程 前/中/后 会适时被调用,做一些内存准备。初始化,配置值检查,配置值填充。合并,回调函数挂载等初始工作.

以下我们以ngx_http_core_moudle模块为例,type 为 NGX_HTTP_MOUDLE, ctx 指向ngx_http_moudle_t结构体变量ngx_http_core_module_ctx.

nginx源代码分析--nginx模块解析
static ngx_http_module_t  ngx_http_core_module_ctx = {
ngx_http_core_preconfiguration, /* preconfiguration */
NULL, /* postconfiguration */ ngx_http_core_create_main_conf, /* create main configuration */
ngx_http_core_init_main_conf, /* init main configuration */ ngx_http_core_create_srv_conf, /* create server configuration */
ngx_http_core_merge_srv_conf, /* merge server configuration */ ngx_http_core_create_loc_conf, /* create location configuration */
ngx_http_core_merge_loc_conf /* merge location configuration */
};
nginx源代码分析--nginx模块解析

依据上面的代码。我们能够非常明显看到各个回调函数的回调时机,比如:ngx_http_core_preconfiguration将在进行http块配置解析前被调用。所以内在ngx_http_block()函数里看到这种代码:

nginx源代码分析--nginx模块解析
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
} module = ngx_modules[m]->ctx; if (module->preconfiguration) {
if (module->preconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
}
nginx源代码分析--nginx模块解析

至于这些回调函数内的详细逻辑,如前所述通常是一些初始或默认值填充工作,但也有回调函数挂载的设置。比方ngx_http_static_module模块的postconfiguration字段回调函数ngx_http_static_init()就是将自己的处理函数ngx_http_static_handler()挂载在http处理状态机上,但整体来看这毕竟都仅仅是一些简单的初始准备工作.

handlers模块

对于clienthttp请求过程。为了获得更强的控制力,nginx将其细分成多个阶段处理,每一个阶段都有零个或多个回调函数专门处理,当我们编写handler模块的时候,必须把模块功能挂载在正确的阶段点。如前面所描写叙述的ngx_http_static_moudle 将自己的功能模块处理函数ngx_http_static_handler()挂载在NGX_HTTP_CONTENT_PHASE阶段.

Http请求处理过程一共分为11个阶段,每个阶段相应的处理功能都比較单一,这样能尽量让nginx模块代码更为内聚:

序号

阶段宏名

阶段描写叙述

0

NGX_HTTP_POST_READ_PHASE

读取请求内容阶段

1

NGX_HTTP_SERVER_REWRITE_PHASE

Server请求地址重写阶段

2

NGX_HTTP_FIND_CONFIG_PHASE

配置查找阶段

3

NGX_HTTP_REWRITE_PHASE

Location请求地址重写阶段

4

NGX_HTTP_POST_REWRITE_PHASE

请求地址重写提交阶段

5

NGX_HTTP_PREACCESS_PHASE

訪问权限检查准备阶段

6

NGX_HTTP_ACCESS_PHASE

訪问权限检查阶段

7

NGX_HTTP_POST_ACCESS_PHASE

訪问权限检查提交阶段

8

NGX_HTTP_TRY_FILES_PHASE

配置项try_files处理阶段

9

NGX_HTTP_CONTENT_PHASE

内容产生阶段

10

NGX_HTTP_LOG_PHASE

日志模块处理阶段

并不是某个阶段都能挂载自己定义的回调函数,比方NGX_HTTP_TRY_FILE_PHASE阶段就是针对配置项try_files的特定处理阶段段。NGX_HTTP_FIND_CONFIG_PHASE、NGX_HTTP_POST_ACCESS_PHASE与NGX_HTTP_POST_REWRITE_PHASE这三个阶段也是为了完毕nginx特定的功能,就算给这几个阶段加上回调函数。也永远不会被调用。我们的自己定义模块回调函数挂载在NGX_HTTP_CONTENT_PHASE阶段的情况比較多,毕竟大部分情况下的业务需求是改动HTTP响应数据。nginx自身的产生响应内容的模块。像ngx_http_static_module、ngx_http_random_index_module、ngx_http_index_module、ngx_http_gzip_static_module、ngx_http_dav_module等都是挂载在这个阶段。

大多数情况下,功能模块会在其相应配置解析完后的回调函数,也就是ngx_http_moudle_t结构体的postconfiguration字段指向的函数内将当前模块的回调功能函数挂载到这11个阶段当中一个上.

以ngx_http_static_module为例:

nginx源代码分析--nginx模块解析
ngx_http_module_t  ngx_http_static_module_ctx = {
NULL, /* preconfiguration */
ngx_http_static_init, /* postconfiguration */ NULL, /* create main configuration */
NULL, /* init main configuration */ NULL, /* create server configuration */
NULL, /* merge server configuration */ NULL, /* create location configuration */
NULL /* merge location configuration */
}; static ngx_int_t
ngx_http_static_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
} *h = ngx_http_static_handler; return NGX_OK;
nginx源代码分析--nginx模块解析

在模块ngx_http_static_module的postconfiguration回调函数ngx_http_static_init()内。将ngx_http_static_module模块的核心功能函数ngx_http_static_handler()挂载在Http请求处理流程中的NGX_HTTP_CONTENT_PHASE阶段。

这样。当一个client的http静态页面请求发送到nginxserver,nginx就行调用到我们这里注冊的ngx_http_static_handler()函数,

各个功能模块将其自身的功能函数挂载在cmcf->phases后,内部的情况例如以下图所看到的:

nginx源代码分析--nginx模块解析

回调函数会依据模块的不同而不同.这些回调函数的调用都时有条件的,调用后也要做一些依据返回值的结果处理.比方某次处理是否进入到阶段NGX_HTTP_CONTENT_PARSE的回调函数的处理,这须要一个事前推断.所以在函数ngx_http_init_phase_handlers()里对全部这个回调函数进行一次重组.

struct ngx_http_phase_handler_s {
ngx_http_phase_handler_pt checker; //阶段检查函数
ngx_http_handler_pt handler;
ngx_uint_t next;
};

nginx源代码分析--nginx模块解析

但从上图中能够看到,该函数仅仅把有回调函数的处理阶段给提取了出来,同一时候利用ngx_http_phase_handler_t结构体数组对这些回调函数进行重组,不仅加上了进入回调函数的条件推断checker函数,并且通过next字段的使用,把原本的二维数组实现转化为可直接在一维函数数组内部跳动;一般来讲,二维数组的遍历须要两层循环。而遍历一维函数数组就仅仅需一层循环。

再来看对http请求进行分段处理的核心函数ngx_http_core_run_phase:

nginx源代码分析--nginx模块解析
void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_http_phase_handler_t *ph;
ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); ph = cmcf->phase_engine.handlers; while (ph[r->phase_handler].checker) { rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]); if (rc == NGX_OK) {
return;
}
}
}
nginx源代码分析--nginx模块解析
ngx_http_core_run_phases函数中r->phase_handler标志当前处理的序号。对于一个client的最開始的请求的时刻。 该值当然就是0了,while循环推断假设存在checker函数(末尾数组元素的checker函数为null),那就调用该checker函数并有可能调用对应的回调函数,以NGX_HTTP_ACCESS_PHASE阶段的ngx_http_core_access_phase()函数为例:
nginx源代码分析--nginx模块解析
ngx_int_t
ngx_http_core_access_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
if (r != r->main) {
r->phase_handler = ph->next;
return NGX_AGAIN;
} rc = ph->handler(r); if (rc == NGX_DECLINED) {
r->phase_handler++;
return NGX_AGAIN;
} if (rc == NGX_AGAIN || rc == NGX_DONE) {
return NGX_OK;
}
ngx_http_finalize_request(r, rc);
return NGX_OK;
}
nginx源代码分析--nginx模块解析

能够看到,一个功能模块的handler函数能够返回多种类型的值,而且这些值有其固有的含义:

序号

返回值

含义

1

NGX_OK

当前阶段已经被成功处理,必须进入到下一个阶段

2

NGX_DECLINED

当前回调不处理当前情况,进入到下一个回调处理

3

NGX_AGAIN

当前处理所需资源不足,须要等待所依赖事件发生

4

NGX_DONE

当前处理结束,仍需等待进一步事件发生后做处理

5

NGX_ERROR, NGX_HTTP_…

当前回调处理错误发生,须要进入到异常处理流程


Filter模块:
对于http请求处理handlers产生的响应内容,在输出client之前须要做过滤处理,这些过滤处理对于完整功能的增强实现和性能的提升是很有必要的,比方过滤模块ngx_http_chunked_filter_moudle,那么就无法完整支持http中chunk的功能。 假设没有ngx_http_not_modified_filter_module过滤模块。那么就无法让client使用本地缓存来提高性能;诸如这些都须要过滤模块的支持。因为响应数据包含响应头和响应体,所以以此相应,任一filter模块必须提供处理响应头的header过滤函数(比方ngx_http_not_modified_filter_module模块提供的ngx_http_not_modified_header_filter()函数)或处理响应体的body过滤功能函数(比方ngx_http_copy_filter_module模块提供的ngx_http_copy_filter()函数)或两者皆有(比方ngx_http_chunked_filter_module模块提供的ngx_http_chunked_header_filter()函数和ngx_http_chunked_body_filter()函数)。

全部的header过滤功能函数和body过滤功能函数会分别组成各自的两条过滤链。例如以下图所看到的(使用附录A所列模块):

nginx源代码分析--nginx模块解析

这2条过滤链怎么形成的呢?在源文件ngx_http.c中,能够看到有2个函数指针变量:

ngx_int_t  (*ngx_http_top_header_filter) (ngx_http_request_t *r);

ngx_int_t  (*ngx_http_top_body_filter) (ngx_http_request_t *r, ngx_chain_t *ch);

这是整个nginx范围内可见的全局变量。然后在每个filter模块内,我们还会看到类似于这种定义(假设当前模块仅仅有header过滤功能函数或仅仅有body过滤功能函数。那么例如以下定义也就仅仅有对应的那个变量):

static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;

static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

注意到static修饰符,也就是说这两个变量是属于模块范围内可见的局部变量。

有了这些函数指针变量,再在各个filter模块的postconfiguration回调函数(该函数会在其相应配置解析完后被调用做一些设置工作,前面已经描写叙述过)内。全局变量与局部变量的巧妙赋值使得终于行成了两条过滤链。以header过滤链为例,通过附录A的模块列表ngx_modules变量。能够看到ngx_http_header_filter_module是具有header过滤功能函数的序号最小的过滤模块,其postconfiguration回调函数例如以下:

nginx源代码分析--nginx模块解析
618:    ngx_http_header_filter_init(ngx_conf_t *cf)
619: {
620: ngx_http_top_header_filter = ngx_http_header_filter;
621:
622: return NGX_OK;
623: }
nginx源代码分析--nginx模块解析
nginx源代码分析--nginx模块解析
232:    static ngx_int_t
233: ngx_http_chunked_filter_init(ngx_conf_t *cf)
234: {
235: ngx_http_next_header_filter = ngx_http_top_header_filter;
236: ngx_http_top_header_filter = ngx_http_chunked_header_filter;
}
nginx源代码分析--nginx模块解析

其他过滤模块的类此增加,逐步形成终于的完整header过滤链;当然。body过滤链的形成过程也与此类似。两条过滤链形成后。其相应的调用入口分别在函数ngx_http_send_header()和函数ngx_http_output_filter()内:

nginx源代码分析--nginx模块解析
1889:    ngx_int_t
1890: ngx_http_send_header(ngx_http_request_t *r)
1891: {
1892: …
1897: return ngx_http_top_header_filter(r);
1898: }
1899:
1901: ngx_int_t
1902: ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
1903: {
1904: …
1912: rc = ngx_http_top_body_filter(r, in);
1913: …
1919: return rc;
1920: }
nginx源代码分析--nginx模块解析

这两个函数很easy。主要是通过过滤链的链头函数指针全局变量进入到两条过滤链内,进而依次运行链上的各个函数。

比方这里ngx_http_top_header_filter指向的是ngx_http_not_modified_header_filter()函数,因此进入到该函数内运行。而在该函数的运行过程中又会依据情况。继续通过当前模块内的函数指针局部变量ngx_http_next_header_filter间接的调用到header过滤链的下一个过滤函数,这对保证过滤链的前后承接是很必要的。除非我们遇到无法继续处理的错误。此时仅仅有返回NGX_ERROR这种值:

nginx源代码分析--nginx模块解析
52:    static ngx_int_t
53: ngx_http_not_modified_header_filter(ngx_http_request_t *r)
54: {
55: …
70: return ngx_http_next_header_filter(r);
71: }
nginx源代码分析--nginx模块解析

依据HTTP协议具备的响应头影响或决定响应体内容的特点,所以通常是先对响应头进行过滤,依据头过滤处理返回值再对响应体进行过滤处理。假设在响应头过滤处理中出错或某些特定情况下,响应体过滤处理能够不用再进行。