Nginx 模块概述

Nginx 模块有三种角色:

  • 处理请求并产生输出的 Handler 模块;
  • 处理由 Handler 产生的输出的 Filter(滤波器)模块;
  • 当出现多个后台服务器时,Load-balancer (负载均衡器)模块负责选择其中一个后台服务器发送请求;
通常,服务器启动时,任何 Handler 模块都有可能去处理配置文件中的 location 定义。若出现多个Handler 模块被配置成需要处理某一特定的 location 时,最终只有其中一个Handler 模块是成功的。Handler 模块有三种返回方式:
  1. 接收请求,并成功返回;
  2. 接收请求,但是出错返回;
  3. 拒绝请求,使默认的 Handler 模块处理该请求;

若 Handler 模块的作用是把一个请求反向代理到后台服务器,则会出现另一种类型的空间模块—— Load-balancer。 Load-balancer 负责决定将请求发送给哪个后端服务器。Nginx 目前支持两种 Load-balancer 模块:round-robin (轮询,处理请求就像打扑克时发牌那样)和"IP hash" method(众多请求时,保证来自同一 IP 的请求被分发的同一个后端服务器)。

若 Handler 模块没有产生错误返回时,则会调用 Filter 模块。每个location 配置里都可以添加多个Filter 模块 ,因此响应可以被压缩和分块。Filter 模块之间的处理顺序是在编译时就已经确定的。Filter 模块采用“CHAIN OF RESPONSIBILITY”链式的设计模式。当有请求到达时,请求依次经过这条链上的全部 Filter 模块,一个Filter 被调用并处理,接下来调用下一个Filter,直到最后一个Filter 被调用完成,Nginx 才真正完成响应流程。
总结如下,典型的处理形式如下:
[cpp] view plaincopy
  1. Client sends HTTP request → Nginx chooses the appropriate handler based on the location config →
  2. (if applicable) load-balancer picks a backend server →
  3. Handler does its thing and passes each output buffer to the first filter →
  4. First filter passes the output to the second filter → second to third → third to fourth → etc.
  5. → Final response sent to client

Nginx 模块的结构

模块的配置结构

        模块最多可以定义三个配置结构:main、server、location。绝大多数模块仅需要一个location 配置。名称约定如下以ngx_http_<module name>_(main|srv|loc)_conf_t为例的dav module:
[cpp] view plaincopy
  1. typedef struct {
  2. ngx_uint_t methods;
  3. ngx_flag_t create_full_put_path;
  4. ngx_uint_t access;
  5. } ngx_http_dav_loc_conf_t;

Nginx 模块的数据结构如下定义:

[cpp] view plaincopy
  1. /* Nginx 模块的数据结构 */
  2. #define NGX_MODULE_V1          0, 0, 0, 0, 0, 0, 1
  3. #define NGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0, 0, 0
  4. struct ngx_module_s {
  5. /* 模块类别由type成员决定,ctx_index表示当前模块在type类模块中的序号 */
  6. ngx_uint_t            ctx_index;
  7. /* index 区别与ctx_index,index表示当前模块在所有模块中的序号 */
  8. ngx_uint_t            index;
  9. /* spare 序列保留变量,暂时不被使用 */
  10. ngx_uint_t            spare0;
  11. ngx_uint_t            spare1;
  12. ngx_uint_t            spare2;
  13. ngx_uint_t            spare3;
  14. /* 当前模块的版本 */
  15. ngx_uint_t            version;
  16. /* ctx指向特定类型模块的公共接口,例如在HTTP模块中,ctx指向ngx_http_module_t结构体 */
  17. void                 *ctx;
  18. /* 处理nginx.conf中的配置项 */
  19. ngx_command_t        *commands;
  20. /* type表示当前模块的类型 */
  21. ngx_uint_t            type;
  22. /* 下面的7个函数指针是在Nginx启动或停止时,分别调用的7中方法 */
  23. /* 在master进程中回调init_master */
  24. ngx_int_t           (*init_master)(ngx_log_t *log);
  25. /* 初始化所有模块时回调init_module */
  26. ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
  27. /* 在worker进程提供正常服务之前回调init_process初始化进程 */
  28. ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
  29. /* 初始化多线程 */
  30. ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
  31. /* 退出多线程 */
  32. void                (*exit_thread)(ngx_cycle_t *cycle);
  33. /* 在worker进程停止服务之前回调exit_process */
  34. void                (*exit_process)(ngx_cycle_t *cycle);
  35. /* 在master进程退出之前回调exit_master */
  36. void                (*exit_master)(ngx_cycle_t *cycle);
  37. /* 保留字段,未被使用 */
  38. uintptr_t             spare_hook0;
  39. uintptr_t             spare_hook1;
  40. uintptr_t             spare_hook2;
  41. uintptr_t             spare_hook3;
  42. uintptr_t             spare_hook4;
  43. uintptr_t             spare_hook5;
  44. uintptr_t             spare_hook6;
  45. uintptr_t             spare_hook7;
  46. };

在该数据结构中,其中最重要的是两个成员 ctx和commands,这里两个成员会在分别在下面的模块配置指令和模块上下文中讲解;若是HTTP 模块时,type 字段必须定义为NGX_HTTP_MODULE;

模块配置指令

模块指令存储在一个 ngx_command_t 类型的静态数组结构中,例如:
[cpp] view plaincopy
  1. static ngx_command_t  ngx_http_circle_gif_commands[] = {
  2. { ngx_string("circle_gif"),
  3. NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
  4. ngx_http_circle_gif,
  5. NGX_HTTP_LOC_CONF_OFFSET,
  6. 0,
  7. NULL },
  8. { ngx_string("circle_gif_min_radius"),
  9. NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  10. ngx_conf_set_num_slot,
  11. NGX_HTTP_LOC_CONF_OFFSET,
  12. offsetof(ngx_http_circle_gif_loc_conf_t, min_radius),
  13. NULL },
  14. ...
  15. ngx_null_command
  16. };

ngx_command_t 类型定义在 core/ngx_conf_file.h:

[cpp] view plaincopy
  1. struct ngx_command_s {
  2. /* 配置项名称 */
  3. ngx_str_t             name;
  4. /* 配置项类型,type将指定配置项可以出现的位置以及携带参数的个数 */
  5. ngx_uint_t            type;
  6. /* 处理配置项的参数 */
  7. char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
  8. /* 在配置文件中的偏移量,conf与offset配合使用 */
  9. ngx_uint_t            conf;
  10. ngx_uint_t            offset;
  11. /* 配置项读取后的处理方法,必须指向ngx_conf_post_t 结构 */
  12. void                 *post;
  13. };

name :配置指令的名称;type    :该配置的类型,指定配置项的出现位置以及可携带参数的个数,下面规定只是其中一部分,更多信息可查看文件core/ngx_conf_file.h:

[plain] view plaincopy
  1. NGX_HTTP_MAIN_CONF:  directive is valid in the main config
  2. NGX_HTTP_SRV_CONF:   directive is valid in the server (host) config
  3. NGX_HTTP_LOC_CONF:   directive is valid in a location config
  4. NGX_HTTP_UPS_CONF:   directive is valid in an upstream config
  5. NGX_CONF_NOARGS:     directive can take 0 arguments
  6. NGX_CONF_TAKE1:      directive can take exactly 1 argument
  7. NGX_CONF_TAKE2:      directive can take exactly 2 arguments
  8. NGX_CONF_TAKE7:      directive can take exactly 7 arguments
  9. NGX_CONF_FLAG:       directive takes a boolean ("on" or "off")
  10. NGX_CONF_1MORE:      directive must be passed at least one argument
  11. NGX_CONF_2MORE:      directive must be passed at least two arguments

set     :这是一个函数指针,当Nginx 在解析配置时,若遇到该配置指令,将会把读取到的值传递给这个函数进行分解处理。因为具体每个配置指令的值如何处理,只有定义这个配置指令的人是最清楚的。来看一下这个函数指针要求的函数原型。

[cpp] view plaincopy
  1. char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

该函数处理成功时,返回 NGX_OK,否则返回 NGX_CONF_ERROR 或者是一个自定义的错误信息的字符串。该函数传入三个类型的参数:

  1. cf    :指向ngx_conf_t  结构的指针,该结构包括从配置指令传递的参数;
  2. cmd:指向当前ngx_command_t 结构;
  3. conf:指向模块配置结构;
为了方便实现对配置指令参数的读取,Nginx 已经默认提供了对一些标准类型的参数进行读取的函数,可以直接赋值给set 字段使用。下面是一部分已经实现的set 类型函数,更多可参考文件core/ngx_conf_file.h:
  • ngx_conf_set_flag_slot : 把 "on" 或 "off" 解析为 1 或 0;
  • ngx_conf_set_str_slot   : 解析字符串并保存 ngx_str_t 类型;
  • ngx_conf_set_num_slot: 解析一个数字并将其保存为int 类型;
  • ngx_conf_set_size_slot: 解析数据大小 ("8k", "1m", etc.) 并将其保存为size_t;
conf   :用于指示配置项所处内存的相对偏移量,仅在type 中没有设置NGX_DIRECT_CONF 和NGX_MAIN_CONF 时才生效。对于HTTP 模块,conf 必须设置,它的取值如下:
  • NGX_HTTP_MAIN_CONF_OFFSET:使用create_main_conf 方法产生的结构体来存储解析出的配置项参数;
  • NGX_HTTP_SRV_CONF_OFFSET:使用 create_srv_conf 方法产生的结构体来存储解析出的配置项参数;
  • NGX_HTTP_LOC_CONF_OFFSET:使用 create_loc_conf 方法产生的结构体来存储解析出的配置项参数;
offset :表示当前配置项在整个存储配置项的结构体中的偏移位置。

模块上下文

这是一个静态的 ngx_http_module_t 结构,它的名称是ngx_http_<module name>_module_ctx。以下是该结构的定义,具体可查阅文件 http/ngx_http_config.h:
  • preconfiguration
  • postconfiguration
  • creating the main conf (i.e., do a malloc and set defaults)
  • initializing the main conf (i.e., override the defaults with what's in nginx.conf)
  • creating the server conf
  • merging it with the main conf
  • creating the location conf
  • merging it with the server conf
[cpp] view plaincopy
  1. typedef struct{/* 可以把不需要调用的函数指针设置为 NULL */
  2. /* 解析配置文件之前被调用 */
  3. ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
  4. /* 完成配置文件的解析后被调用 */
  5. ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
  6. /* 创建存储main级别的全局配置项的结构体(直属于http块) */
  7. void        *(*create_main_conf)(ngx_conf_t *cf);
  8. /* 初始化main级别的配置项 */
  9. char        *(*init_main_conf)(ngx_conf_t *cf);
  10. /* 创建存储srv级别的配置项的结构体(直属于server块) */
  11. void        *(*create_srv_conf)(ngx_conf_t *cf);
  12. /* 合并main级别与srv级别下的同名配置项 */
  13. char        *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
  14. /* 创建存储loc级别的配置项的结构体(直属于location块) */
  15. void        *(*create_loc_conf)(ngx_conf_t *cf);
  16. /* 合并srv级别与loc级别下的同名配置项 */
  17. char        *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
  18. }ngx_http_module_t;
在以上的结构内容中,大多数模块只使用最后两项:ngx_http_<module name>_create_loc_conf和ngx_http_<module name >_merge_loc_conf;例如:
[cpp] view plaincopy
  1. static ngx_http_module_t  ngx_http_circle_gif_module_ctx = {
  2. NULL,                          /* preconfiguration */
  3. NULL,                          /* postconfiguration */
  4. NULL,                          /* create main configuration */
  5. NULL,                          /* init main configuration */
  6. NULL,                          /* create server configuration */
  7. NULL,                          /* merge server configuration */
  8. ngx_http_circle_gif_create_loc_conf,  /* create location configuration */
  9. ngx_http_circle_gif_merge_loc_conf /* merge location configuration */
  10. };

下面针对最后两项进行说明,以下是以 circle_gif 模块为例子,该模块源码;

create_loc_conf 函数

该函数是传入一个 ngx_conf_t 结构的参数,返回新创建模块的配置结构,在这里是返回:ngx_http_circle_gif_loc_conf_t
[cpp] view plaincopy
  1. static void *
  2. ngx_http_circle_gif_create_loc_conf(ngx_conf_t *cf)
  3. {
  4. ngx_http_circle_gif_loc_conf_t  *conf;
  5. conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_circle_gif_loc_conf_t));
  6. if (conf == NULL) {
  7. return NGX_CONF_ERROR;
  8. }
  9. conf->min_radius = NGX_CONF_UNSET_UINT;
  10. conf->max_radius = NGX_CONF_UNSET_UINT;
  11. return conf;
  12. }

merge_loc_conf 函数

           Nginx 为不同的数据类型提供了merge 函数,可查阅 core/ngx_conf_file.h;merge_loc_conf 函数定义如下:
[cpp] view plaincopy
  1. static char *
  2. ngx_http_circle_gif_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
  3. {
  4. ngx_http_circle_gif_loc_conf_t *prev = parent;
  5. ngx_http_circle_gif_loc_conf_t *conf = child;
  6. ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);
  7. ngx_conf_merge_uint_value(conf->max_radius, prev->max_radius, 20);
  8. if (conf->min_radius < 1) {
  9. ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  10. "min_radius must be equal or more than 1");
  11. return NGX_CONF_ERROR;
  12. }
  13. if (conf->max_radius < conf->min_radius) {
  14. ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  15. "max_radius must be equal or more than min_radius");
  16. return NGX_CONF_ERROR;
  17. }
  18. return NGX_CONF_OK;
  19. }

模块的定义

对任何开发模块,都需要定义一个 ngx_module_t 类型的变量来说明这个模块本身的信息,它告诉了 Nginx 这个模块的一些信息。这个变量是 ngx_http_<module name>_module;例如:更多例子可查找文件 core/ngx_conf_file.h;
[cpp] view plaincopy
  1. ngx_module_t  ngx_http_<module name>_module = {
  2. NGX_MODULE_V1,
  3. &ngx_http_<module name>_module_ctx, /* module context */
  4. ngx_http_<module name>_commands,   /* module directives */
  5. NGX_HTTP_MODULE,               /* module type */
  6. NULL,                          /* init master */
  7. NULL,                          /* init module */
  8. NULL,                          /* init process */
  9. NULL,                          /* init thread */
  10. NULL,                          /* exit thread */
  11. NULL,                          /* exit process */
  12. NULL,                          /* exit master */
  13. NGX_MODULE_V1_PADDING
  14. };

Handler 模块

        Handler 模块必须提供一个真正的处理函数,这个函数负责处理来自客户端的请求。该函数既可以选择自己直接生成内容,也可以选择拒绝处理,并由后续的 Handler 去进行处理,或者是选择丢给后续的 Filter 模块进行处理。以下是该函数的原型:

[cpp] view plaincopy
  1. typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);

其中r 是 request 结构http 请求,包含客户端请求所有的信息,例如:request method, URI, and headers。 该函数处理成功返回NGX_OK,处理发生错误返回NGX_ERROR,拒绝处理(留给后续的Handler 进行处理)返回NGX_DECLINE。 返回NGX_OK 也就代表给客户端的响应已经生成,否则返回NGX_ERROR 就发生错误了。

        Handler 模块处理过程中做了四件事情:获取 location 配置生成合适的响应发送响应的 header 头部发送响应的 body 包体

获取 location 配置

        获取 location 配置 指向调用 ngx_http_get_module_loc_conf 函数即可,该函数传入的参数是 request 结构和  自定义的 module 模块。例如:circle gif模块;
[cpp] view plaincopy
  1. static ngx_int_t
  2. ngx_http_circle_gif_handler(ngx_http_request_t *r)
  3. {
  4. ngx_http_circle_gif_loc_conf_t  *circle_gif_config;
  5. circle_gif_config = ngx_http_get_module_loc_conf(r, ngx_http_circle_gif_module);
  6. ...
  7. }

生成合适的响应

         这里主要是 request 结构,其定义如下:更多可参考文件  http/ngx_http_request.h;
[cpp] view plaincopy
  1. typedef struct {
  2. ...
  3. /* the memory pool, used in the ngx_palloc functions */
  4. ngx_pool_t                       *pool;
  5. ngx_str_t                         uri;
  6. ngx_str_t                         args;
  7. ngx_http_headers_in_t             headers_in;
  8. ...
  9. } ngx_http_request_t;

其中参数的意义如下:

  • uri              是 request 请求的路径,e.g. "/query.cgi".
  • args           是请求串参数中问号后面的参数(e.g. "name=john").
  • headers_in 包含有用的stuff,例如:cookies 和browser 信息。

发送响应的 header 头部

       发送响应头部有函数ngx_http_send_header(r) 实现。响应的header 头部在 headers_out 结构中,定义如下:更多可参考文件 http/ngx_http_request.h;
[cpp] view plaincopy
  1. typedef stuct {
  2. ...
  3. ngx_uint_t                        status;
  4. size_t                            content_type_len;
  5. ngx_str_t                         content_type;
  6. ngx_table_elt_t                  *content_encoding;
  7. off_t                             content_length_n;
  8. time_t                            date_time;
  9. time_t                            last_modified_time;
  10. ..
  11. } ngx_http_headers_out_t;

例如,一个模块设置为 Content-Type to "image/gif", Content-Length to 100, and return a 200 OK response,则其实现为:

[cpp] view plaincopy
  1. r->headers_out.status = NGX_HTTP_OK;
  2. r->headers_out.content_length_n = 100;
  3. r->headers_out.content_type.len = sizeof("image/gif") - 1;
  4. r->headers_out.content_type.data = (u_char *) "image/gif";
  5. ngx_http_send_header(r);

假如content_encoding 是 (ngx_table_elt_t*)类型时,则模块需要为这些类型分配内存,可以调用ngx_list_push 函数,实现如下:

[cpp] view plaincopy
  1. r->headers_out.content_encoding = ngx_list_push(&r->headers_out.headers);
  2. if (r->headers_out.content_encoding == NULL) {
  3. return NGX_ERROR;
  4. }
  5. r->headers_out.content_encoding->hash = 1;
  6. r->headers_out.content_encoding->key.len = sizeof("Content-Encoding") - 1;
  7. r->headers_out.content_encoding->key.data = (u_char *) "Content-Encoding";
  8. r->headers_out.content_encoding->value.len = sizeof("deflate") - 1;
  9. r->headers_out.content_encoding->value.data = (u_char *) "deflate";
  10. ngx_http_send_header(r);

发送响应的 body 包体

       到此,该模块已经产生响应,并把它存储在内存中。发送包体的步骤是:首先分配响应特殊的缓冲区,然后分配缓冲区链接到chain link,然后在 chain link 调用发送函数。
       1、chain links 是 Nginx  使 Handler 模块在缓冲区中产生响应。在 chain 中每个 chain link 有一个指向下一个 link 的指针。首先,模块声明缓冲区 buffer 和 chain link:
[cpp] view plaincopy
  1. ngx_buf_t    *b;
  2. ngx_chain_t   out;

2、然后分配缓冲区 buffer,使响应数据指向它:

[cpp] view plaincopy
  1. b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
  2. if (b == NULL) {
  3. ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  4. "Failed to allocate response buffer.");
  5. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  6. }
  7. b->pos = some_bytes; /* first position in memory of the data */
  8. b->last = some_bytes + some_bytes_length; /* last position */
  9. b->memory = 1; /* content is in read-only memory */
  10. /* (i.e., filters should copy it rather than rewrite in place) */
  11. b->last_buf = 1; /* there will be no more buffers in the request */

3、接着,把模块挂载到 chain link 上:

[cpp] view plaincopy
  1. out.buf = b;
  2. out.next = NULL;

4、最后,发送包体:

[cpp] view plaincopy
  1. return ngx_http_output_filter(r, &out);

Handler 模块挂载

        Handler 模块真正的处理函数通过两种方式挂载到处理过程中:按处理阶段挂载;按需挂载。

按处理阶段挂载

为了更精细地控制对于客户端请求的处理过程,Nginx 把这个处理过程划分成了11个阶段。依次列举如下:

[cpp] view plaincopy
  1. NGX_HTTP_POST_READ_PHASE:
  2. 读取请求内容阶段
  3. NGX_HTTP_SERVER_REWRITE_PHASE:
  4. Server请求地址重写阶段
  5. NGX_HTTP_FIND_CONFIG_PHASE:
  6. 配置查找阶段:
  7. NGX_HTTP_REWRITE_PHASE:
  8. Location请求地址重写阶段
  9. NGX_HTTP_POST_REWRITE_PHASE:
  10. 请求地址重写提交阶段
  11. NGX_HTTP_PREACCESS_PHASE:
  12. 访问权限检查准备阶段
  13. NGX_HTTP_ACCESS_PHASE:
  14. 访问权限检查阶段
  15. NGX_HTTP_POST_ACCESS_PHASE:
  16. 访问权限检查提交阶段
  17. NGX_HTTP_TRY_FILES_PHASE:
  18. 配置项try_files处理阶段
  19. NGX_HTTP_CONTENT_PHASE:
  20. 内容产生阶段
  21. NGX_HTTP_LOG_PHASE:
  22. 日志模块处理阶段

一般情况下,我们自定义的模块,大多数是挂载在NGX_HTTP_CONTENT_PHASE阶段的。挂载的动作一般是在模块上下文调用的postconfiguration 函数中。注意:有几个阶段是特例,它不调用挂载任何的Handler,也就是你就不用挂载到这几个阶段了:

[cpp] view plaincopy
  1. NGX_HTTP_FIND_CONFIG_PHASE
  2. NGX_HTTP_POST_ACCESS_PHASE
  3. NGX_HTTP_POST_REWRITE_PHASE
  4. NGX_HTTP_TRY_FILES_PHASE

按需挂载

以这种方式挂载的Handler 也被称为content handler。当一个请求进来以后,Nginx 从NGX_HTTP_POST_READ_PHASE 阶段开始依次执行每个阶段中所有 Handler。执行到  NGX_HTTP_CONTENT_PHASE 阶段时,如果这个location 有一个对应的content handler 模块,那么就去执行这个content handler 模块真正的处理函数。否则继续依次执行NGX_HTTP_CONTENT_PHASE 阶段中所有content phase handlers,直到某个函数处理返回NGX_OK 或者NGX_ERROR。但是使用这个方法挂载上去的handler 有一个特点是必须在NGX_HTTP_CONTENT_PHASE 阶段才能被执行。如果你想自己的handler 更早的阶段被执行,那就不要使用这种挂载方式。        以下是例子:

[cpp] view plaincopy
  1. circle gif ngx_command_t looks like this:
  2. { ngx_string("circle_gif"),
  3. NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
  4. ngx_http_circle_gif,
  5. 0,
  6. 0,
  7. NULL }

挂载函数:

[cpp] view plaincopy
  1. static char *
  2. ngx_http_circle_gif(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  3. {
  4. ngx_http_core_loc_conf_t  *clcf;
  5. clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
  6. clcf->handler = ngx_http_circle_gif_handler;
  7. return NGX_CONF_OK;
  8. }

Handler 模块编写

Handler 模块编写步骤如下:

  1. 编写模块基本结构:包括模块的定义,模块上下文结构,模块的配置结构等;
  2. 实现 handler 的挂载函数;根据模块的需求选择正确的挂载方式;
  3. 编写 handler 处理函数;模块的功能主要通过这个函数来完成;

Filter 模块

Filter 处理由Handler 模块产生的响应。Filter 模块包括过滤头部(Header Filter)和过滤包体(Body Filter ),Filter 模块过滤头部处理HTTP 的头部(HTTP headers),Filter 包体处理响应内容(response content),这两个阶段可以对HTTP 响应头部和内容进行修改。

Filter 模块相关结构

        Filter 模块是采用链表形式的,其基本结构是ngx_chain_t 和 ngx_buf_t;这两种结构定义如下:
[cpp] view plaincopy
  1. typedef struct ngx_chain_s ngx_chain_t;
  2. struct ngx_chain_s {
  3. ngx_buf_t    *buf;
  4. ngx_chain_t  *next;
  5. };
  6. struct ngx_buf_s {
  7. u_char          *pos;       /* 当前buffer真实内容的起始位置 */
  8. u_char          *last;      /* 当前buffer真实内容的结束位置 */
  9. off_t            file_pos;  /* 在文件中真实内容的起始位置   */
  10. off_t            file_last; /* 在文件中真实内容的结束位置   */
  11. u_char          *start;    /* buffer内存的开始分配的位置 */
  12. u_char          *end;      /* buffer内存的结束分配的位置 */
  13. ngx_buf_tag_t    tag;      /* buffer属于哪个模块的标志 */
  14. ngx_file_t      *file;     /* buffer所引用的文件 */
  15. /* 用来引用替换过后的buffer,以便当所有buffer输出以后,
  16. * 这个影子buffer可以被释放。
  17. */
  18. ngx_buf_t       *shadow;
  19. /* the buf's content could be changed */
  20. unsigned         temporary:1;
  21. /*
  22. * the buf's content is in a memory cache or in a read only memory
  23. * and must not be changed
  24. */
  25. unsigned         memory:1;
  26. /* the buf's content is mmap()ed and must not be changed */
  27. unsigned         mmap:1;
  28. unsigned         recycled:1; /* 内存可以被输出并回收 */
  29. unsigned         in_file:1;  /* buffer的内容在文件中 */
  30. /* 马上全部输出buffer的内容, gzip模块里面用得比较多 */
  31. unsigned         flush:1;
  32. /* 基本上是一段输出链的最后一个buffer带的标志,标示可以输出,
  33. * 有些零长度的buffer也可以置该标志
  34. */
  35. unsigned         sync:1;
  36. /* 所有请求里面最后一块buffer,包含子请求 */
  37. unsigned         last_buf:1;
  38. /* 当前请求输出链的最后一块buffer         */
  39. unsigned         last_in_chain:1;
  40. /* shadow链里面的最后buffer,可以释放buffer了 */
  41. unsigned         last_shadow:1;
  42. /* 是否是暂存文件 */
  43. unsigned         temp_file:1;
  44. /* 统计用,表示使用次数 */
  45. /* STUB */ int   num;
  46. };

Filter 过滤头部

header filter 包含三个基本步骤:

  1. 决定是否处理响应;
  2. 对响应进行处理;
  3. 调用下一个 filter;
        例如下面的"not modified" header filter:其中 headers_out 结构可参考文件 http/ngx_http_request.h;

[cpp] view plaincopy
  1. static
  2. ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r)
  3. {
  4. time_t if_modified_since;
  5. if_modified_since = ngx_http_parse_time(r->headers_in.if_modified_since->value.data,
  6. r->headers_in.if_modified_since->value.len);
  7. /* step 1: decide whether to operate */
  8. if (if_modified_since != NGX_ERROR &&
  9. if_modified_since == r->headers_out.last_modified_time) {
  10. /* step 2: operate on the header */
  11. r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
  12. r->headers_out.content_type.len = 0;
  13. ngx_http_clear_content_length(r);
  14. ngx_http_clear_accept_ranges(r);
  15. }
  16. /* step 3: call the next filter */
  17. return ngx_http_next_header_filter(r);
  18. }

Filter 过滤包体

Filter 包体只能在chain link缓冲区buffer 中操作。模块必须决定是否修改输入缓冲区,或分配新的缓冲区替换当前缓冲区,或是在当前缓冲区之后还是之前插入新的缓冲区。很多模块接收多个缓冲区,导致这些模块在不完整的chain 缓冲区中操作。Filter 包体操作如下:

[cpp] view plaincopy
  1. static ngx_int_t ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in);

以下是一个例子:

[cpp] view plaincopy
  1. /*
  2. * Let's take a simple example.
  3. * Suppose we want to insert the text "<l!-- Served by Nginx -->" to the end of every request.
  4. * First, we need to figure out if the response's final buffer is included in the buffer chain we were given.
  5. * Like I said, there's not a fancy API, so we'll be rolling our own for loop:
  6. */
  7. ngx_chain_t *chain_link;
  8. int chain_contains_last_buffer = 0;
  9. chain_link = in;
  10. for ( ; ; ) {
  11. if (chain_link->buf->last_buf)
  12. chain_contains_last_buffer = 1;
  13. if (chain_link->next == NULL)
  14. break;
  15. chain_link = chain_link->next;
  16. }
  17. /*
  18. * Now let's bail out if we don't have that last buffer:
  19. */
  20. if (!chain_contains_last_buffer)
  21. return ngx_http_next_body_filter(r, in);
  22. /*
  23. * Super, now the last buffer is stored in chain_link.
  24. * Now we allocate a new buffer:
  25. */
  26. ngx_buf_t    *b;
  27. b = ngx_calloc_buf(r->pool);
  28. if (b == NULL) {
  29. return NGX_ERROR;
  30. }
  31. /*
  32. * And put some data in it:
  33. */
  34. b->pos = (u_char *) "<!-- Served by Nginx -->";
  35. b->last = b->pos + sizeof("<!-- Served by Nginx -->") - 1;
  36. /*
  37. * And hook the buffer into a new chain link:
  38. */
  39. ngx_chain_t   *added_link;
  40. added_link = ngx_alloc_chain_link(r->pool);
  41. if (added_link == NULL)
  42. return NGX_ERROR;
  43. added_link->buf = b;
  44. added_link->next = NULL;
  45. /*
  46. * Finally, hook the new chain link to the final chain link we found before:
  47. */
  48. chain_link->next = added_link;
  49. /*
  50. * And reset the "last_buf" variables to reflect reality:
  51. */
  52. chain_link->buf->last_buf = 0;
  53. added_link->buf->last_buf = 1;
  54. /*
  55. * And pass along the modified chain to the next output filter:
  56. */
  57. return ngx_http_next_body_filter(r, in);
  58. /*
  59. * The resulting function takes much more effort than what you'd do with, say, mod_perl ($response->body =~ s/$/<!-- Served by mod_perl -->/),
  60. * but the buffer chain is a very powerful construct, allowing programmers to process data incrementally so that the client gets something as soon as possible.
  61. * However, in my opinion, the buffer chain desperately needs a cleaner interface so that programmers can't leave the chain in an inconsistent state.
  62. * For now, manipulate it at your own risk.
  63. */

Filter 模块挂载

        Filters 模块和Handler 模块一样,也是挂载到post-configuration ,如下面代码所示:

[cpp] view plaincopy
  1. static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {
  2. NULL, /* preconfiguration */
  3. ngx_http_chunked_filter_init, /* postconfiguration */
  4. ...
  5. };

其中 ngx_http_chunked_filter_init 处理如下定义:

[cpp] view plaincopy
  1. static ngx_int_t
  2. ngx_http_chunked_filter_init(ngx_conf_t *cf)
  3. {
  4. ngx_http_next_header_filter = ngx_http_top_header_filter;
  5. ngx_http_top_header_filter = ngx_http_chunked_header_filter;
  6. ngx_http_next_body_filter = ngx_http_top_body_filter;
  7. ngx_http_top_body_filter = ngx_http_chunked_body_filter;
  8. return NGX_OK;
  9. }

由于 Filter 模块是 “CHAIN OF RESPONSIBILITY” 链表模式的。Handler 模块生成响应后,Filter 模块调用两个函数:ngx_http_output_filter和 ngx_http_send_header,其中ngx_http_output_filter 函数是调用全局函数 ngx_http_top_body_filter;ngx_http_send_header 函数是调用全局函数 ngx_http_top_header_filter。

[cpp] view plaincopy
  1. ngx_int_t
  2. ngx_http_send_header(ngx_http_request_t *r)
  3. {
  4. ...
  5. return ngx_http_top_header_filter(r);
  6. }
  7. ngx_int_t
  8. ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
  9. {
  10. ngx_int_t          rc;
  11. ngx_connection_t  *c;
  12. c = r->connection;
  13. rc = ngx_http_top_body_filter(r, in);
  14. if (rc == NGX_ERROR) {
  15. /* NGX_ERROR may be returned by any filter */
  16. c->error = 1;
  17. }
  18. return rc;
  19. }

Filter 模块的指行方式如下图所示:

开发 Nginx 新模块

        把自己开发的模块编译到 Nginx 中需要编写两个文件:
  1. "config",该文件会被 ./configure 包含;
  2. "ngx_http_<your module>_module.c",该文件是定义模块的功能;
        config 文件的编写如下:
[cpp] view plaincopy
  1. /*
  2. * "config" for filter modules:
  3. */
  4. ngx_addon_name=ngx_http_<your module>_module  /* 模块的名称 */
  5. HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_<your module>_module"  /* 保存所有 HTTP 模块*/
  6. NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_<your module>_module.c"       /* 指定新模块的源码路径 */
  7. /*
  8. * "config" for other modules:
  9. */
  10. ngx_addon_name=ngx_http_<your module>_module
  11. HTTP_MODULES="$HTTP_MODULES ngx_http_<your module>_module"
  12. NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_<your module>_module.c"
        关于 "ngx_http_<your module>_module.c" 文件的编写,可参考上面的Handler 模块,同时可参考Nginx 现有的模块:src/http/modules/;例如下面的“Hello World ”代码:
[cpp] view plaincopy
  1. #include <ngx_config.h>
  2. #include <ngx_core.h>
  3. #include <ngx_http.h>
  4. typedef struct
  5. {
  6. ngx_str_t hello_string;
  7. ngx_int_t hello_counter;
  8. }ngx_http_hello_loc_conf_t;
  9. static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf);
  10. static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);
  11. static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd,
  12. void *conf);
  13. static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd,
  14. void *conf);
  15. static ngx_command_t ngx_http_hello_commands[] = {
  16. {
  17. ngx_string("hello_string"),
  18. NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
  19. ngx_http_hello_string,
  20. NGX_HTTP_LOC_CONF_OFFSET,
  21. offsetof(ngx_http_hello_loc_conf_t, hello_string),
  22. NULL },
  23. {
  24. ngx_string("hello_counter"),
  25. NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
  26. ngx_http_hello_counter,
  27. NGX_HTTP_LOC_CONF_OFFSET,
  28. offsetof(ngx_http_hello_loc_conf_t, hello_counter),
  29. NULL },
  30. ngx_null_command
  31. };
  32. /*
  33. static u_char ngx_hello_default_string[] = "Default String: Hello, world!";
  34. */
  35. static int ngx_hello_visited_times = 0;
  36. static ngx_http_module_t ngx_http_hello_module_ctx = {
  37. NULL,                          /* preconfiguration */
  38. ngx_http_hello_init,           /* postconfiguration */
  39. NULL,                          /* create main configuration */
  40. NULL,                          /* init main configuration */
  41. NULL,                          /* create server configuration */
  42. NULL,                          /* merge server configuration */
  43. ngx_http_hello_create_loc_conf, /* create location configuration */
  44. NULL                            /* merge location configuration */
  45. };
  46. ngx_module_t ngx_http_hello_module = {
  47. NGX_MODULE_V1,
  48. &ngx_http_hello_module_ctx,    /* module context */
  49. ngx_http_hello_commands,       /* module directives */
  50. NGX_HTTP_MODULE,               /* module type */
  51. NULL,                          /* init master */
  52. NULL,                          /* init module */
  53. NULL,                          /* init process */
  54. NULL,                          /* init thread */
  55. NULL,                          /* exit thread */
  56. NULL,                          /* exit process */
  57. NULL,                          /* exit master */
  58. NGX_MODULE_V1_PADDING
  59. };
  60. static ngx_int_t
  61. ngx_http_hello_handler(ngx_http_request_t *r)
  62. {
  63. ngx_int_t    rc;
  64. ngx_buf_t   *b;
  65. ngx_chain_t  out;
  66. ngx_http_hello_loc_conf_t* my_conf;
  67. u_char ngx_hello_string[1024] = {0};
  68. ngx_uint_t content_length = 0;
  69. ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "ngx_http_hello_handler is called!");
  70. my_conf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
  71. if (my_conf->hello_string.len == 0 )
  72. {
  73. ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string is empty!");
  74. return NGX_DECLINED;
  75. }
  76. if (my_conf->hello_counter == NGX_CONF_UNSET
  77. || my_conf->hello_counter == 0)
  78. {
  79. ngx_sprintf(ngx_hello_string, "%s", my_conf->hello_string.data);
  80. }
  81. else
  82. {
  83. ngx_sprintf(ngx_hello_string, "%s Visited Times:%d", my_conf->hello_string.data,
  84. ++ngx_hello_visited_times);
  85. }
  86. ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string:%s", ngx_hello_string);
  87. content_length = ngx_strlen(ngx_hello_string);
  88. /* we response to 'GET' and 'HEAD' requests only */
  89. if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
  90. return NGX_HTTP_NOT_ALLOWED;
  91. }
  92. /* discard request body, since we don't need it here */
  93. rc = ngx_http_discard_request_body(r);
  94. if (rc != NGX_OK) {
  95. return rc;
  96. }
  97. /* set the 'Content-type' header */
  98. /*
  99. *r->headers_out.content_type.len = sizeof("text/html") - 1;
  100. *r->headers_out.content_type.data = (u_char *)"text/html";
  101. */
  102. ngx_str_set(&r->headers_out.content_type, "text/html");
  103. /* send the header only, if the request type is http 'HEAD' */
  104. if (r->method == NGX_HTTP_HEAD) {
  105. r->headers_out.status = NGX_HTTP_OK;
  106. r->headers_out.content_length_n = content_length;
  107. return ngx_http_send_header(r);
  108. }
  109. /* allocate a buffer for your response body */
  110. b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
  111. if (b == NULL) {
  112. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  113. }
  114. /* attach this buffer to the buffer chain */
  115. out.buf = b;
  116. out.next = NULL;
  117. /* adjust the pointers of the buffer */
  118. b->pos = ngx_hello_string;
  119. b->last = ngx_hello_string + content_length;
  120. b->memory = 1;    /* this buffer is in memory */
  121. b->last_buf = 1;  /* this is the last buffer in the buffer chain */
  122. /* set the status line */
  123. r->headers_out.status = NGX_HTTP_OK;
  124. r->headers_out.content_length_n = content_length;
  125. /* send the headers of your response */
  126. rc = ngx_http_send_header(r);
  127. if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
  128. return rc;
  129. }
  130. /* send the buffer chain of your response */
  131. return ngx_http_output_filter(r, &out);
  132. }
  133. static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
  134. {
  135. ngx_http_hello_loc_conf_t* local_conf = NULL;
  136. local_conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
  137. if (local_conf == NULL)
  138. {
  139. return NULL;
  140. }
  141. ngx_str_null(&local_conf->hello_string);
  142. local_conf->hello_counter = NGX_CONF_UNSET;
  143. return local_conf;
  144. }
  145. /*
  146. static char *ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
  147. {
  148. ngx_http_hello_loc_conf_t* prev = parent;
  149. ngx_http_hello_loc_conf_t* conf = child;
  150. ngx_conf_merge_str_value(conf->hello_string, prev->hello_string, ngx_hello_default_string);
  151. ngx_conf_merge_value(conf->hello_counter, prev->hello_counter, 0);
  152. return NGX_CONF_OK;
  153. }*/
  154. static char *
  155. ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  156. {
  157. ngx_http_hello_loc_conf_t* local_conf;
  158. local_conf = conf;
  159. char* rv = ngx_conf_set_str_slot(cf, cmd, conf);
  160. ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_string:%s", local_conf->hello_string.data);
  161. return rv;
  162. }
  163. static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd,
  164. void *conf)
  165. {
  166. ngx_http_hello_loc_conf_t* local_conf;
  167. local_conf = conf;
  168. char* rv = NULL;
  169. rv = ngx_conf_set_flag_slot(cf, cmd, conf);
  170. ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_counter:%d", local_conf->hello_counter);
  171. return rv;
  172. }
  173. static ngx_int_t
  174. ngx_http_hello_init(ngx_conf_t *cf)
  175. {
  176. ngx_http_handler_pt        *h;
  177. ngx_http_core_main_conf_t  *cmcf;
  178. cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
  179. h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
  180. if (h == NULL) {
  181. return NGX_ERROR;
  182. }
  183. *h = ngx_http_hello_handler;
  184. return NGX_OK;
  185. }
        写好上面的两个文件后,在编译 Nginx 时,步骤如下:
[python] view plaincopy
  1. ./configure --add-module=path/to/your/new/module/directory
  2. make
  3. make install

参考资料:

《Emiller's Guide To Nginx Module Development》
《nginx模块开发篇》
《https://github.com/simpl/ngx_devel_kit》

Nginx 模块开发相关推荐

  1. Nginx模块开发入门

    前言 Nginx是当前最流行的HTTP Server之一,根据W3Techs的统计,目前世界排名(根据Alexa)前100万的网站中,Nginx的占有率为6.8%.与Apache相比,Nginx在高并 ...

  2. Nginx 模块开发高级篇

    Nginx 模块开发高级篇 变量 综述 在Nginx中同一个请求需要在模块之间数据的传递或者说在配置文件里面使用模块动态的数据一般来说都是使用变量,比如在HTTP模块中导出了host/remote_a ...

  3. Nginx模块开发—Nginx代码规范

    2019独角兽企业重金招聘Python工程师标准>>> 1.简介 基本上,Nginx所采用的是一种类似BSD的C代码风格,很规范.也很清晰.建议我们的Nginx模块开发也采用Ngin ...

  4. Nginx模块开发系列1--预备知识

    摘要:在进行Nginx模块开发之前,需要先了解一下Nginx的模块工作原理以及需要用到的结构体. 文章目录 1.Nginx模块工作原理 2.ngx_module_t 结构体 3.ngx_command ...

  5. Nginx源码从模块开发入手,3个项目弄透nginx模块开发丨Linux服务器开发丨C++后端开发丨中间件开发丨分布式丨web服务器

    Nginx源码从模块开发入手,3个项目弄透nginx模块开发 1. Nginx http请求的11个处理流程 2. Upstream, Filter,Handler模块分析 3. nginx如何拒绝无 ...

  6. 16w行的nginx源码,如何分拆模块阅读,手把手教你造轮子丨Nginx模块开发丨C/C++丨Linux服务器开发丨后端开发

    16w行的nginx源码,如何分拆模块阅读,让你明白轮子如何造  1. 多进程模型下的惊群处理 2. 内存池的代码封装 3. slab共享内存分配 视频讲解如下,点击观看: 16w行的nginx源码, ...

  7. Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

    Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现 一.Nignx中的模块是什么? 二.模块的基本结构 `ngx_module_s` `ngx_command_s` `ngx_ ...

  8. 【Linux网络编程】Nginx -- 模块开发(upstream / subrequest)

    [Linux网络编程]Nginx -- 模块开发(upstream / subrequest) [1]upstream VS subrequest 简介 upstream 为访问上游服务器,它把Ngi ...

  9. 推荐我的新书《深入理解Nginx:模块开发与架构解析》

    http://www.china-pub.com/STATIC/zt_mb/zt_huodong_2013_3.asp?filename=2013_jsj_nginx_20130401 目录 < ...

最新文章

  1. ensp查看历史配置命令_eNSP常用命令.doc
  2. java 连接kafka超时_java – Kafka KStreams – 处理超时
  3. shell bash判断文件或文件夹是否存在
  4. SmartSVN for Mac 使用说明
  5. 转 Java jar (SpringBoot Jar)转为win可执行的exe程序
  6. Java进阶: springmvc已经引入jar包仍报错lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper
  7. java udp发送速率_项目总结22:Java UDP Socket数据的发送和接收
  8. 用小乌龟git解决冲突之后,再提交,出现自己没用动过的文件
  9. 机顶盒改成无线打印服务器,【当贝市场】如何将网络机顶盒改装成无线路由器...
  10. matlab导入txt数据画图
  11. 【SpringCloud系列】 分布式事务-LCN
  12. 三校生计算机教学计划,三校生高考英语教学计划.doc
  13. python小白系列1
  14. #留言板(五)#留言板界面
  15. ZOJ 3557 (插板法+Lucas定理)
  16. 中北大学信息商务学院计算机系学生会名单,2017年中北大学信息商务学院新媒体中心部员竞选结果公示...
  17. 最简单的 Git 入门教程
  18. 关于APP广告位的设计与优化(上)
  19. videojs中文文档详解_HTTP的世界观(附HTTP/3中文翻译)
  20. matlab在解线性方程组的应用,matlab解线性方程组线性方程组及MATLAB应用

热门文章

  1. 【MYSQL】总结MySQL中对表内容的关联运算(join)
  2. 发现文件夹和文件夹都显示为蓝色和绿色,是否中毒了?
  3. Oracle DBA之配置静态监听(listener registered statically)
  4. Lync在Internet上无法登录问题之一
  5. 一起谈.NET技术,.NET异步编程:IO完成端口与BeginRead
  6. 深入Android 【五】 —— 任务和进程
  7. 简单的消息发送小程序
  8. 2020ICPC(小米邀请赛1) - Phone Network(线段树优化递推)
  9. CodeForces - 1293C NEKO's Maze Game(思维,水题)
  10. 【Boost】boost库asio详解9——TCP的简单例子2