本章,我们实现一个nginx的访问统计模块,用来统计资源被被某个web访问的次数。
创建访问统计模块
Nginx 提供了很简单的方法来帮助把自己的模块编译到 nginx,方法是把名为 config 的文件放在与自定义模块代码的同一目录下。
我们在 /usr 目录下创建一个 ngx_http_location_count_module 文件夹,文件夹里面创建一个 ngx_http_location_count_module.c 文件和 config 配置文件,目录结构如下:
ngx_http_location_count_module/ ├── config └── ngx_http_location_count_module.c
config 配置文件
config 配置文件需要3个参数:
- ngx_addon_name :一般设置为模块名,执行 configure 时调用
- HTTP_MODULES:保存所有模块内容的变量,相当于源码中的 ngx_modules[] 数组。
- NGX_ADDON_SRCS:自定义模块源码的路径,配置命令中会设置该值
内容如下:
ngx_addon_name=ngx_http_location_count_module HTTP_MODULES="$HTTP_MODULES ngx_http_location_count_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_location_count_module.c"
ngx_http_location_count_module.c 文件
我们先简单定义下访问统计模块的关键信息,然后简单的测试该模块能够被成功加入ngxin。ngx_http_location_count_module.c 的内容如下:
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> static void* ngx_http_location_count_create_loc_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char* ngx_http_location_count_create_cmd_set(ngx_conf_t *cf); static ngx_command_t ngx_http_location_count_cmd[] = { { ngx_string("count"), NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, ngx_http_location_count_create_cmd_set, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_location_count_ctx = { NULL, //preconfigure NULL, //postconfigure NULL, //create main NULL, //init main NULL, //create server NULL, //merge server ngx_http_location_count_create_loc_conf, //create loc NULL }; //ngx_http_location_count_module ngx_module_t ngx_http_location_count_module = { NGX_MODULE_V1, &ngx_http_location_count_ctx, ngx_http_location_count_cmd, NGX_HTTP_MODULE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING }; static void* ngx_http_location_count_create_loc_conf(ngx_conf_t *cf) { } static char* ngx_http_location_count_create_cmd_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { }
将统计模块加入nginx
接下来,我们将编译nginx所依赖的几个公共库放到指定位置。在这里,我在 /usr 目录下放置以下几个开源软件:openssl-1.1.0、pcre-8.41、zlib-1.2.11
然后进入到ngxin源码目录 /usr/nginx-1.14.1 下,执行如下命令进行nginx配置命令,命令中会指定统计模块源码的路径:
[root@localhost nginx]# cd /usr/nginx-1.14.1/ [root@localhost nginx-1.14.1]# ./configure --prefix=/usr/local/nginx --with-http_realip_module --with-http_addition_module --with-http_gzip_static_module --with-http_secure_link_module --with-http_stub_status_module --with-stream --with-pcre=/usr/pcre-8.41 --with-zlib=/usr/zlib-1.2.11 --with-openssl=/usr/openssl-1.1.0 --add-module=/usr/ngx_http_location_count_module
配置成功之后,会看下统计模块 ngx_http_location_count_module 已被成功加入到nginx:
我们查看 /usr/nginx-1.14.1/objs/ngx_modules.c,搜索 ngx_http_location_count_module,也可以看到改模块被加入到代码中了:
接着,我们执行make编译nginx源码,将我们的过滤模块也一起编译:
[root@localhost nginx-1.14.1]# cd /usr/nginx-1.14.1/ [root@localhost nginx-1.14.1]# make
访问统计模块的设计
Nginx 的模块化是将各个模块串成一个链表,在每次请求到来的时候依次遍历链表上的所有模块,调用所有的处理函数。比如 upstream模块、事件模块、HTTP 模块等等。其中 HTTP 模块是实现了 HTTP 协议。
模块的定义
在编写 HTTP 模块之前,首先应该考虑的一点是自己的模块应该介入 HTTP 模块的11个阶段中的哪一个阶段。由于我们要编写的是页面访问次数的统计,意味着我们在 HTTP 请求寻找到相应的 location 配置之后就可以介入,因为我们只需要知道请求的 IP 地址。
下面是访问统计模块的定义:
ngx_module_t ngx_http_location_count_module = { //宏定义,初始化模块数据结构中的某些变量的值 NGX_MODULE_V1, //模块的上下文,来使得不同模块有自己的特定行为。 &ngx_http_location_count_ctx, //定义模块配置项,来处理 nginx.conf 中相应内容 ngx_http_location_count_cmd, //定义模块配置项,来处理 nginx.conf 中相应内容。 NGX_HTTP_MODULE, //剩下的内容包括初始化和销毁的函数回调,我们都不需要处理,所以为 NULL NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING };
模块初始化的定义
HTTP模块的初始化由 ngx_http_location_count_module 的成员 ngx_http_location_count_ctx 来完成。该成员是一个 ngx_http_module_t 类型的结构体变量,ngx_http_module_t 的原型如下:
typedef struct { // 解析配置文件前 ngx_int_t (*preconfiguration)(ngx_conf_t *cf); // 完成解析配置文件后 ngx_int_t (*postconfiguration)(ngx_conf_t *cf); // 创建存储main级别的配置项时的结构体 void *(*create_main_conf)(ngx_conf_t *cf); // 初始化main级别的配置项 char *(*init_main_conf)(ngx_conf_t *cf, void *conf); // 创建存储srv级别的配置项时的结构体 void *(*create_srv_conf)(ngx_conf_t *cf); // 合并main级别和srv级别的同名配置项 char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); // 创建存储loc级别的配置项时的结构体 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;
可以看出该结构体由8个回调函数组成,实际的执行顺序分别是:
create_main_conf、create_srv_conf、create_loc_conf、preconfiguration、init_main_conf、merge_srv_conf、merge_loc_conf、postconfiguration。
由于本模块只使用到 create_loc_conf 回调,也就是在创建 location 配置项前需要初始化,因此 ngx_http_location_count_ctx 的初始化如下:
static ngx_http_module_t ngx_http_location_count_ctx = { NULL, //preconfigure NULL, //postconfigure NULL, //create main NULL, //init main NULL, //create server NULL, //merge server ngx_http_location_count_create_loc_conf, //create loc NULL };
ngx_http_location_count_create_loc_conf() 函数的定义如下:
static void* ngx_http_location_count_create_loc_conf(ngx_conf_t *cf) { //初始化计自定义模块的全局配置conf ngx_http_location_count_conf_t *conf = ngx_palloc(cf->pool, sizeof(ngx_http_location_count_conf_t)); if (NULL == conf) { return NULL; } //打印调试信息 ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, "ngx_http_location_count_create_loc_conf"); return conf; }
可见,该函数只是对结构体 ngx_http_location_count_conf_t 进行内存分配,作用很简单。
下面是跟 ngx_http_location_count_create_loc_conf() 函数有关的结构体解释。
typedef struct { ngx_rbtree_t rbtree; //保存访问者的IP和访问次数 ngx_rbtree_node_t sentinel; //红黑树叶子节点 } ngx_http_location_count_shm_t; typedef struct { ngx_slab_pool_t *sbpool; //共享内存池对象 ssize_t shmsize; //分配的共享内存的大小 ngx_http_location_count_shm_t *shm; //自定义模块的共享内存 //ngx_uint_t interval; } ngx_http_location_count_conf_t;
这样定义结构体的原因是:为统计模块申请一块共享内存,然后在共享内存里保存一颗红黑树。红黑树保存所有的访问记录,用IP作为key,访问次数作为value。
模块配置
真正实现模块功能的地方就是模块的配置,对于本模块来说,就是模块成员 ngx_http_location_count_cmd ,其定义如下:
static ngx_command_t ngx_http_location_count_cmd[] = { { ngx_string("count"), //表示该配置处于 location 并且无参数 NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, //模块配置的回调函数 ngx_http_location_count_create_cmd_set, //模块配置的位置处于 http 中的 location,后面的都是填充 NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command };
其中
配置定义完之后,我们就知道了如何在 nginx.conf 中配置本模块,内容如下:
http { ... server { ... location /test { count; } ... } ... }
该配置表明,当URL请求为 /test 时,会调用本模块的 handler。
static char* ngx_http_location_count_create_cmd_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_shm_zone_t *shm_zone; ngx_str_t name = ngx_string("http_location_count_slab"); ngx_http_core_loc_conf_t *corecf; ngx_http_location_count_conf_t *lconf = (ngx_http_location_count_conf_t*)conf; lconf->shmsize = 1024 * 1024; //分配共享内存空间,获取 ngx_shm_zone_t shm_zone = ngx_shared_memory_add(cf, &name, lconf->shmsize, &ngx_http_location_count_module); if (shm_zone == NULL) { return NGX_CONF_ERROR; } shm_zone->init = ngx_http_location_count_shm_zone_init; shm_zone->data = lconf; //获取HTTP模块的loc级别的配置 corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); //注册 handler corecf->handler = ngx_http_location_count_handler; ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, "ngx_http_location_count_create_cmd_conf"); return NGX_CONF_OK; }
该函数中,需要注意以下2点:
- 为模块分配共享内存空间
- 注册handler,在handler中实现业务逻辑
为模块分配共享内存空间
为什么要分配共享内存呢?由于ngxin是多进程的,多个访问请求可能会被多个进程处理。因此需要有一个公共区域来存储访问量,因此就会用到 Nginx 提供的共享内存。
首先使用 Nginx 提供的接口
static ngx_int_t ngx_http_location_count_shm_zone_init(ngx_shm_zone_t *zone, void *data) { ngx_http_location_count_conf_t *conf; ngx_http_location_count_conf_t *oconf = data; conf = (ngx_http_location_count_conf_t*)zone->data; // 若是 nginx -s reload 情况,则不需重新分配内存 if (oconf) { conf->shm = oconf->shm; conf->sbpool = oconf->sbpool; return NGX_OK; } //获取共享内存池的地址 conf->sbpool = (ngx_slab_pool_t*)zone->shm.addr; //分配共享内存,用来保存红黑树 conf->shm = ngx_slab_alloc(conf->sbpool, sizeof(ngx_http_location_count_shm_t)); if (conf->shm == NULL) { return NGX_ERROR; } conf->sbpool->data = conf->shm; //初始化红黑树对象,使用自定义的节点插入函数 ngx_rbtree_init(&conf->shm->rbtree, &conf->shm->sentinel, ngx_http_location_count_rbtree_insert_value); return NGX_OK; }
初始化红黑树
初始化红黑树中我们需要注册一个回调函数 ngx_http_location_count_rbtree_insert_value(),为什么要自定义插入函数?因为默认的红黑树插入方法是以 IP 地址的哈希值为 红黑树节点的key,我们需要直接以 IP 作为 key,访问次数为 value。其定义如下:
//自定义红黑树节点插入函数. node为待插入节点 static void ngx_http_location_count_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) { ngx_rbtree_node_t **p; for (;;) { if (node->key < temp->key) { p = &temp->left; } else if (node->key > temp->key) { p = &temp->right; } else { return; //节点已存在 } if (*p == sentinel) { break; //为找到该节点 } temp = *p; } *p = node; node->parent = temp; node->left = sentinel; node->right = sentinel; ngx_rbt_red(node); }
业务逻辑
整个模块的大体框架前面已经搭建好了,剩下的就是业务逻辑了,也就是实现访问统计。
注册 handler 是为了在每次请求到来之后,都能够执行该 handler,本模块的核心功能都在 handler 中。handler 定义如下:
//业务处理:统计访问次数并返回结果到客户端 static ngx_int_t ngx_http_location_count_handler(ngx_http_request_t *r) { u_char html[1024] = {0}; int len = sizeof(html); ngx_rbtree_key_t key = 0; struct sockaddr_in *client_addr = (struct sockaddr_in *)r->connection->sockaddr; key = (ngx_rbtree_key_t)client_addr->sin_addr.s_addr; ngx_http_location_count_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_location_count_module); // 记录访问量.需要先对共享内存加锁,防止多进程错误写入 ngx_shmtx_lock(&conf->sbpool->mutex); ngx_http_location_count_rbtree_lookup(r, conf, key); ngx_shmtx_unlock(&conf->sbpool->mutex); // 构造 HTML ngx_encode_http_page_rb(conf, (char*)html); // HTTP header r->headers_out.status = 200; ngx_str_set(&r->headers_out.content_type, "text/html"); ngx_http_send_header(r); // HTTP body ngx_buf_t *b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); ngx_chain_t out; out.buf = b; out.next = NULL; b->pos = html; b->last = html + len; b->memory = 1; b->last_buf = 1; return ngx_http_output_filter(r, &out); }
业务逻辑分为3个小部分,分别是:
- 统计访问次数
- 构造 HTML 页面
- 发送 HTML 响应
统计访问次数
首先通过 ngx_http_location_count_rbtree_lookup() 函数实现统计访问次数,该函数先查找红黑树中是否有该请求 IP 地址的记录,如果没有,则通过 ngx_rbtree_insert() 将该记录作为新的结点插入到红黑树中;否则在原记录的基础上加1。
需要注意的一点是,该共享内存是临界资源,存在竞争的情况,因此在内存分配的时候需要上锁,通过使用 Nginx 提供的函数 ngx_shmtx_lock 、ngx_shmtx_unlock 。
//自定义红黑树查找函数。key为待查找节点的key static ngx_uint_t ngx_http_location_count_rbtree_lookup(ngx_http_request_t *r, ngx_http_location_count_conf_t *conf, ngx_uint_t key) { ngx_rbtree_node_t *node, *sentinel; node = conf->shm->rbtree.root; sentinel = conf->shm->rbtree.sentinel; while (node != sentinel) { if (key < node->key) { node = node->left; } else if (key > node->key) { node = node->right; } else { // 找到记录 node->data++; return NGX_OK; } } // 分配共享内存 node = ngx_slab_alloc_locked(conf->sbpool, sizeof(ngx_rbtree_node_t)); if (node == NULL) { return NGX_ERROR; } // 插入结点 node->key = key; node->data = 1; //会调用ngx_http_location_count_rbtree_insert_value() ngx_rbtree_insert(&conf->shm->rbtree, node); return NGX_OK; }
构造 HTML 页面
构造页面比较简单,取出的红黑树中的数据构造 HTML 即可:
//构造ngx返回客户端的html页面 static int ngx_encode_http_page_rb(ngx_http_location_count_conf_t *conf, char *html) { sprintf(html, "<h1>Http_Location_Count</h1>"); strcat(html, "<h2>"); // 从最小值开始 ngx_rbtree_node_t *node = ngx_rbtree_min(conf->shm->rbtree.root, conf->shm->rbtree.sentinel); // 遍历红黑树 do { char str[INET_ADDRSTRLEN] = {0}; char buffer[128] = {0}; sprintf(buffer, "req from %s, count %d<br/>", inet_ntop(AF_INET, &node->key, str, sizeof(str)), node->data); strcat(html, buffer); node = ngx_rbtree_next(&conf->shm->rbtree, node); } while(node); strcat(html, "</h2>"); return NGX_OK; }
这里通过 ngx_rbtree_min() 函数取到最小值,然后依次遍历整个红黑树,生成相应的 HTML,其格式大致如下:
<h1>Http_Location_Count</h1> <h2> ... req from 0.0.0.0, count 1 req from 10.10.10.10, count 1 ... </h2>
发送 HTML 响应
最后发送 HTTP 响应,先构造 HTTP 响应头,只需设置状态码 200,类型 text/html 即可。
其次构造 HTTP 响应体,Nginx 中 HTTP Body 是由 ngx_buf_t 结构来表示,需要先分配一个 ngx_buf_t b ,该数据结构用来处理大数据,b->pos 指向 html 首指针,b->last 指向 html 尾指针,表面希望 Nginx 处理全部 html 内容,b->memory 置 1,表示这段内存只读,b->last_buf 置 1 表示这是最后一块缓冲区。然后定义 ngx_chain_t 将 b 作为链表结点,通过调用 ngx_http_output_filter 来将其作为 output 过滤器串到过滤器的链表上,Nginx 会发送包体出去。
访问统计模块的运行
完整代码
ngx_http_location_count_module.c
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> static void* ngx_http_location_count_create_loc_conf(ngx_conf_t *cf); static char* ngx_http_location_count_create_cmd_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_location_count_handler(ngx_http_request_t *r); static void ngx_http_location_count_rbtree_insert_value(ngx_rbtree_node_t *tmp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); static ngx_int_t ngx_http_location_count_shm_zone_init(ngx_shm_zone_t *zone, void *data); typedef struct { ngx_rbtree_t rbtree; //保存访问者的IP和访问次数 ngx_rbtree_node_t sentinel; //红黑树叶子节点 } ngx_http_location_count_shm_t; typedef struct { ngx_slab_pool_t *sbpool; //共享内存池对象 ssize_t shmsize; //分配的共享内存的大小 ngx_http_location_count_shm_t *shm; //自定义模块的共享内存 //ngx_uint_t interval; } ngx_http_location_count_conf_t; static ngx_command_t ngx_http_location_count_cmd[] = { { ngx_string("count"), NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, ngx_http_location_count_create_cmd_set, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_location_count_ctx = { NULL, //preconfigure NULL, //postconfigure NULL, //create main NULL, //init main NULL, //create server NULL, //merge server ngx_http_location_count_create_loc_conf, //create loc NULL }; //ngx_http_location_count_module ngx_module_t ngx_http_location_count_module = { //宏定义,初始化模块数据结构中的某些变量的值 NGX_MODULE_V1, //模块的上下文,来使得不同模块有自己的特定行为。 &ngx_http_location_count_ctx, //定义模块配置项,来处理 nginx.conf 中相应内容 ngx_http_location_count_cmd, //定义模块配置项,来处理 nginx.conf 中相应内容。 NGX_HTTP_MODULE, //剩下的内容包括初始化和销毁的函数回调,我们都不需要处理,所以为 NULL NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING }; static int ngx_encode_http_page_rb(ngx_http_location_count_conf_t *conf, char *html); static void* ngx_http_location_count_create_loc_conf(ngx_conf_t *cf) { //初始化计自定义模块的全局配置conf ngx_http_location_count_conf_t *conf = ngx_palloc(cf->pool, sizeof(ngx_http_location_count_conf_t)); if (NULL == conf) { return NULL; } //打印调试信息 ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, "ngx_http_location_count_create_loc_conf"); return conf; } static char* ngx_http_location_count_create_cmd_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_shm_zone_t *shm_zone; ngx_str_t name = ngx_string("http_location_count_slab"); ngx_http_core_loc_conf_t *corecf; ngx_http_location_count_conf_t *lconf = (ngx_http_location_count_conf_t*)conf; lconf->shmsize = 1024 * 1024; //获取 ngx_shm_zone_t shm_zone = ngx_shared_memory_add(cf, &name, lconf->shmsize, &ngx_http_location_count_module); if (shm_zone == NULL) { return NGX_CONF_ERROR; } shm_zone->init = ngx_http_location_count_shm_zone_init; shm_zone->data = lconf; //获取HTTP模块的loc级别的配置 corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); //注册 handler corecf->handler = ngx_http_location_count_handler; ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, "ngx_http_location_count_create_cmd_conf"); return NGX_CONF_OK; } static ngx_int_t ngx_http_location_count_shm_zone_init(ngx_shm_zone_t *zone, void *data) { ngx_http_location_count_conf_t *conf; ngx_http_location_count_conf_t *oconf = data; conf = (ngx_http_location_count_conf_t*)zone->data; // 处理 nginx -s reload 情况 if (oconf) { conf->shm = oconf->shm; conf->sbpool = oconf->sbpool; return NGX_OK; } //分配共享内存 conf->sbpool = (ngx_slab_pool_t*)zone->shm.addr; conf->shm = ngx_slab_alloc(conf->sbpool, sizeof(ngx_http_location_count_shm_t)); if (conf->shm == NULL) { return NGX_ERROR; } conf->sbpool->data = conf->shm; //初始化红黑树对象,使用自定义的节点插入函数 ngx_rbtree_init(&conf->shm->rbtree, &conf->shm->sentinel, ngx_http_location_count_rbtree_insert_value); return NGX_OK; } //自定义红黑树节点插入函数. node为待插入节点 static void ngx_http_location_count_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) { ngx_rbtree_node_t **p; for (;;) { if (node->key < temp->key) { p = &temp->left; } else if (node->key > temp->key) { p = &temp->right; } else { return; //节点已存在 } if (*p == sentinel) { break; //为找到该节点 } temp = *p; } *p = node; node->parent = temp; node->left = sentinel; node->right = sentinel; ngx_rbt_red(node); } //自定义红黑树查找函数。key为待查找节点的key static ngx_uint_t ngx_http_location_count_rbtree_lookup(ngx_http_request_t *r, ngx_http_location_count_conf_t *conf, ngx_uint_t key) { ngx_rbtree_node_t *node, *sentinel; node = conf->shm->rbtree.root; sentinel = conf->shm->rbtree.sentinel; while (node != sentinel) { if (key < node->key) { node = node->left; } else if (key > node->key) { node = node->right; } else { // 找到记录 node->data++; return NGX_OK; } } // 分配共享内存 node = ngx_slab_alloc_locked(conf->sbpool, sizeof(ngx_rbtree_node_t)); if (node == NULL) { return NGX_ERROR; } // 插入结点 node->key = key; node->data = 1; //会调用ngx_http_location_count_rbtree_insert_value() ngx_rbtree_insert(&conf->shm->rbtree, node); return NGX_OK; } //业务处理:统计访问次数并返回结果到客户端 static ngx_int_t ngx_http_location_count_handler(ngx_http_request_t *r) { u_char html[1024] = {0}; int len = sizeof(html); ngx_rbtree_key_t key = 0; struct sockaddr_in *client_addr = (struct sockaddr_in *)r->connection->sockaddr; key = (ngx_rbtree_key_t)client_addr->sin_addr.s_addr; ngx_http_location_count_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_location_count_module); // 记录访问量.需要先对共享内存加锁,防止多进程错误写入 ngx_shmtx_lock(&conf->sbpool->mutex); ngx_http_location_count_rbtree_lookup(r, conf, key); ngx_shmtx_unlock(&conf->sbpool->mutex); // 构造 HTML ngx_encode_http_page_rb(conf, (char*)html); // HTTP header r->headers_out.status = 200; ngx_str_set(&r->headers_out.content_type, "text/html"); ngx_http_send_header(r); // HTTP body ngx_buf_t *b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); ngx_chain_t out; out.buf = b; out.next = NULL; b->pos = html; b->last = html + len; b->memory = 1; b->last_buf = 1; return ngx_http_output_filter(r, &out); } //构造ngx返回客户端的html页面 static int ngx_encode_http_page_rb(ngx_http_location_count_conf_t *conf, char *html) { sprintf(html, "<h1>Http_Location_Count</h1>"); strcat(html, "<h2>"); // 从最小值开始 ngx_rbtree_node_t *node = ngx_rbtree_min(conf->shm->rbtree.root, conf->shm->rbtree.sentinel); // 遍历红黑树 do { char str[INET_ADDRSTRLEN] = {0}; char buffer[128] = {0}; sprintf(buffer, "req from %s, count %d<br/>", inet_ntop(AF_INET, &node->key, str, sizeof(str)), node->data); strcat(html, buffer); node = ngx_rbtree_next(&conf->shm->rbtree, node); } while(node); strcat(html, "</h2>"); return NGX_OK; }
执行模块
编译模块
[root@localhost nginx-1.14.1]# cd /usr/nginx-1.14.1/ [root@localhost nginx-1.14.1]# make && make install
启动nginx
在执行下面的启动命令前,确保nginx尚未启动。
[root@localhost nginx]# cd /usr/local/nginx/ [root@localhost nginx]# ./sbin/nginx -c ./conf/nginx.conf nginx: [emerg] ngx_http_location_count_create_loc_conf nginx: [emerg] ngx_http_location_count_create_loc_conf nginx: [emerg] ngx_http_location_count_create_loc_conf nginx: [emerg] ngx_http_location_count_create_loc_conf nginx: [emerg] ngx_http_location_count_create_loc_conf nginx: [emerg] ngx_http_location_count_create_cmd_conf nginx: [emerg] ngx_http_location_count_create_loc_conf nginx: [emerg] ngx_http_location_count_create_loc_conf nginx: [emerg] ngx_http_location_count_create_loc_conf [root@localhost nginx]#
执行结果
在浏览器中输入配置好的IP,并加上/test。效果如下:
参考文献:
一起写一个 Nginx 访问统计模块_nginx统计模块_良晨的博客-CSDN博客