【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=模块源代码文件目录 make make 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框架初始化时无需完成特定的工作则可将回调置为NULL 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 模块 // 回调方法,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框架初始化时无需完成特定的工作则可将回调置为NULL 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 模块 // 回调方法,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./content-3-877251.html |
|