Nginx学习笔记系列第六篇-定制开发Nginx插件

这是我参与更文挑战的第24天,活动详情查看: 更文挑战

背景

这是Nginx学习笔记汇总的第六篇,继续上一篇文章,把定制开发Nginx插件的相关笔记继续记录一下

定制开发Nginx插件

模块上下文结构

模块的上下文结构存储在ngx_http_module_t类型的静态变量中

typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

    void       *(*create_main_conf)(ngx_conf_t *cf);
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t; 
复制代码

它是一组回调函数指针,这些函数有在创建存储配置信息的对象的函数,也有在创建前和创建后会调用的函数,这些函数都将被Nginx在适当的时间调用

preconfiguration: 在创建和读取该模块的配置信息之前被调用
postconfiguration: 在创建和读取该模块的配置信息之后被调用
create_main_conf: 调用该函数创建本模块位于http block的配置信息存储结构,该函数成功的时候,返回创建的配置对象,失败的话,返回NULL
init_main_conf: 调用该函数初始化本模块位于http block的配置信息存储结构,该函数成功时,返回NGX_CONF_OK,失败返回NGX_CONF_ERROR或错误字符串
create_srv_conf: 调用该函数创建本模块位于http server block的配置信息存储结构,每个server block会创建一个,该函数成功时返回创建的配置对象,失败返回 NULL
merge_srv_conf: 因为有些配置指令既可以出现在http block,也可以出现在http server block中,那么遇到这种情况,每个server都会有自己存储结构来存储该server的配置,但是在这种情况下http block中的配置与server block中的配置信息发生冲突的时候,就需要调用此函数进行合并,该函数并非必须提供,当预计到绝对不会发生需要合并的情况的时候,就无需提供;为了安全起见还是建议提供,该函数执行成功时,返回NGX_CONF_OK,失败返回NGX_CONF_ERROR或错误字符串
create_loc_conf: 调用该函数创建本模块位于location block的配置信息存储结构,每个在配置中指明的location创建一个,该函数执行成功返回创建的配置对象,失败话返回NULL
merge_loc_conf: 与merge_srv_conf类似,这个也是进行配置值合并的地方,该函数成功时返回NGX_CONF_OK,失败返回NGX_CONF_ERROR或错误字符串
复制代码

Nginx里面的配置信息都是上下一层层的嵌套的,对于具体某个location的话,对于同一个配置,如果当前层次没有定义,那么就使用上层的配置,否则使用当前层次的配置
这些配置信息一般默认都应该设为一个未初始化的值,针对这个需求,Nginx定义了一系列的宏定义来代表各种配置所对应数据类型的未初始化值:

#define NGX_CONF_UNSET       -1
#define NGX_CONF_UNSET_UINT  (ngx_uint_t) -1
#define NGX_CONF_UNSET_PTR   (void *) -1
#define NGX_CONF_UNSET_SIZE  (size_t) -1
#define NGX_CONF_UNSET_MSEC  (ngx_msec_t) -1
复制代码

如果在本层次已经配置了,也就是配置项的值已经被读取进来了,就使用本层次的值作为定义合并的结果,否则使用上层的值,如果上层的值也是这些UNSET类的值,那就赋值为默认值,否则就使用上层的值作为合并的结果,对于这样类似的操作,Nginx定义了一些宏操作来做这些事情,例如:

#define ngx_conf_merge_uint_value(conf, prev, default) \
    if (conf == NGX_CONF_UNSET_UINT) {      \
        conf = (prev == NGX_CONF_UNSET_UINT) ? default : prev; \
    }
复制代码

一个名为hello模块的上下文的定义:

static ngx_http_module_t ngx_http_hello_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_hello_init,           /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */

    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

    ngx_http_hello_create_loc_conf, /* create location configuration */
    NULL                        /* merge location configuration */
};
复制代码

注意:这里并没有提供merge_loc_conf函数,因为这个模块的配置指令已经确定只出现在NGX_HTTP_LOC_CONF中这一个层次上,不会发生需要合并的情况

模块的定义

开发模块需要定义一个ngx_module_t类型的变量来说明这个模块本身的信息,这是这个模块最重要的一个信息,它告诉Nginx这个模块的一些信息,上面定义的配置信息,还有模块上下文信息都是通过这个结构来告诉Nginx系统的,也就是加载模块的上层代码,都需要通过定义的这个结构来获取这些信息

ngx_module_t结构的定义:

typedef struct ngx_module_s      ngx_module_t;
struct ngx_module_s {
    ngx_uint_t            ctx_index;
    ngx_uint_t            index;
    ngx_uint_t            spare0;
    ngx_uint_t            spare1;
    ngx_uint_t            abi_compatibility;
    ngx_uint_t            major_version;
    ngx_uint_t            minor_version;
    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;
    ngx_int_t           (*init_master)(ngx_log_t *log);
    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);
    void                (*exit_master)(ngx_cycle_t *cycle);
    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

#define NGX_NUMBER_MAJOR  3
#define NGX_NUMBER_MINOR  1
#define NGX_MODULE_V1          0, 0, 0, 0,                              \
    NGX_DSO_ABI_COMPATIBILITY, NGX_NUMBER_MAJOR, NGX_NUMBER_MINOR
#define NGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0, 0, 0
复制代码

例:
hello模块的模块定义

ngx_module_t ngx_http_hello_module = {
    NGX_MODULE_V1,
    &ngx_http_hello_module_ctx,    /* module context */
    ngx_http_hello_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
};
复制代码

模块可以提供一些回调函数给Nginx,当Nginx在创建进程线程或者结束进程线程时进行调用,但大多数模块在这些时刻并不需要做什么,所以都简单赋值为NULL

handler处理函数

handler模块必须提供一个处理函数,用于处理客户端的请求,这个函数既可以选择自己直接生成内容,也可以选择拒绝处理,由后续的handler去进行处理,或者是选择丢给后续的filter进行处理

函数的原型申明:

typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
复制代码

r是http请求,里面包含请求所有的信息
函数处理成功返回NGX_OK,处理发生错误返回NGX_ERROR,拒绝处理(留给后续handler进行处理)返回NGX_DECLINE,返回NGX_OK也就代表给客户端的响应已经生成好了,否则返回NGX_ERROR就表示发生错误

handler模块的挂载

handler模块的处理函数通过两种方式挂载到处理过程中:

  • 按处理阶段挂载
  • 按需挂载

按处理阶段挂载handler模块

为了精细控制客户端请求的处理过程,Nginx把处理过程划分成11个阶段,从前到后依次是:

NGX_HTTP_POST_READ_PHASE: 读取请求内容阶段
NGX_HTTP_SERVER_REWRITE_PHASE: Server请求地址重写阶段
NGX_HTTP_FIND_CONFIG_PHASE: 配置查找阶段
NGX_HTTP_REWRITE_PHASE: Location请求地址重写阶段
NGX_HTTP_POST_REWRITE_PHASE: 请求地址重写提交阶段
NGX_HTTP_PREACCESS_PHASE: 访问权限检查准备阶段
NGX_HTTP_ACCESS_PHASE: 访问权限检查阶段
NGX_HTTP_POST_ACCESS_PHASE: 访问权限检查提交阶段
NGX_HTTP_TRY_FILES_PHASE: 配置项try_files处理阶段
NGX_HTTP_CONTENT_PHASE: 内容产生阶段
NGX_HTTP_LOG_PHASE: 日志模块处理阶段
复制代码

自定义模块大多数是挂载在NGX_HTTP_CONTENT_PHASE阶段,挂载的动作一般是在模块上下文调用的postconfiguration函数中

注意:有几个阶段是特例,它不调用挂载地任何的handler,也就是你就不用挂载到这几个阶段:

NGX_HTTP_FIND_CONFIG_PHASE
NGX_HTTP_POST_ACCESS_PHASE
NGX_HTTP_POST_REWRITE_PHASE
NGX_HTTP_TRY_FILES_PHASE
复制代码

所以其实真正是有7个phase可以去挂载handler

挂载的代码如下:

static ngx_int_t
ngx_http_hello_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    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);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_hello_handler;

    return NGX_OK;
}
复制代码

使用这种方式挂载的handler也被称为content phase handlers

按需挂载handler模块

以这种方式挂载的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,即当某个location处理到NGX_HTTP_CONTENT_PHASE阶段时,如果有content handler模块,那么NGX_HTTP_CONTENT_PHASE挂载的所有content phase handlers都不会被执行

使用这个方法挂载上去的handler有一个特点是必须在NGX_HTTP_CONTENT_PHASE阶段才能执行到,如果你想自己的handler在更早的阶段执行,那就不要使用这种挂载方式

使用场景: 比如某个模块对某个location进行了处理以后,发现符合自己处理的逻辑,而且也没有必要再调用NGX_HTTP_CONTENT_PHASE阶段的其它handler进行处理的时候,就动态挂载上这个handler

挂载示例

static char *
ngx_http_circle_gif(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_circle_gif_handler;

    return NGX_CONF_OK;
}
复制代码

handler模块的编写步骤

实现一个handler模块的步骤;

  1. 编写模块基本结构:包括模块的定义,模块上下文结构,模块的配置结构等
  2. 实现handler的挂载函数:根据模块的需求选择正确的挂载方
  3. 编写handler处理函数:模块的功能主要通过这个函数来完成
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享