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

nginx的模块非常之多,可以认为所有代码都是以模块的形式组织,这包括核心模块和功能模块,针对不同的应用场合,并非所有的功能模块都要被用到,附录A给出的是默认configure(即简单的http服务器应用)下被连接的模块,这里虽说是模块连接,但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 : 处理客户端请求并产生响应内容,比如ngx_http_static_moudle模块,负责客户端的静态页面请求,并将对应的静态磁盘文件作为响应内容输出.

2)filters : 对handlers产生的响应内容做各种过滤处理(即增,删,改),比如 ngx_http_not_modify_filter_moudle,如果通过时间判断前后2次请求的响应内容没有发生任何改变,那么可以直接响应"304 Not Modified"状态标识,让客户端使用缓存即可,而原本发送的响应内容将被清除掉.

3)upstream : 如果存在后端真实的服务器,nginx 可以利用upstream模块充当反向代理的角色,对客户端的请求只负责转发到后端的真实服务器,如ngx_http_proxy_moudle模块.

4)load-balance : 在nginx充当中间代理时,由于后端真实服务器往往多于一个,对于某一次客户端的请求,如何选择对应的后端真实服务器来进行处理,这就有类似于ngx_http_upstream_ip_hash_module这样的模块来实现不同的负载均衡算法(Load Balance)。

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

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

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

需要C/C++ Linux服务器架构师学习资料加qun(563998835)获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

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

根据上面的代码,我们可以很明显看到各个回调函数的回调时机,例如:ngx_http_core_preconfiguration将在进行http块配置解析前被调用,所以内在ngx_http_block()函数里看到这样的代码:

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

handlers模块

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

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

并非某个阶段都能挂载自定义的回调函数,比如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为例:

在模块ngx_http_static_module的postconfiguration回调函数ngx_http_static_init()内,将ngx_http_static_module模块的核心功能函数ngx_http_static_handler()挂载在Http请求处理流程中的NGX_HTTP_CONTENT_PHASE阶段。这样,当一个客户端的http静态页面请求发送到nginx服务器,nginx就能够调用到我们这里注册的ngx_http_static_handler()函数,

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

回调函数会根据模块的不同而不同.这些回调函数的调用都时有条件的,调用后也要做一些根据返回值的结果处理.比如某次处理是否进入到阶段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; 

};

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

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

ngx_http_core_run_phases函数中r->phase_handler标志当前处理的序号,对于一个客户端的最开始的请求的时刻, 该值当然就是0了,while循环判断如果存在checker函数(末尾数组元素的checker函数为null),那就调用该checker函数并有可能调用相应的回调函数,以NGX_HTTP_ACCESS_PHASE阶段的ngx_http_core_access_phase()函数为例:

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

Filter模块:

对于http请求处理handlers产生的响应内容,在输出客户端之前需要做过滤处理,这些过滤处理对于完整功能的增强实现和性能的提升是非常有必要的,比如过滤模块ngx_http_chunked_filter_moudle,那么就无法完整支持http中chunk的功能。如果没有ngx_http_not_modified_filter_module过滤模块,那么就无法让客户端使用本地缓存来提高性能;诸如这些都需要过滤模块的支持。由于响应数据包括响应头和响应体,所以以此对应,任一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所列模块):

这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回调函数如下:

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

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

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

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

这两个函数非常简单,主要是通过过滤链的链头函数指针全局变量进入到两条过滤链内,进而依次执行链上的各个函数。比如这里ngx_http_top_header_filter指向的是ngx_http_not_modified_header_filter()函数,因此进入到该函数内执行,而在该函数的执行过程中又会根据情况,继续通过当前模块内的函数指针局部变量ngx_http_next_header_filter间接的调用到header过滤链的下一个过滤函数,这对保证过滤链的前后承接是非常必要的,除非我们遇到无法继续处理的错误,此时只有返回NGX_ERROR这样的值:

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

根据HTTP协议具备的响应头影响或决定响应体内容的特点,所以一般是先对响应头进行过滤,根据头过滤处理返回值再对响应体进行过滤处理,如果在响应头过滤处理中出错或某些特定情况下,响应体过滤处理可以不用再进行。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354