打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
【Linux网络编程】Nginx -- 模块开发(基本模块解析)

【1】处理流程图示

Nginx 一次常规的请求和响应的处理流程

典型的 HTTP 模块在 Nginx 中调用的简化流程

【2】模块开发示例

【2.1】将模块编译进入 Nginx

将模块源代码文件放到一个目录下,并在该目录中编写一个文件用于告知Nginx编译本模块的方式,该文件名必须为config;此时只要在configure脚本执行时加入参数--add-module=PATH(PATH为给定的源代码、config文件的保存目录),便可以在执行正常编译安装流程时完成Nginx编译工作;

【2.1.1】Config 文件解析

#仅在configure执行时使用,一般设置为模块名称ngx_addon_name=模块完整名称#保存所有的HTTP模块名称,每个HTTP模块间由空格符相连HTTP_MODULES="$HTTP_MODULES 模块完整名称"#用于指定新增模块的源代码,多个待编译的源代码间以空格符相连#在设置NGX_ADDON_SRCS时使用的参数$ngx_addon_dir的值为--add-module=PATH的PATH参数NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/源代码文件名"

【2.1.2】Nginx 模块编译安装

./configure --prefix=安装目录 --add-module=模块源代码文件目录makemake install

【2.2】Nginx 模块示例代码解析

【2.2.1】发送内存数据

// 该 HTTP 模块接入 Nginx 的方式// 1. 不希望该模块对整个 HTTP 请求有效// 2. 在 nginx.conf 文件中的 http{}、server{}、location{} 块内定义 mytest 配置项//// 在 HTTP 框架定义的 NGX_HTTP_CONTENT_PHASE 阶段开始处理请求#include <ngx_config.h>#include <ngx_core.h>#include <ngx_http.h>static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r);// commands数组用于定义模块的配置文件参数,每一个数组元素都是ngx_command_t类型// 数组以ngx_null_command结尾// #define ngx_null_command  { ngx_null_string, 0, NULL, 0, 0, NULL }// 空指令,用于在指令数组的最后当做哨兵,结束数组,避免指定长度,类似NULL的作用static ngx_command_t  ngx_http_mytest_commands[] ={    // 定义mytest配置项的处理,此处指定mytest配置项由ngx_http_mytest函数处理    {        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_command_t 结构体中的 set 成员        // 当在某个配置块中出现 mytest 配置项时,nginx 将会调用 ngx_http_mytest 方法        ngx_http_mytest,        NGX_HTTP_LOC_CONF_OFFSET,        0,        NULL    },    ngx_null_command};// 定义 HTTP 框架各阶段的回调方法// 若HTTP框架初始化时无需完成特定的工作则可将回调置为NULLstatic 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 模块// 回调方法,init_module、init_process、exit_process、exit_master 由 Nginx 框架代码调用,与 HTTP 模块无关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};static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){    ngx_http_core_loc_conf_t  *clcf;    // 首先找到 mytest 配置项所属的配置块,clcf 可以是 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 方法处理该请求    //    // 请求处理函数的原型,见 src/http/ngx_http_request.h    // typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);    clcf->handler = ngx_http_mytest_handler;    return NGX_CONF_OK;}// ngx_http_request_t中保存了请求的信息static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r){    // 判断 HTTP 头信息中的方法是否支持    if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)))    {        return NGX_HTTP_NOT_ALLOWED;    }    // 丢弃 HTTP 请求包体    ngx_int_t rc = ngx_http_discard_request_body(r);    if (rc != NGX_OK)    {        return rc;    }    // 构造并发送响应    // 设置返回的 Content-Type    ngx_str_t type = ngx_string("text/plain");    // 返回包体的内容    ngx_str_t response = ngx_string("Hello World!");    // 设置返回的状态码    r->headers_out.status = NGX_HTTP_OK;    // 响应包具有包体内容,需要设置 Content-Length 长度    r->headers_out.content_length_n = response.len;    // 设置 Content-Type    r->headers_out.content_type = type;    // 发送 HTTP 头部    rc = ngx_http_send_header(r);    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)    {        return rc;    }    // 从 Nginx 的内存池中分配一块内存    // 构造 ngx_buf_t 结构体准备发送包体    ngx_buf_t                 *b;    b = ngx_create_temp_buf(r->pool, response.len);    if (b == NULL)    {        return NGX_HTTP_INTERNAL_SERVER_ERROR;    }    // 将 Hellow World 复制到 ngx_buf_t 指向的内存中    ngx_memcpy(b->pos, response.data, response.len);    // 设置 b->last 指针    b->last = b->pos   response.len;    // 声明这是最后一块缓冲区    b->last_buf = 1;    // 构造发送时的 ngx_chain_t 结构体    ngx_chain_tout;    // 赋值 ngx_buf_t    out.buf = b;    // 设置 next 为 NULL    out.next = NULL;    // ngx_http_output_filter 方法向客户端发送 HTTP 响应包体    return ngx_http_output_filter(r, &out);}

【2.2.2】发送文件

// 该 HTTP 模块接入 Nginx 的方式// 1. 不希望该模块对整个 HTTP 请求有效// 2. 在 nginx.conf 文件中的 http{}、server{}、location{} 块内定义 mytest 配置项//// 在 HTTP 框架定义的 NGX_HTTP_CONTENT_PHASE 阶段开始处理请求#include <ngx_config.h>#include <ngx_core.h>#include <ngx_http.h>static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r);// commands数组用于定义模块的配置文件参数,每一个数组元素都是ngx_command_t类型// 数组以ngx_null_command结尾// #define ngx_null_command  { ngx_null_string, 0, NULL, 0, 0, NULL }// 空指令,用于在指令数组的最后当做哨兵,结束数组,避免指定长度,类似NULL的作用static ngx_command_t  ngx_http_mytest_commands[] ={    // 定义mytest配置项的处理,此处指定mytest配置项由ngx_http_mytest函数处理    {        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_command_t 结构体中的 set 成员        // 当在某个配置块中出现 mytest 配置项时,nginx 将会调用 ngx_http_mytest 方法        ngx_http_mytest,        NGX_HTTP_LOC_CONF_OFFSET,        0,        NULL    },    ngx_null_command};// 定义 HTTP 框架各阶段的回调方法// 若HTTP框架初始化时无需完成特定的工作则可将回调置为NULLstatic 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 模块// 回调方法,init_module、init_process、exit_process、exit_master 由 Nginx 框架代码调用,与 HTTP 模块无关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};static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){    ngx_http_core_loc_conf_t  *clcf;    // 首先找到 mytest 配置项所属的配置块,clcf 可以是 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 方法处理该请求    //    // 请求处理函数的原型,见 src/http/ngx_http_request.h    // typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);    clcf->handler = ngx_http_mytest_handler;    return NGX_CONF_OK;}// ngx_http_request_t中保存了请求的信息static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r){    // 判断 HTTP 头信息中的方法是否支持    if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)))    {        return NGX_HTTP_NOT_ALLOWED;    }    // 丢弃 HTTP 请求体    ngx_int_t rc = ngx_http_discard_request_body(r);    if (rc != NGX_OK)    {        return rc;    }    // 内存池中分配ngx_buf_t数据体    ngx_buf_t *b;    b = ngx_palloc(r->pool, sizeof(ngx_buf_t));    // 设置文件名    u_char* filename = (u_char*)"/tmp/intro.html";    // 将in_file标志位置1,表示ngx_buf_t发送的是文件而不是内存缓冲数据    // ngx_http_output_filter调用后,若检测到in_file标志为1,则从ngx_buf_t缓冲区中的file成员处获取实际的文件    b->in_file = 1;    // 分配内存    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));    // 打开文件    b->file->fd = ngx_open_file(filename, NGX_FILE_RDONLY | NGX_FILE_NONBLOCK, NGX_FILE_OPEN, 0);    // 配置日志指针    b->file->log = r->connection->log;    // 记录文件名称    b->file->name.data = filename;    // 文件名称长度    b->file->name.len = sizeof(filename) - 1;    if (b->file->fd <= 0)    {        return NGX_HTTP_NOT_FOUND;    }    // 允许 range 协议    r->allow_ranges = 1;    // 获取文件信息    if (ngx_file_info(filename, &b->file->info) == NGX_FILE_ERROR)    {        return NGX_HTTP_INTERNAL_SERVER_ERROR;    }    // 设置文件数据起始偏移量    b->file_pos = 0;    // 设置文件数据结束偏移量    b->file_last = b->file->info.st_size;    // 内存池清理添加    ngx_pool_cleanup_t* cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_pool_cleanup_file_t));    if (cln == NULL)    {        return NGX_ERROR;    }    // 配置内存池清理信息    cln->handler = ngx_pool_cleanup_file;    ngx_pool_cleanup_file_t  *clnf = cln->data;    clnf->fd = b->file->fd;    clnf->name = b->file->name.data;    clnf->log = r->pool->log;    // 构造响应    // 设置返回的 Content-Type    ngx_str_t type = ngx_string("text/plain");    // 设置返回的状态码    r->headers_out.status = NGX_HTTP_OK;    // 响应包具有包体内容,需要设置 Content-Length 长度    r->headers_out.content_length_n = b->file->info.st_size;    // 设置 Content-Type    r->headers_out.content_type = type;    // 发送 HTTP 头部    rc = ngx_http_send_header(r);    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)    {        return rc;    }    // 构造发送时的 ngx_chain_t 结构体    ngx_chain_tout;    // 赋值 ngx_buf_t    out.buf = b;    // 设置 next 为 NULL    out.next = NULL;    // ngx_http_output_filter 方法向客户端发送 HTTP 响应包体    return ngx_http_output_filter(r, &out);}

【2.3】Nginx 模块代码配置与调式

【2.3.1】添加模块相关配置信息

...http {    ...    # 模块开发对应的配置    location /test {        mytest;    }    ...}...

【2.3.2】运行 Nginx 并附着进程调式

启动 Nginx 并查看 Worker 进程 ID

VSCode 配置

{    // Use IntelliSense to learn about possible attributes.    // Hover to view descriptions of existing attributes.    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387    "version": "0.2.0",    "configurations": [        {             "name": "nginx_worker_debug",            "type": "cppdbg",            "request": "attach",            "program": "/home/shallysun/code_dev/SourceCode/nginx_1_19/nginx/bin/sbin/nginx",            "processId": "45472",            "MIMode": "gdb",            "setupCommands": [                {                    "description": "Enable pretty-printing for gdb",                    "text": "-enable-pretty-printing",                    "ignoreFailures": true                }            ]        }    ]}

调试效果

【3】Nginx 模块开发典型数据结构解析

【3.1】ngx_command_s / ngx_command_t

// Nginx 在解析配置文件中的一个配置项时,遍历所有模块,对于每一个模块遍历 commands 数组,// 在数组中检查到 ngx_null_command 时会停止使用当前模块解析该配置项;//// 指令结构体,用于定义nginx指令// ngx_command_t (ngx_core.h)struct ngx_command_s {    // 指令的名字    ngx_str_t             name;    // 指令的类型,是NGX_CONF_XXX的组合,决定指令出现的位置、参数数量、类型等    // NGX_HTTP_MAIN_CONF/NGX_HTTP_SRV_CONF/NGX_HTTP_LOC_CONF    ngx_uint_t            type;    // 出现了 name 中指定的配置项后,会调用 set 方法处理配置项参数    // 指令解析函数,是函数指针    // 预设有ngx_conf_set_flag_slot等,见本文件    // cf:解析的环境结构体,重要的是cf->args,是指令字符串数组    // cmd:该指令的结构体    // conf当前的配置结构体,需转型后才能使用    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);    // 在配置文件中的偏移量    // 专门给http/stream模块使用,决定存储在main/srv/loc的哪个层次    // NGX_HTTP_MAIN_CONF_OFFSET/NGX_HTTP_SRV_CONF_OFFSET/NGX_HTTP_LOC_CONF_OFFSET    // NGX_STREAM_MAIN_CONF_OFFSET    // 其他类型的模块不使用,直接为0    ngx_uint_t            conf;    // 变量在conf结构体里的偏移量,可用offsetof得到    // 主要用于nginx内置的命令解析函数,自己写命令解析函数可以置为0    //    // 当前配置项在整个存储配置项的结构体中的偏移位置    ngx_uint_t            offset;    // 解析后处理的数据    // 配置项读取后的处理方法,必须是 ngx_conf_post_t 结构的指针    void                 *post;};

【3.2】ngx_http_module_t

// http 框架在读取、重载配置文件时定义了由 ngx_http_module_t 接口描述的 8 个阶段// http 框架在启动过程中会在每个阶段调用 ngx_http_module_t 中的相应方法// http模块的函数表,在配置解析阶段被框架调用typedef struct {    // 解析配置文件之前调用    // ngx_http_block里,创建配置结构体后,开始解析之前调用    // 常用于添加变量定义    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);    // 完成配置文件解析后调用    // ngx_http_block里,解析、合并完配置后调用    // 常用于初始化模块的phases handler    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);    // 当需要创建数据结构用于存储 main 级别的全局配置项时,可以通过 create_main_conf 回调创建存储全局配置项的结构体    // 创建模块的main配置,只有一个,在http main域    void       *(*create_main_conf)(ngx_conf_t *cf);    // 初始化模块的main配置,只有一个,在http main域    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);    // 当需要创建数据结构用于存储 srv 级别的配置项时,可以通过 create_srv_conf 回调创建存储 srv 级别配置项的结构体    // 创建、合并模块的srv配置    void       *(*create_srv_conf)(ngx_conf_t *cf);    // 用于合并 main 级别和 srv 级别下的同名配置项    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);    // 当需要创建数据结构用于存储 loc 级别的配置项时,可以通过 create_loc_conf 回调创建存储 loc 级别配置项的结构体    // 创建、合并模块的location配置    void       *(*create_loc_conf)(ngx_conf_t *cf);    // 用于合并 srv 级别和 loc 级别下的同名配置项    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);} ngx_http_module_t;

【3.3】ngx_module_s / ngx_module_t

// 重新定义了填充宏,加入了签名字符串// 早期(1.9.11之前)的定义是// #define NGX_MODULE_V1          0, 0, 0, 0, 0, 0, 1// 注意前两个字段改成了unset(-1)而不是0,表示序号未初始化#define NGX_MODULE_V1                                                             NGX_MODULE_UNSET_INDEX, NGX_MODULE_UNSET_INDEX,                               NULL, 0, 0, nginx_version, NGX_MODULE_SIGNATURE// 填充宏,填充ngx_module_t的最后8个字段,设置为空指针// 1.10没有变化#define NGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0, 0, 0// 重要的数据结构,定义nginx模块// 重要的模块ngx_core_module/ngx_event_module/ngx_http_module// 1.9.11后有变化,改到了ngx_module.h,可以定义动态模块,使用了spare0等字段struct ngx_module_s {    // 成员变量 ctx_index index spare0 spare1 version 通常使用宏NGX_MODULE_V1填充    // 每类(http/event)模块各自的index,表示当前模块在一类模块中的序号    // 由管理此类模块的 Nginx 核心模块设置    // 可用于表示优先级以及各个模块的位置    // 初始化为-1    ngx_uint_t            ctx_index;    // 在ngx_modules数组里的唯一索引,main()里赋值    // 使用计数器变量ngx_max_module设置    // 表示当前模块在ngx_modules数组中的序号,即当前模块在所有模块中的序号    ngx_uint_t            index;    // 1.10,模块的名字,标识字符串,默认是空指针    // 由脚本生成ngx_module_names数组,然后在ngx_preinit_modules里填充    // 动态模块在ngx_load_module里设置名字    char                 *name;    // 两个保留字段,1.9之前有4个    ngx_uint_t            spare0;    ngx_uint_t            spare1;    // nginx.h:#define nginx_version      1010000    // 模块的版本,默认为1    ngx_uint_t            version;    // 模块的二进制兼容性签名,即NGX_MODULE_SIGNATURE    const char           *signature;    // 模块不同含义不同,通常是函数指针表,是在配置解析的某个阶段调用的函数    // core模块的ctx    //typedef struct {    //    ngx_str_t             name;    //    void               *(*create_conf)(ngx_cycle_t *cycle);    //    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);    //} ngx_core_module_t;    //    //用于指向一类模块的上下文结构体,指向特定类型模块的公共接口    void                 *ctx;    // 模块支持的指令,数组形式,最后用空对象表示结束,处理 nginx.conf 中的配置项    ngx_command_t        *commands;    // 模块的类型标识,相当于RTTI,如CORE/HTTP/STRM/MAIL等    // Nginx 官方取值为 NGX_HTTP_MODULE NGX_CORE_MODULE NGX_CONF_MODULE NGX_EVENT_MODULE NGX_MAIL_MODULE    ngx_uint_t            type;    // 以下7个函数会在进程(Nginx)的启动或结束阶段被调用    // 若不需要Nginx在某个阶段调用相应的回调函数则置为NULL    // init_master目前nginx不会调用,当 master 进程启动时回调    ngx_int_t           (*init_master)(ngx_log_t *log);    // 在ngx_init_cycle里被调用    // 在master进程里,fork出worker子进程之前    // 做一些基本的初始化工作,数据会被子进程复制    //    // 初始化所有模块时回调    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);    // 在ngx_single_process_cycle/ngx_worker_process_init里调用    // 在worker进程进入工作循环之前被调用    // 初始化每个子进程自己专用的数据    //    // 在正常服务前被调用    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);    // init_thread目前nginx不会调用    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);    // exit_thread目前nginx不会调用    void                (*exit_thread)(ngx_cycle_t *cycle);    // 在ngx_worker_process_exit调用,在服务停止前调用    void                (*exit_process)(ngx_cycle_t *cycle);    // 在ngx_master_process_exit(os/unix/ngx_process_cycle.c)里调用    // 在master进程退出前调用    void                (*exit_master)(ngx_cycle_t *cycle);    // 下面8个成员通常用用NGX_MODULE_V1_PADDING填充    // 暂时无任何用处,保留字段    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;};

参考致谢
本博客为博主的学习实践总结,并参考了众多博主的博文,在此表示感谢,博主若有不足之处,请批评指正。

【1】深入理解 Nginx 模块开发与架构解析

【2】Nginx模块开发入门

来源:https://www.icode9.com/content-3-877251.html
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
手把手教你开发Nginx模块
nginx源码分析
Nginx+Naxsi部署专业级web应用防火墙
nginx源代码分析_浪湾(langwan) 一个思想跳跃的程序员
解剖Nginx·模块开发篇(5)解读内置非默认模块 ngx
handler模块(100%)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服