《深入理解Nginx:模块开发与架构解析》一3.5 定义自己的HTTP模块

3.5 定义自己的HTTP模块

上文中我们了解了定义HTTP模块时需要定义哪些成员以及实现哪些方法,但在定义HTTP模块前,首先需要确定自定义的模块应当在什么样的场景下开始处理用户请求,也就是说,先要弄清楚我们的模块是如何介入到Nginx处理用户请求的流程中的。从2.4节中的HTTP配置项意义可知,一个HTTP请求会被许多个配置项控制,实际上这是因为一个HTTP请求可以被许多个HTTP模块同时处理。这样一来,肯定会有一个先后问题,也就是说,谁先处理请求谁的“权力”就更大。例如,ngx_http_access_module模块的deny选项一旦得到满足后,Nginx就会决定拒绝来自某个IP的请求,后面的诸如root这种访问静态文件的处理方式是得不到执行的。另外,由于同一个配置项可以从属于许多个server、location配置块,那么这个配置项将会针对不同的请求起作用。因此,现在面临的问题是,我们希望自己的模块在哪个时刻开始处理请求?是希望自己的模块对到达Nginx的所有请求都起作用,还是希望只对某一类请求(如URI匹配了location后表达式的请求)起作用?
Nginx的HTTP框架定义了非常多的用法,我们有很大的自由来定义自己的模块如何介入HTTP请求的处理,但本章只想说明最简单、最常见的HTTP模块应当如何编写,因此,我们这样定义第一个HTTP模块介入Nginx的方式:
1)不希望模块对所有的HTTP请求起作用。
2)在nginx.conf文件中的http{}、server{}或者location{}块内定义mytest配置项,如果一个用户请求通过主机域名、URI等匹配上了相应的配置块,而这个配置块下又具有mytest配置项,那么希望mytest模块开始处理请求。
在这种介入方式下,模块处理请求的顺序是固定的,即必须在HTTP框架定义的NGX_HTTP_CONTENT_PHASE阶段开始处理请求,具体内容下文详述。
下面开始按照这种方式定义mytest模块。首先,定义mytest配置项的处理。从上文中关于ngx_command_t结构的说明来看,只需要定义一个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成员(完整定义为char (set)(ngx_conf_t cf, ngx_command_t cmd, void *conf);),当在某个配置块中出现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、srv或者loc级别配置项,也就是说,在每个http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体/

clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

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

clcf->handler = ngx_http_mytest_handler;

    return NGX_CONF_OK;
}

当Nginx接收完HTTP请求的头部信息时,就会调用HTTP框架处理请求,另外在11.6节描述的NGX_HTTP_CONTENT_PHASE阶段将有可能调用mytest模块处理请求。在ngx_http_mytest方法中,我们定义了请求的处理方法为ngx_http_mytest_handler,举个例子来说,如果用户的请求URI是/test/example,而在配置文件中有这样的location块:

Location /test {
    mytest;
}

那么,HTTP框架在NGX_HTTP_CONTENT_PHASE阶段就会调用到我们实现的ngx_http_mytest_handler方法来处理这个用户请求。事实上,HTTP框架共定义了11个阶段(第三方HTTP模块只能介入其中的7个阶段处理请求,详见10.6节),本章只关注NGX_HTTP_CONTENT_PHASE处理阶段,多数HTTP模块都在此阶段实现相关功能。下面简单说明一下这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_TRY_FILES_PHASE,
//用于处理HTTP请求内容的阶段,这是大部分HTTP模块最喜欢介入的阶段
  NGX_HTTP_CONTENT_PHASE,

/处理完请求后记录日志的阶段。例如,ngx_http_log_module模块就在这个阶段中加入了一个handler处理方法,使得每个HTTP请求处理完毕后会记录access_log日志/

NGX_HTTP_LOG_PHASE
} ngx_http_phases;

当然,用户可以在以上11个阶段中任意选择一个阶段让mytest模块介入,但这需要学习完第10章、第11章的内容,完全熟悉了HTTP框架的处理流程后才可以做到。
暂且不管如何实现处理请求的ngx_http_mytest_handler方法,如果没有什么工作是必须在HTTP框架初始化时完成的,那就不必实现ngx_http_module_t的8个回调方法,可以像下面这样定义ngx_http_module_t接口。

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个方法)。

时间: 2024-11-05 16:31:12

《深入理解Nginx:模块开发与架构解析》一3.5 定义自己的HTTP模块的相关文章

《深入理解Nginx:模块开发与架构解析》一1.3 准备工作

1.3 准备工作 由于Linux具有免费.使用广泛.商业支持越来越完善等特点,本书将主要针对Linux上运行的Nginx来进行介绍.需要说明的是,本书不是使用手册,而是介绍Nginx作为Web服务器的设计思想,以及如何更有效地使用Nginx达成目的,而这些内容在各操作系统上基本是相通的(除了第9章关于事件驱动方式以及第14章的进程间同步方式在类UNIX操作系统上略有不同以外). 1.3.1 Linux操作系统 首先我们需要一个内核为Linux 2.6及以上版本的操作系统,因为Linux 2.6及

《深入理解Nginx:模块开发与架构解析》一3.4 HTTP模块的数据结构

3.4 HTTP模块的数据结构 定义HTTP模块方式很简单,例如: ngx_module_t ngx_http_mytest_module; 其中,ngx_module_t 是一个Nginx模块的数据结构(详见8.2节).下面来分析一下Nginx模块中所有的成员,如下所示: typedef struct ngx_module_s ngx_module_t; struct ngx_module_s { /* 下面的ctx_index.index.spare0.spare1.spare2.spare

《深入理解Nginx:模块开发与架构解析》一3.1 如何调用HTTP模块

3.1 如何调用HTTP模块 在开发HTTP模块前,首先需要了解典型的HTTP模块是如何介入Nginx处理用户请求流程的.图3-1是一个简化的时序图,这里省略了许多异步调用,忽略了多个不同的HTTP处理阶段,仅标识了在一个典型请求的处理过程中主要模块被调用的流程,以此帮助读者理解HTTP模块如何处理用户请求.完整的流程将在第11章中详细介绍.从图3-1中看到,worker进程会在一个for循环语句里反复调用事件模块检测网络事件.当事件模块检测到某个客户端发起的TCP请求时(接收到SYN包),将会

《深入理解Nginx:模块开发与架构解析》一2.3 Nginx服务的基本配置

2.3 Nginx服务的基本配置 Nginx在运行时,至少必须加载几个核心模块和一个事件类模块.这些模块运行时所支持的配置项称为基本配置-所有其他模块执行时都依赖的配置项. 下面详述基本配置项的用法.由于配置项较多,所以把它们按照用户使用时的预期功能分成了以下4类: 用于调试.定位问题的配置项. 正常运行的必备配置项. 优化性能的配置项. 事件类配置项(有些事件类配置项归纳到优化性能类,这是因为它们虽然也属于events {}块,但作用是优化性能). 有这么一些配置项,即使没有显式地进行配置,它

《深入理解Nginx:模块开发与架构解析》一3.2 准备工作

3.2 准备工作 Nginx模块需要使用C(或者C++)语言编写代码来实现,每个模块都要有自己的名字.按照Nginx约定俗成的命名规则,我们把第一个HTTP模块命名为ngx_http_mytest_module.由于第一个模块非常简单,一个C源文件就可以完成,所以这里按照官方惯例,将唯一的源代码文件命名为ngx_http_mytest_module.c. 实际上,我们还需要定义一个名称,以便在编译前的configure命令执行时显示是否执行成功(即configure脚本执行时的ngx_addon

《深入理解Nginx:模块开发与架构解析》一3.3 如何将自己的HTTP模块编译进Nginx

3.3 如何将自己的HTTP模块编译进Nginx Nginx提供了一种简单的方式将第三方的模块编译到Nginx中.首先把源代码文件全部放到一个目录下,同时在该目录中编写一个文件用于通知Nginx如何编译本模块,这个文件名必须为config.它的格式将在3.3.1节中说明.这样,只要在configure脚本执行时加入参数--add-module=PATH(PATH就是上面我们给定的源代码.config文件的保存目录),就可以在执行正常编译安装流程时完成Nginx编译工作.有时,Nginx提供的这种

《深入理解Nginx:模块开发与架构解析》一2.2 Nginx配置的通用语法

2.2 Nginx配置的通用语法 Nginx的配置文件其实是一个普通的文本文件.下面来看一个简单的例子. user nobody; worker_processes 8; error_log /var/log/nginx/error.log error; #pid logs/nginx.pid; events { use epoll; worker_connections 50000; } http { include mime.types; default_type application/o

《深入理解Nginx:模块开发与架构解析》一1.2 为什么选择Nginx

1.2 为什么选择Nginx 为什么选择Nginx?因为它具有以下特点:(1)更快这表现在两个方面:一方面,在正常情况下,单次请求会得到更快的响应:另一方面,在高峰期(如有数以万计的并发请求),Nginx可以比其他Web服务器更快地响应请求.实际上,本书第三部分中大量的篇幅都是在说明Nginx是如何做到这两点的.(2)高扩展性Nginx的设计极具扩展性,它完全是由多个不同功能.不同层次.不同类型且耦合度极低的模块组成.因此,当对某一个模块修复Bug或进行升级时,可以专注于模块自身,无须在意其他.

《深入理解Nginx:模块开发与架构解析》一2.4 用HTTP核心模块配置一个静态Web服务器

2.4 用HTTP核心模块配置一个静态Web服务器 静态Web服务器的主要功能由ngx_http_core_module模块(HTTP框架的主要成员)实现,当然,一个完整的静态Web服务器还有许多功能是由其他的HTTP模块实现的.本节主要讨论如何配置一个包含基本功能的静态Web服务器,文中会完整地说明ngx_http_core_module模块提供的配置项及变量的用法,但不会过多说明其他HTTP模块的配置项.在阅读完本节内容后,读者应当可以通过简单的查询相关模块(如ngx_http_gzip_f