Nginx模块开发(二),阶段执行

我们在编写自己的http模块的时候都需要注册一个handler方法,该方法最终有http的核心模块进行回调,核心模块又会根据handler方法的返回值来确定如何向下执行。那么我们自己的handler的方法应该返回什么值,以及这些返回值都代表什么意思,通过下面的讲解我们可以对其有一个大概的了解。

Nginx在处理客户端请求的时候把处理过程分成了11个阶段,对于每个阶段如何执行,http核心模块为每个阶段都绑定了一个checker方法,这个方法负责检验并执行我们自定义的handler方法。

Nginx的十一个处理阶段
Nginx的处理阶段定义在src/http/ngx_http_core_module.h/ngx_http_phases中
 typedef enum {
	 //当解析完客户端请求头后,第一个介入的阶段
    NGX_HTTP_POST_READ_PHASE = 0,
	
 //server级别的uri重写阶段
    NGX_HTTP_SERVER_REWRITE_PHASE,
	
 //通过uri匹配location阶段(不可介入)
NGX_HTTP_FIND_CONFIG_PHASE,

//location级别的uri重写阶段
NGX_HTTP_REWRITE_PHASE,

//用来检查重写的次数(不可介入)
    NGX_HTTP_POST_REWRITE_PHASE,
	
 //访问权限检查阶段
    NGX_HTTP_PREACCESS_PHASE,
	
 //检验权限访问阶段
NGX_HTTP_ACCESS_PHASE,

//通过上一个阶段设置的r->access_code来确定该uri是否有权限继续向下执行(不可介入)
    NGX_HTTP_POST_ACCESS_PHASE,
    //专门为try_files指令提供的阶段(不可介入)
    NGX_HTTP_TRY_FILES_PHASE,
    //用来处理和输出内容的阶段
NGX_HTTP_CONTENT_PHASE,

	 //最后一个阶段,当请求处理完毕后要执行的阶段
    NGX_HTTP_LOG_PHASE
} ngx_http_phases;


当nginx解析完请求后首先会将r->write_event_handler设置为ngx_http_core_run_phases方法,该方法就是用来执行各个阶段绑定的handler方法的。该方法很简单,就是循环执行各个阶段绑定的checker方法。

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

    // handlers是一个数组,该数组按阶段顺序的保存了所有http模块的所有checker和handler方法
    // 其中cheker方法是http核心模块定义的方法,用来检查并调用各http模块自定义的handler方法
    ph = cmcf->phase_engine.handlers;

    // 循环调用当前请求上的所有阶段的handler回调方法
    // r->phase_handler代表当前请求应该调用那一个阶段的那个方法
    // r->phase_handler指定的就是ph中的数组下标
    while (ph[r->phase_handler].checker) { // 首先检查是否存在checker方法

    	 // 调用cheker方法,并将模块自定义的handler方法传入其中
        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

        // 成功则直接反回,这一步会将控制权限交给nginx事件框架
        if (rc == NGX_OK) {
            return;
        }
    }
}


通过上面的代码我们大概就可以知道,nginx中每个阶段可以完成的功能都是通过绑定在每个阶段的checker方法来约束的。实际上checker方法是通过我们自定义的handler方法的返回值来决定具体的行为。

在编写http模块的时候一般NGX_HTTP_CONTENT_PHASE阶段是每个http模块比较感兴趣的。该阶段主要用来向客户端输出响应内容,通过src/http/ngx_http.c/ngx_http_init_phase_handlers方法可以看到,该阶段注册的checker方法是ngx_http_core_content_phase,下面我们就看看该阶段的checker  方法都做了哪些事情。

实际上对于NGX_HTTP_CONTENT_PHASE阶段,有两种方式来注册回调hanlder, 一种是按需注册,另一种和其他阶段的注册方式一样。

通过下面的方式注册的handler就是按需注册:
 ngx_http_core_local_conf_t clcf;
 clcf = ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module)
 clcf->handler = ngx_http_my_handler; 

如果使用这种方式进行注册,我们需要把注册代码放到ngx_command_t结构体指定的解析指令的方法中。

这种方式的注册有一个特点,那就是他是局部的,只对某个location起作用,比比如我们有这样一个指令hello_string,该指令用来原样输出指令后面的参数,只有当前请求的uri在匹配的location下存在hello_string指令时,ngx_http_my_handler方法才会执行。

我们在src/http/ngx_http_core_module.c/ngx_http_core_content_phase方法中可以看到:
if (r->content_handler) {
      r->write_event_handler = ngx_http_request_empty_handler;
      ngx_http_finalize_request(r, r->content_handler(r));
      return NGX_OK;
}


r->content_handler就是我们前面通过按需注册的my_handler方法,但是这里有一个疑问,我们前面明明将my_handler方法注册到了clcf->handler中,为什么这里只是检查调用r->content_handler呢?这个问题的答案我们可以从/src/http/ngx_http_core_module.c/ngx_http_core_find_config_phase这个checker方法中找到,这个checker是被注册到NGX_HTTP_FIND_CONFIG_PHASE阶段的,该阶段又是负责匹配具体location的,当匹配到某个location后,该checker方法会调用ngx_http_update_location_config方法,这个方法最终会将clcf->handler赋值给r->content_handler。

通过上面的代码可以看到如果存在这种按需注册的handler,checker方法会直接返回NGX_OK,也就是说使用其他方式在该阶段注册的handler就不会再调用了。
最后调用ngx_http_finalize_request方法去结束请求,当然这时候是不是真的可以结束请求,需要根据my_handler的返回值,以及是否可以一次性将数据输出客户端,还有就是当前请求是否存在异步处理的事件等。

如果r->content_handler不存在,那么就是一般的注册方式了
 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);
 *h = ngx_http_my_handler;

这种注册方式是全局性质的,也就是说不管location里面是否存在于该handler相关的指令,只要执行到该阶段,该方法就会被调用。

看这种方式checker是如何执行回调handler的:
rc = ph->hanlder(r);

if (rc != NGX_DECLINED) {
	ngx_http_finalize_request(r,rc);
	return NGX_OK;
}


我们看到checker会直接执行回调handler,然后判断返回值是不是等于NGX_DECLIEND,如果不相等,比如我们返回的是NGX_OK,或者NGX_ERROR等。那么他都会直接调用ngx_http_finalize_request去结束请求,并且checker方法会直接返回NGX_OK,也就是说该阶段注册的其他handler方法将不会再被执行了。

从下面的代码我们可以看到,如果我们的handler返回的是NGX_DECLIEND值,那么就代表在该阶段绑定的后续其他handler方法可以被继续执行。

//ph加一代表后续的方法,有可能是当前模块在该阶段注册的其它方法
//也有可能是其他http模块在该阶段注册的方法(比如nginx自带的static模块)
ph++;
if (ph->chcker) {
	r->phase_handler++; 
   return NGX_AGAIN;
}



ngx_http_finalize_request方法
通过上面的讲解我们看到,checker方法最终都会调用该方法来结束请求。如果我们的响应数据无法一次性全部输出时,为了不阻塞进程该方法会把r->write_event_handler设置为ngx_http_writer方法,当可写事件到来后nginx的事件框架就会回调该方法进行输出数据。实际上ngx_http_finalize_request方法相当复杂,后学有时间我们在分析该方法。

你可能感兴趣的:(Nginx模块开发(二),阶段执行)