Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2316220
  • 博文数量: 252
  • 博客积分: 5472
  • 博客等级: 大校
  • 技术积分: 3107
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-17 18:39
文章分类

全部博文(252)

文章存档

2012年(96)

2011年(156)

分类: LINUX

2012-03-16 15:39:24

了解nginx的http core module 的结构和运行机制

(别人写的 真的很难懂)

HTTP相关的Module都在 src/http 目录和其子目录下, 其中 src/http 下的文件为http模块的核心文件, src/http/modules 下的文件为http模块的扩展模块。

其中:

ngx_http.[c|h]

ngx_http.c 中,注册了 http 这个指令的处理模块,对应ngx_http_block函数

  1. static ngx_command_t ngx_http_commands[] = {

  2.     { ngx_string("http"),
  3.       NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
  4.       ngx_http_block,
  5.       0,
  6.       0,
  7.       NULL },

  8.       ngx_null_command
  9. };

这个函数里面会进行一些conf资源分配/Merge,配置文件解析等工作。 这里面有个一比较重要的工作是注册了nginx http 的 phase handler


  1. if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
  2.         return NGX_CONF_ERROR;
  3.     }

phase handler的类型在 ngx_http_core_module 这里定义:

  1. typedef enum {
  2.     NGX_HTTP_POST_READ_PHASE = 0,

  3.     NGX_HTTP_SERVER_REWRITE_PHASE,

  4.     NGX_HTTP_FIND_CONFIG_PHASE,
  5.     NGX_HTTP_REWRITE_PHASE,
  6.     NGX_HTTP_POST_REWRITE_PHASE,

  7.     NGX_HTTP_PREACCESS_PHASE,

  8.     NGX_HTTP_ACCESS_PHASE,
  9.     NGX_HTTP_POST_ACCESS_PHASE,

  10.     NGX_HTTP_TRY_FILES_PHASE,
  11.     NGX_HTTP_CONTENT_PHASE,

  12.     NGX_HTTP_LOG_PHASE
  13. } ngx_http_phases;

每一个phase的handlers 都是一个数组,里面可以包含多个元素,通过 ngx_array_push 添加新的handler

其中每个phase的处理大都包含了对ngx_request_t 的 write 或者 read event的改写,其中

在 ngx_http_core_content_phase 里面, 有对location handler的调用, 其中的 r->content_handler 就是运行时刻从location handler中注册的,

  1. if (r->content_handler) {
  2.         r->write_event_handler = ngx_http_request_empty_handler;
  3.         ngx_http_finalize_request(r, r->content_handler(r)); /*实际的请求发送处理*/
  4.         return NGX_OK;
  5.     }

其中, 在各个phase的结束阶段,一般都是调用

 
  1. r->phase_handler++;
  2.     return NGX_AGAIN;

移动request 中 phase_handler的指针,并且示意主程序继续进行。

这里,无论是phase handler,还是 location handler,我们都是可以在程序里进行注册的。

另 外, ngx_http_block 里面调用了 ngx_http_optimize_servers ,这个函数对listening和connection相关的变量进行了初始化和调优,并最终在 ngx_http_add_listening (被ngx_http_add_listening调用) 中注册了listening 的 handler 为 ngx_http_init_connection


  1. ls->handler = ngx_http_init_connection;
ngx_http_init_connection 在 ngx_http_request.c中定义,后续会进行详细的介绍 ngx_http_request.[c|h]

这里面,ngx_http_init_connection 注册了connection事件的读操作的回叫函数, 并将写操作设置为空函数

  1. rev = c->read;
  2.     rev->handler = ngx_http_init_request;
  3.     c->write->handler = ngx_http_empty_handler;

当 新的连接进入的时候,就执行到 ngx_http_init_request, 开始对后面的流程进行处理,主要是将rev的handler 设置为ngx_http_process_request_line , 然后ngx_http_process_request_line 会先后有调度到 ngx_http_process_request_headers 和 ngx_http_process_request 函数对读取过来的event进行处理,其中, ngx_http_process_request_headers 里面会对http的请求头进行解析,ngx_http_process_request 设置event handler 到ngx_http_request_handler ,ngx_http_request_handler 中会根据事件可能是读取还是写入的操作分别调用 request 的 read_event_handler 和 write_event_handler ,所以后续程序对 request 的 [read/write]event_handler 调整 本质上类似对 rev 和 wev的handler的调整,只是回叫函数的参数变更为了 ngx_request_t 而不是之前的ngx_event_t


  1. c->read->handler = ngx_http_request_handler;
  2.     c->write->handler = ngx_http_request_handler;
  3.     r->read_event_handler = ngx_http_block_reading;

根据上面代码可以看出, 模块开始使用 ngx_http_block_reading 这个handler对后续的读请求进行处理

在注册完事件后, ngx_http_process_request 会分别调用下面的两个函数

  1. ngx_http_handler(r);
  2.     ngx_http_run_posted_requests(c);

其中, ngx_http_handler 在ngx_http_core_module中定义,处理程序的主请求, ngx_http_run_posted_requests 在ngx_http_request.c 里定义,处理所有提交的子请求数据的输出。

ngx_http_core_module.[c|h]

对于 ngx_http_core_module 是http 模块中比较重要的模块, 他本身是一个 NGX_HTTP_MODULE (不同于ngx_http_module, ngx_http_module本质上是一个 NGX_CORE_MODULE

这里面对http block下面的一些指令进行了处理, 比如 server, location 等, 同时, 上面提到的 ngx_http_handler 也在这里面

ngx_http_handler 所作的最核心的工作就是在最后调用 并将 write event 设置为 ngx_http_core_run_phases, 开始依次处理各个阶段的 handler

当handler处理完成后,http的处理流程也就基本上完成了..

  1. while (ph[r->phase_handler].checker) {

  2.         rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

  3.         if (rc == NGX_OK) {
  4.             return;
  5.         }
  6.     }

run_phases 的过程实际上非常简单, 一次的运行每一个handler, 当任意一个handler返回ok或者所有handler执行完成后,整个流程结束。

这 里需要注意的是, ph的下标变化是根据 r->phase_handler 变量决定的, 所以在每个handler内部,如果想要让主程序继续处理下一个 handler,需要手动的 r->phase_handler++ ,将phase handler数组的下标转移到下一个成员。

关于subrequest

在 ngx_http_core_module 里面,我们可以看到一些subrequest的函数,根据evan miller 的文章,我们知道 subrequest是在主请求的基础上发起的子请求,subrequest返回的内容会被附加到自请求上面,他的实现方法就是调用 ngx_http_subrequest 函数,subrequest函数的原形是

  1. ngx_int_t
  2. ngx_http_subrequest(ngx_http_request_t *r,
  3.     ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,
  4.     ngx_http_post_subrequest_t *ps, ngx_uint_t flags)

在里面,在这个函数里,他会1)创建新的request变量,根据 主request的值去填充这个变量2)注册新的变量的write event handler

  1. sr->read_event_handler = ngx_http_request_empty_handler;
  2. sr->write_event_handler = ngx_http_handler;

3) 并且把subrequet 的request 注册到主request的 posted_requests 变量里

  1. for (p = &r->main->posted_requests; *p; p = &(*p)->next) { /* void */ }

也就是说,一但调用了 ngx_http_subrequest 只后,subrequest已经已经注册到了nginx的事件循环中,和主循环并行进行处理,所以根据evan miller的文章里我们也可以看到,subrequest的串行处理是比较困难的。

关于 internal redirect

nginx 的 internal redirect 有两种方式, 一个是调用 ngx_http_internal_redirect 命令, 一个是使用 X-Accel-Redirect 头,其中X-Accel-Redirect 头是交由upstream模块进行的, 在ngx_http_upstream_process_headers函数中,我们可以看到

  1. if (u->headers_in.x_accel_redirect
  2.         && !(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT))
  3.    {
  4.         .....
  5.         .....
  6.         ngx_http_internal_redirect(r, uri, &args);
  7.         return NGX_DONE;
  8.     }

也就是说,本质上,这两种方法都是通过 ngx_http_internal_redirect 的函数实现的,而ngx_http_internal_redirect 函数又做了些什么呢?

  1. r->internal = 1;

  2.     ngx_http_handler(r);

  3.     return NGX_DONE;

可以看到,其实它只是简单的修改request结构体,并通过 ngx_http_handler 重新进行http处理流程而已。

关 于internal redirect 和 subrequest,他们有类似的地方,都是在最后调用 ngx_http_handler 重新开始对request的处理,但是不同的地方我想主要是 subrequest会新建立一个request结构,原来的request处理并不会结束,而 internal redirect会结束当前的请求处理,使用嗯但前的request结构体来发起请求。

关于upstream

upstream 是nginx里面非常重要的一种处理模式,nginx的很多模块都是使用他来完成的, upstream和普通的handler有很大的不同,比如nginx的location handler 和phase handler, 他们的处理模式非常类似apache的handler,就是获取后同步的进行处理,但是对于有些场合,比如proxy或者fastcgi, nginx进程需要和上游的服务(http,或者php-cgi)进行交互,并且将结果返回给客户端,在交互的过程中,由于后端服务的响应时间各异,如果 后端服务的响应时间过长,nginx等待后端服务响应的过程一之同步阻塞在这里是不现实的,所以nginx采用了异步的方案,每当上游服务器有响应的时候 才进行处理,并将处理结果返回给前端,这样nginx在处理上游请求的时候,可以同时注册多个io时间,使用epoll_wait(linux下默认) 也就是nginx的主事件循环进行处理,大大提高了nginx的并发能里,每个进程可以处理多个连接 ,不会因为一个上游连接阻塞整个nginx进程, 当然这也有依赖于你程序的实现方式。

关于upstream的使用方式, evan miller的文章里已经说的很清楚了,就是可以在location handler里(或者其它插入点中) 注册upstream的一些callback

  1. u->conf = &plcf->upstream;

  2. /* attach the callback functions */
  3.     u->create_request = ngx_http_proxy_create_request;
  4.     u->reinit_request = ngx_http_proxy_reinit_request;
  5.     u->process_header = ngx_http_proxy_process_status_line;
  6.     u->abort_request = ngx_http_proxy_abort_request;
  7.     u->finalize_request = ngx_http_proxy_finalize_request;

  8.     r->upstream = u;

并 且最后在调用 ngx_http_read_client_request_body 中传入 ngx_http_upstream_init 作为callback function 参数初始化 upstream流程, 在 ngx_http_upsteam_init 里面,会调用 ngx_http_upstream_connect建立连接,并注册读写的event handler

  1. /* event handler 最终会根据读/或者写的请求调用 write_event_handler和 read_event_handler ,也就是 ngx_http_upstream_send_request_handler和
  2.     ngx_http_upstream_process_header */
  3.     c->write->handler = ngx_http_upstream_handler;
  4.     c->read->handler = ngx_http_upstream_handler;

  5.     u->write_event_handler = ngx_http_upstream_send_request_handler;
  6.     u->read_event_handler = ngx_http_upstream_process_header;

这 里需要注意的是,在处理上游请求的时候,由于是异步的事件,所以每次上游服务器在调用callback的时候并不一定返回的是全部数 据,process_header 函数必须根据判断每次返回的数据包是否完成,如果没有处理完成则返回 NGX_AGAIN,来指示nginx需要重新进入process_header函数进行处理,并知道处理完成的时候返回NGX_OK 来指示nginx完成处理。 具体的例子可以参考 fastcgi 模块的ngx_http_fastcgi_process_header函数

另外, nginx可以针对上游服务的响应body进行处理, 通过注册 u->pipe->input_filter 这个回叫来实现,同样可以参考fastcgi模块,下面是例子

  1. /* 这里注册对于响应体的处理 */
  2.     u->pipe->input_filter = ngx_http_fastcgi_input_filter;
  3.     u->pipe->input_ctx = r;

通常在处理的过程中需要自己创建状态机来处理请求的不同状态,对于一些请求相关的信息,可以自己创建结构体,通过

  1. #define ngx_http_get_module_ctx(r, module) (r)->ctx[module.ctx_index]
  2. #define ngx_http_set_ctx(r, c, module) r->ctx[module.ctx_index] = c;
这两个宏进行读取和存储。
阅读(5148) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~