源码:nginx 1.12.0     
一、nginx http模块简介     
     由于nginx的性能优势,现在已经有越来越多的单位、个人采用nginx或者openresty、tengine等衍生版来作为WEB服务器、负载均衡服务器、安全网关来使用。在这些场景下,依赖的就是nginx的http模块,nginx的设计者采用模块化的设计思路,允许用户在http请求处理的各个阶段添加自己设计的模块来实现自己的一些逻辑,扩充一些功能。
 
二、http模块功能介绍
     http模块的丰富功能其实是由一个个http_module来共同实现的,每个module提供单独的功能方便开发、维护。nginx通过配置文件中的一个个配置项的设置来实现对相关模块功能的调用,如下示例:

http {
server {
location ~ \.php$ {
#proxy_pass命令在http_proxy_module中的被ngx_http_proxy_pass函数实现
proxy_pass http://127.0.0.1;
}
}
}
     http模块对一个完整http请求的处理流程如下:
     
上面的流程中,http_handler、output_filter这两步分别是执行phase_handler、filter两类http module的位置。在讲这两类模块之前,需要先理清这些模块是如何加载到添加到nginx中的。
     在nginx启动时,会将各个module加载到一个数组中,然后调用ngx_parse_conf寻找配置文件中特定类型的command,然后根据command找到包含该command的module,并执行command对应的函数(这些函数通常是将对应command的配置结构中设置一些变量或者将函数赋值给相关的handler函数指针,以便在处理对应command请求时可以直接调用)。
     在处理http这个command时,对应函数会初始化所有类型为HTTP_CORE_MODULE的模块,处理流程如下:
 
三、phase handlers请求处理阶段
     nginx对http的请求处理分成了POST_READ, SERVER_REWRITE, FIND_CONFIG, REWRITE, POST_REWRITE, PREACCESS, ACCESS, POST_ACCESS, TRY_FILES, CONTENT, LOG这11个阶段,除了FIND_CONFIG, POST_REWRITE, POST_ACCESS, TRY_FILES这4个阶段用户不能添加自定义的处理函数,其余每个阶段都通过ngx_http_init_phases函数初始化了一个数组用于保存本阶段的处理函数指针。如下:

//////   nginx/src/http/ngx_http.c ////////
static ngx_int_t
ngx_http_init_phases(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
//申请指针数组,用于存储该阶段的处理函数指针
if (ngx_array_init(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers,
cf->pool, 1, sizeof(ngx_http_handler_pt))
!= NGX_OK) {
return NGX_ERROR;
}
.....
return NGX_OK;
} ///// nginx/src/http/modules/ngx_http_rewrite_module.c /////
// 该函数是在rewrite的postconfiguration阶段被调用
static ngx_int_t
ngx_http_rewrite_init(ngx_conf_t *cf)
{
.....
//获取rewrite命令对应的core_module配置结构
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); //在&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers指向的数组中申请一个函数指针空间
h = ngx_array_push(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
//将函数添加到数组中新申请的元素中
*h = ngx_http_rewrite_handler;
 ......
return NGX_OK;
} ////// nginx/src/http/ngx_http.c ////////
//遍历各个PHASE,给每个PHASE添加相关的checker函数,并将各个phase阶段注册的函数按照phase+数组index的顺序统一添加到一个phase_engine中,每个phase handler函数都会由唯一的index标识,便于之后的遍历使用
static ngx_int_t
ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
h = cmcf->phases[i].handlers.elts;
switch (i) {
case NGX_HTTP_SERVER_REWRITE_PHASE:
if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) {
cmcf->phase_engine.server_rewrite_index = n;
}
//根据phase阶段确定每个阶段对应的checker
checker = ngx_http_core_rewrite_phase;
break;
.....
.....
default:
checker = ngx_http_core_generic_phase;
} n += cmcf->phases[i].handlers.nelts;
//遍历各个phase handlers数组中的注册函数并统一添加到cmcf->phase_engine.handlers中
for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--) {
ph->checker = checker;
ph->handler = h[j];
ph->next = n;
ph++;
}
}
return NGX_OK;
}
     
  在查看module源码的时候发现一些没有注册postconfiguration函数的模块,也就是说这些模块并没有通过ngx_array_push函数将handler添加到对应的phase中,例如http_memcached_module。但是这些模块通过command对应的函数将真正的handler赋值给了该command所在location对应的conf结构的handler指针,这些handler指针在ngx_http_update_location_config函数(该函数被ngx_http_core_find_config_phase调用)中被赋值给了r->content_handler,然后在http_core_content_phase函数中被执行了(但是也跳过了该phase中其他的handler)。由此可见,这些没有通过ngx_array_push显示的加入到某一phase的handler,都通过这种方式加入到了content_phase中。

////////  nginx/src/http/ngx_http_core_module.c  /////
void
ngx_http_update_location_config(ngx_http_request_t *r)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
....
//如果对应command存在直接的handler函数,就直接调用
if (clcf->handler) {
r->content_handler = clcf->handler;
}
} ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph)
{
....
if (r->content_handler) {
r->write_event_handler = ngx_http_request_empty_handler;
//如果存在的话就直接执行该handler函数并跳过content阶段的其他处理函数
ngx_http_finalize_request(r, r->content_handler(r));
return NGX_OK;
}
.....
} //调用各个所有phase中的函数完成对request的处理
void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
....
while (ph[r->phase_handler].checker) {
//根据请求中的phase索引确定执行的checker函数
//checker函数根据处理结果来决定是结束处理还是继续下一个phase handler
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
if (rc == NGX_OK) {
return;
}
}
}
   
   综合上述两种request处理module处理方法,如果需要在特定的PHASE阶段执行的话,就用ngx_array_push这种方式;r->content_handler这种方式只能在CONTENT PHASE阶段执行,且导致该阶段的其他handler无法执行,这需要开发者考虑。
 
四、filter输出处理模块
     filter输出处理主要是在输出的时候对输出内容进行处理,filter handler调用顺序与phase handler类型(根据phase阶段顺序)不同,filter类型的各个handler是通过链表的形式联系到一块的,只有链表头ngx_http_top_body_filter 函数指针是全局的。在函数ngx_http_output_filter中,有一个调用filter handler的入口,这个函数可以在正常的ngx_http_send_response函数中被调用,也可以在特定的phase handler中被调用。
     在各个module初始化的时候,会将ngx_http_top_body_filter指针的值保存到ngx_http_next_body_filter局部变量中,然后把当前filter的处理函数赋值给ngx_http_top_body_filter,同时每个filter的处理函数中都将ngx_http_next_body_filter的值赋值给ctx->output_filter,以便可以顺序遍历各个filter。
     对于ngx_http_write_filter_module、ngx_http_header_filter_module两个模块中没有ngx_http_next_body_filter变量,是因为ngx_http_write_filter_module是最后一个filter模块,因此不用next。ngx_http_header_filter_module虽然是倒数第二个模块,但是filter函数中调用了write_filter的函数,因此也没有使用next。

///// nginx/src/http/ngx_http_core_module.c ///////
ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
.....
//通过链表接口来一次遍历各个filter模块进行处理
rc = ngx_http_top_body_filter(r, in);
.....
return rc;
} ///// nginx/src/http/ngx_http_copy_filter_module.c //////////
//在postconfiguration阶段被调用,初始化filter handler链表
static ngx_int_t
ngx_http_copy_filter_init(ngx_conf_t *cf)
{
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_copy_filter; return NGX_OK;
} //将本阶段输出执行链表中下一个filter handler函数
static ngx_int_t
ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
.....
.....
ngx_http_set_ctx(r, ctx, ngx_http_copy_filter_module);
//记录下一个要执行的filter
ctx->output_filter = (ngx_output_chain_filter_pt) ngx_http_next_body_filter;
ctx->filter_ctx = r;
....
....
rc = ngx_output_chain(ctx, in);
.....
return rc;
}

nginx源码分析——http模块的更多相关文章

  1. nginx源码分析之模块初始化

    在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...

  2. nginx源码分析——event模块

    源码:nginx 1.12.0   一.简介      nginx是一款非常受欢迎的软件,具备高性能.模块化可定制的良好特性.之前写了一篇nginx的http模块分析的文章,主要对http处理模块进行 ...

  3. Nginx源码分析--epoll模块

    Nginx采用epoll模块实现高并发的网络编程,现在对Nginx的epoll模块进行分析. 定义在src/event/modules/ngx_epoll_module.c中 1. epoll_cre ...

  4. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  5. Nginx源码分析:3张图看懂启动及进程工作原理

    编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享.转载请注明来自高可用架构公众号「ArchNotes」.   导读:很多工程师及架构师都希望了解及掌握高性能服务器 ...

  6. nginx源码分析-源码结构

    本文主要简单介绍nginx源码目录结构.程序编译流程.如何构建学习nginx的环境等.本文以及后续nginx源码分析文章是基于nginx当前(2009-02-27)的稳定版本0.6.35进行的分析,该 ...

  7. nginx源码分析线程池详解

    nginx源码分析线程池详解 一.前言     nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,n ...

  8. nginx源码分析--使用GDB调试(strace、 pstack )

    nginx源码分析--使用GDB调试(strace.  pstack ) http://blog.csdn.net/scdxmoe/article/details/49070577

  9. nginx源码分析——configure脚本

    源码:nginx 1.13.0-release   一.前言      在分析源码时,经常可以看到类似 #if (NGX_PCRE) .... #endif 这样的代码段,这样的设计可以在不改动源码的 ...

随机推荐

  1. 深究WeixinJSBridge未定义之因

    对于一个没有使用微信JS-SDK的网站来说,我们竟然收到了WeixinJSBridge is not defined 的报错: 去网上一搜,发现很多开发者都遇到类似的问题: 我的微信项目,没有用到微信 ...

  2. 深度剖析Spark分布式执行原理

    让代码分布式运行是所有分布式计算框架需要解决的最基本的问题. Spark是大数据领域中相当火热的计算框架,在大数据分析领域有一统江湖的趋势,网上对于Spark源码分析的文章有很多,但是介绍Spark如 ...

  3. entity framework core 支持批量插入,值得期待

    entity framework6.x之前搞了这么多版本,构架这么牛B,居然没有批量插入更新的功能,但有很多替换的解决方案,例如Entity Framework Extended Library(ht ...

  4. android 透明状态栏方法及其适配键盘上推(一)

    android的状态栏(statusBar)版本的差异化比较大.在android 4.4 以上和5.x可以设置状态栏背景颜色,但是不可以设置状态栏中字和图标的颜色.而系统默认的statusbar的字体 ...

  5. JavaScript重新介绍

    本文转载自 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/A_re-introduction_to_JavaScript 引言 为什么 ...

  6. SolrCloud的介绍

    SolrCloud(solr云)是Solr提供的分布式搜索方案. 当你需要大规模,容错,分布式索引和检索能力时使用 SolrCloud. 当索引量很大,搜索请求并发很高时,同样需要使用SolrClou ...

  7. Chapter 4. Working with Key/Value Pairs

    Chapter4 working with key/value pairs key/values pairs键值对是Spark中非常常见的一种数据类型(type),RDD有时经常操作键值对数据类型.第 ...

  8. 【转】深入理解RunLoop

    RunLoop 是 iOS 和 OS X 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理.之后会介绍一下在 iOS 中,苹果是如何 ...

  9. jQuery 事件——关于select选中

    场景: eg:在管理一篇博文时,因博文的管理有一列叫:状态的列,该列有诸多状态,如:正常,待审核,删除等... 此时,若使用Select下拉列表进行状态选择,并在选中具体项值后,通过Ajax异步提交, ...

  10. Docker remote API简单配置使用

    1.启动docker remote API的方式如下: docker -d -H uninx:///var/run/docker.sock -H tcp://0.0.0.0:5678 2.但是为了伴随 ...