• Nginx模块开发之http handler实现流量统计(入门篇)


    一、Nginx模块之http handler简介

    Nginx通过模块化的方式提供了丰富的功能扩展能力。其中,HTTP Handler是Nginx模块开发中非常重要的一个概念。HTTP Handler可以用来拦截、处理和操作传入的HTTP请求,在请求的生命周期中执行特定的逻辑。它可以用于实现各种功能,如流量统计、访问控制、缓存管理等。

    通过使用HTTP Handler,可以自定义和扩展Nginx的功能,根据具体需求进行灵活的定制。HTTP Handler可以通过Nginx模块的代码编写和配置来实现,无需修改或重新编译Nginx的核心代码。

    当nginx解析conf文件时,可以为cmd加上handler。
    ngx_handler_arch
    在本文中探讨如何使用Nginx模块开发来实现流量统计功能。从基础的HTTP Handler编写开始,逐步引导完成一个简单而功能强大的流量统计模块。

    让我们一起探索Nginx模块开发中的HTTP Handler,并为网站添加流量统计功能提供强大的基础!

    二、Nginx handler模块开发

    2.1、示例代码

    代码中在重点地方带有详细的注释。

    
    #include 
    #include 
    #include 
    
    #include 
    #include 
    
    /*
    * ip的访问次数存放在一个key-value数据结构里面,ip是key,value是统计的次数
    * 可用的数据结构:
    * hash
    * rbtree
    * 最简单的是数组
    */
    typedef struct {
    	int count;
    	struct in_addr addr;
    }ngx_pv_table;
    
    ngx_pv_table pv_table[256] = { 0 }; //这只适合局域网内存储,正在的数据结构最好用rbtree
    
    // 重新组织网页 (网页组包)
    void ngx_encode_http_page(char *html)
    {
    	sprintf(html, "

    Hello, NGX handler! I am FLY.

    "
    ); strcat(html, "

    "); int i = 0; for (i = 0; i < 256; i++) { if (pv_table[i].count != 0) { char str[INET_ADDRSTRLEN] = { 0 }; char buffer[128] = { 0 }; sprintf(buffer, "req from : %s, count: %d
    "
    , inet_ntop(AF_INET, &pv_table[i].addr, str, sizeof(str)), pv_table[i].count); strcat(html, buffer); } } strcat(html, "

    "
    ); } ngx_int_t ngx_http_count_handler(ngx_http_request_t *r) { // 这里做统计功能 // 获取ip地址 struct sockaddr_in *cliaddr = (struct sockaddr_in *)r->connection->sockaddr; // 地址和我们看到的是反着的,通过右移得到ip地址的末尾.符号后面那个位数 int idx = cliaddr->sin_addr.s_addr >> 24; pv_table[idx].count++; memcpy(&pv_table[idx].addr, &cliaddr->sin_addr, sizeof(cliaddr->sin_addr)); // 重新组织网页 u_char html[1024] = { 0 }; int len = sizeof(html); ngx_encode_http_page((char*)html); /* * 发送http响应 */ r->headers_out.status = 200; ngx_str_set(&r->headers_out.content_type, "text/html"); // 发送http 头 ngx_http_send_header(r); // 内存池拿出一个buffer的内存空间 ngx_buf_t *b = ngx_palloc(r->pool, sizeof(ngx_buf_t)); b->pos = html; b->last = html + len; b->memory = 1;//内存里操作 b->last_buf = 1;//最后内存块 // 缓冲链 ngx_chain_t out; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); } char *ngx_http_handler_count_set(ngx_conf_t *cf,ngx_command_t *cmd,void *conf) { ngx_http_core_loc_conf_t *ccf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); // 设置handler的入口函数 ccf->handler = ngx_http_count_handler; memset(pv_table, 0, sizeof(pv_table)); return NGX_OK; } // conf文件中的每一行都是一个指令指令 ngx_command_t ngx_http_handler_module_cmd[] = { { //命令名称,比如listen,定义了就可以在conf文件中使用,注意不能和其他的起冲突 ngx_string("count"), // 指示name命令放的位置在哪里以及可以带多少个参数,NGX_CONF_FLAGE表示开关标志 // predix on/off NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, // 命令解析,可以使用nginx内部的也可以自己实现 ngx_http_handler_count_set,//ngx_http_handler_set_slot, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL, }, ngx_null_command }; // 用来解析对应的conf文件 static ngx_http_module_t ngx_http_handler_module_ctx = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; // 模块定义 ngx_module_t ngx_http_handler_module = { NGX_MODULE_V1, &ngx_http_handler_module_ctx, ngx_http_handler_module_cmd, // http的ascii值,指示是什么模块 NGX_HTTP_MODULE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING // 填充 };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169

    2.2、编写config文件

    创建:

    touch config
    
    • 1

    内容:

    ngx_addon_name=ngx_http_handler_module
    HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_handler_module"
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_handler_module.c"
    
    
    • 1
    • 2
    • 3
    • 4

    注意,config文件要和模块的代码在相同目录。

    2.3、编译模块到Nginx源码中

    (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=/home/fly/workspace/pcre-8.41 --with-zlib=/home/fly/workspace/zlib-1.2.11 --with-openssl=/home/fly/workspace/openssl-1.1.0g --add-module=/mnt/hgfs/sourcecode_learning/ngx_http_handler_module
    
    • 1

    注意模块路径要正确。出现如下表示成功:

    configuring additional modules
    adding module in /mnt/hgfs/sourcecode_learning/ngx_http_handler_module
     + ngx_http_handler_module was configured
    creating objs/Makefile
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2)查看是否添加模块到动态代码中:

    cat objs/ngx_modules.c
    
    
    • 1
    • 2

    (3)编译安装:

    make
    sudo make install
    
    
    • 1
    • 2
    • 3

    2.4、修改conf文件

    编译安装完成后,conf文件添加count;

    
    worker_processes 4;
    
    events {
    	worker_connections 1024;
    }
    
    http {
    
    	upstream backend {
    		server 192.168.7.146:8889;
    		server 192.168.7.146:8890;
    	}
    
    	server {
    		listen 8888;
    		location / {
    			proxy_pass http://backend;
    		}
    	}
    	server {
                    listen 8889;
    		location / {
    			count;
    		}
           }
    	server {
                    listen 8890;
            }
    	server {
                    listen 8891;
            }
    
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    2.5、执行效果

    sudo /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/fly.conf 
    
    • 1

    在网页输入IP和端口,执行效果如下:
    ngx_handler_show
    可以看到,返回的网页中多出了访问次数统计。

    三、Nginx的热更新

    (1)conf文件热更新:通过reload指令进行重新加成conf文件。reload过程中是重新开启新的进程来加载新的conf文件;比如原来有4个进程在运行,加载新的conf文件时就重新开启4个进程来加载新的配置文件。
    (2)可执行程序的热更新:编译安装新的nginx,会把原来的nginx重命名为nginx.old,然后调用nginx reload就会更新。

    总结

    1. 上述代码虽然实现了IP访问服务器的流量统计;但是,Nginx是多进程的,上述示例代码没有实现统计数在进程间的共享,这回造成其他进程是重新计数的问题。解决这个问题可以使用共享内存的方式在进程间通信。
    2. 上述代码使用了最简单的数据结构:数组。这不是好的决策,可以将其改为红黑树。

    在这里插入图片描述

  • 相关阅读:
    Goland2023版新UI的debug模式调试框按钮功能说明
    anaconda安装及配置+pytorch安装与配置(自用笔记)
    nuxt 不解析HTML结构bug
    MobileNetV2架构解析
    采集SEO方法-添加关键词
    3天快速入门python机器学习(黑马xxx)
    yolov5训练问题
    第k小的数
    Android 13 - Media框架(12)- MediaCodec(二)
    【蓝桥杯真题练习】STEMA科技素养练习题库 答案版012 持续更新中~
  • 原文地址:https://blog.csdn.net/Long_xu/article/details/127967938