决定自己的HTTP模块如何起作用

  • 一个HTTP请求会被许多个配置项控制,这是因为一个HTTP请求可以被许多个HTTP模块同时处理,所以肯定会有一个先后顺序的问题。
  • 我们面临的问题:
  • 我们希望自己的模块在哪个时刻开始处理请求?
  • 我们是希望自己的模块对到达Nginx的所有请求都起作用,还是只对某一类请求(如URI匹配了location后表达式的请求)起作用?
  • 定义第一个HTTP模块介入Nginx的方式
  • 模块并不对所有的HTTP请求起作用。
  • 在nginx.conf文件中的http{}、server{}或者location{}块内定义mytest配置项,如果一个用户请求通过主机域名、URI等匹配上了相应的配置块,而这个配置块下又具有mytest配置项,那么希望mytest模块开始处理请求。
  • 在这种介入方式下,模块处理请求的顺序是固定的,即必须在HTTP框架定义的NGX_HTTP_CONTENT_PHASE阶段开始处理请求。

开始定义mytest模块

  • 定义mytest配置项的处理
  • 只需要定义一个ngx_command_t数组,并将mytest配置项出现后的解析方法设置为ngx_http_mytest:
static ngx_command_t ngx_http_mytest_commands[] = {
{
    ngx_string("mytest"),
    NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
    ngx_http_mytest,
    NGX_HTTP_LOC_CONF_OFFSET,
    0,
    NULL
    },
    ngx_null_command
};
  • ngx_http_mytest是ngx_command_t结构体中的set成员,当在某个配置块中出现mytest配置项时,Nginx将会调用ngx_http_mytest方法。
  • 实现ngx_http_mytest方法
static char * ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
    ngx_http_core_loc_conf_t *clcf;

    /* 首先找到mytest配置项所属的配置块,clcf看上去像是location块内的数据结构,其实不然,
     * 它可以是main、src或者loc级别的配置项,也就是说,在每个http{}和server{}内也都有一个
     * ngx_http_core_loc_conf结构体
     */
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    /* HTTP框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段是,如果请求的主机域名、URL
     * 与mytest配置项所在的配置块相匹配,就将调用我们实现的ngx_http_mytest_handler方法处
     * 理这个请求
     * */
    clcf->handler = ngx_http_mytest_handler;

    return NGX_CONF_OK;
}
  • 当Nginx接收完HTTP请求的头部信息时,就会调用HTTP框架处理请求,NGX_HTTP_CONTENT_PHASE阶段将有可能调用mytest模块处理请求。
  • 在ngx_http_mytest方法中,定义了请求的处理方法为ngx_http_mytest_handler。
  • HTTP框架共定义了11个阶段,第三方模块只能介入其中的7个阶段处理请求。11个阶段的定义如下:
typedef enum {
	// 在接收到完整的HTTP头部后处理的HTTP阶段
    NGX_HTTP_POST_READ_PHASE = 0,

	/* 在还没有查询到URI匹配的location前,这时rewrite重写URL也作为一个独立的HTTP阶段 */
    NGX_HTTP_SERVER_REWRITE_PHASE,

	/* 
	根据URI寻找匹配的location,这个阶段通常由ngx_http_core_module模块实现。
	不建议其他HTTP模块重新定义这一阶段的行为。 
	*/
    NGX_HTTP_FIND_CONFIG_PHASE,
    /*
	NGX_HTTP_FIND_CONFIG_PHASE阶段后重写URL的意义与NGX_HTTP_SERVER_REWRITE_PHASE阶段不同
	因为这两者会导致查找到不同的location块(location是与URI进行匹配的)
	*/
    NGX_HTTP_REWRITE_PHASE,
    /*
	该阶段用于在rewrite重写URL后跳到NGX_HTTP_FIND_CONFIG_PHASE,找到与新的URI匹配的location。
	这一阶段无法由第三方HTTP模块处理,仅由ngx_http_core_module模块使用
	*/
    NGX_HTTP_POST_REWRITE_PHASE,
	// 处理NGX_HTTP_ACCESS_PHASE阶段前,HTTP模块可以介入的处理阶段
    NGX_HTTP_PREACCESS_PHASE,
	// 这个阶段用于HTTP模块判断是否允许这个请求访问Nginx服务器
    NGX_HTTP_ACCESS_PHASE,
    /*
    当NGX_HTTP_ACCESS_PHASE阶段中HTTP模块的handler处理方法返回不允许访问的错误码时
    (实际是NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),这个阶段负责构造拒绝服务的用户响应。
    这个阶段实际上用于给NGX_HTTP_ACCESS_PHASE阶段收尾。
    */
    NGX_HTTP_POST_ACCESS_PHASE,
	/*
	这个阶段完全是为try_files配置项而设立的。
	当HTTP请求访问静态文件资源时,try_files配置项可以使这个请求顺序地访问多个静态文件资源。
	如果某一次访问失败,则继续访问try_files中指定的下一个静态资源。
	这个功能完全是在NGX_HTTP_TRY_FILES_PHASE阶段中实现的。
	*/
    NGX_HTTP_PRECONTENT_PHASE,
	// 用于处理HTTP请求内容的阶段,这是大部分HTTP模块最喜欢介入的阶段
    NGX_HTTP_CONTENT_PHASE,
	/*
	处理完请求后记录日志的阶段。
	ngx_http_log_module模块就在这个阶段中加入了一个handler处理方法,
	使得每个HTTP请求处理完毕后会记录access_log日志
	*/
    NGX_HTTP_LOG_PHASE
} ngx_http_phases;
  • 定义ngx_http_module_t接口
  • 如果没有什么工作是必须在HTTP框架初始化时完成的,那就不必实现ngx_http_module_t的8个回调方法,定义方法如下:
static ngx_http_module_t ngx_http_mytest_module_ctx = {
    NULL,                       /* preconfiguration */
    NULL,                       /* 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 */
};
  • 定义mytest模块
ngx_module_t ngx_http_mytest_module = {
    NGX_MODULE_V1,
    &ngx_http_mytest_module_ctx,    /* module context */
    ngx_http_mytest_commands,       /* module directives */
    NGX_HTTP_MODULE,                /* module type */
    NULL,                           /* init master */
    NULL,                           /* init module */
    NULL,                           /* init process */
    NULL,                           /* init thread */
    NULL,                           /* exit thread */
    NULL,                           /* exit process */
    NULL,                           /* exit master */
    NGX_MODULE_V1_PADDING
};

至此,mytest模块在编译时将会被加入到ngx_modules全局数组中。Nginx在启动时,会调用所有模块的初始化回调方法,当然,这个例子中我们没有实现它们(也没有实现HTTP框架初始化时会调用的ngx_http_module_t中的8个方法)。