Nginx模块开发之http handler实现流量统计(1)

文章目录

  • 一、handler简介
  • 二、Nginx handler模块开发
    • 2.1、示例代码
    • 2.2、编写config文件
    • 2.3、编译模块到Nginx源码中
    • 2.4、修改conf文件
    • 2.5、执行效果
  • 三、Nginx的热更新
  • 总结

一、handler简介

Handler模块就是接受来自客户端的请求并产生输出的模块。
配置文件中使用location指令可以配置content handler模块,当Nginx系统启动的时候,每个handler模块都有一次机会把自己关联到对应的location上。
如果有多个handler模块都关联了同一个location,那么实际上只有一个handler模块真正会起作用。
所以在开发阶段应该避免多个handler模块关联同一个location的情况发生。

handler模块处理的结果通常有三种情况:

  1. 处理成功。
  2. 处理失败:处理的时候发生了错误。
  3. 拒绝处理:这个location的处理就会由默认的handler模块来进行处理。
    例如,当请求一个静态文件的时候,如果关联到这个location上的一个handler模块拒绝处理,就会由默认的ngx_http_static_module模块进行处理,该模块是一个典型的handler模块。

二、Nginx handler模块开发

2.1、示例代码


#include <ngx_config.h>
#include <ngx_http.h>
#include <ngx_core.h>

#include <arpa/inet.h>
#include <netinet/in.h>

/*
* 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, "<h1>Hello, NGX handler! I am FLY.</h1>");

	strcat(html, "<h2>");

	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 <br/>",
				inet_ntop(AF_INET, &pv_table[i].addr, str, sizeof(str)),
				pv_table[i].count);

			strcat(html, buffer);
		}

	}

	strcat(html, "</h2>");
}



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、handler模块必须提供一个真正的处理函数(即上文中的ngx_http_count_handler),这个函数负责对来自客户端请求的真正处理。这个函数的处理,既可以选择自己直接生成内容,也可以选择拒绝处理,由后续的handler去进行处理,或者是选择丢给后续的filter进行处理。
这个处理函数的原型如下:

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

2.2、编写config文件

创建:

touch config

内容:

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"

注意,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

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

configuring additional modules
adding module in /mnt/hgfs/sourcecode_learning/ngx_http_handler_module
 + ngx_http_handler_module was configured
creating objs/Makefile

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

cat objs/ngx_modules.c

(3)编译安装:

make
sudo make install

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;
        }



}

2.5、执行效果

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

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

三、Nginx的热更新

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

总结

  1. 上述代码虽然实现了IP访问服务器的流量统计;但是,Nginx是多进程的,上述示例代码没有实现统计数在进程间的共享,这回造成其他进程是重新计数的问题。解决这个问题可以使用共享内存的方式在进程间通信。
  2. 上述代码使用了最简单的数据结构:数组。这不是好的决策,可以将其改为红黑树。
  3. nginx的handler模块开发也可以用在黑白名单的处理(比如当判断到同一个ip发送多个无效请求,可以将其加入到黑名单中)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/182475.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微软发布最新.NET 8长期支持版本,云计算、AI应用支持再强化

11 月 15 日开始的为期三天的 .NET Conf 在线活动的开幕日上&#xff0c;.NET 8作为微软的开源跨平台开发平台正式发布。.NET 团队着重强调云、性能、全栈 Blazor、AI 和 .NET MAUI 是.NET 8的主要亮点。.NET团队在 .NET Conf 2023 [1]活动开幕式上表示&#xff1a;“通过这个版…

JSP过滤器和监听器

什么是过滤器 Servlet过滤器与Servlet十分相似&#xff0c;但它具有拦截客户端&#xff08;浏览器&#xff09;请求的功能&#xff0c;Servlet过滤器可以改变请求中的内容&#xff0c;来满足实际开发中的需要。 对于程序开发人员而言&#xff0c;过滤器实质就是在Web应用服务器…

不会代码也能拿高薪?掌握面试法宝,轻松10000+

快速排序&#xff08;Quicksort&#xff09;是对冒泡排序的一种改进。 快速排序由 C.A.R.Hoare 在 1962 年提出。 它的基本思想是&#xff1a;通过一趟排序将要排序的数据分割成独立的两部分&#xff0c;其中一部分的所有数据都比另外一部分的所有数据都要小&#xff0c;然后…

代码随想录算法训练营第五十二天|1143.最长公共子序列 1035.不相交的线 53. 最大子序和

文档讲解&#xff1a;代码随想录 视频讲解&#xff1a;代码随想录B站账号 状态&#xff1a;看了视频题解和文章解析后做出来了 1143.最长公共子序列 class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:dp [[0] * (len(text2) 1) for _ i…

Redisson分布式锁源码解析、集群环境存在的问题

一、使用Redisson步骤 Redisson各个锁基本所用Redisson各个锁基本所用Redisson各个锁基本所用 二、源码解析 lock锁 1&#xff09; 基本思想&#xff1a; lock有两种方法 一种是空参 另一种是带参 * 空参方法&#xff1a;会默认调用看门狗的过期时间30*1000&…

从Discord的做法中学习 — 使用Golang进行请求合并

正如你可能之前看到的&#xff0c;Discord去年发布了一篇有价值的文章&#xff0c;讨论了他们成功存储了数万亿条消息。虽然有很多关于这篇文章的YouTube视频和文章&#xff0c;但我认为这篇文章中一个名为“数据服务为数据服务”的部分没有得到足够的关注。在这篇文章中&#…

redis运维(十九)redis 的扩展应用 lua(一)

一 redis 的扩展应用 lua redis如何保证原子操作 说明&#xff1a;引入lua脚本,核心解决原子性问题 ① redis为什么引入lua? lua脚本本身体积小,启动速度快 ② redis引入lua的优势 小结&#xff1a; 类似自定义redis命令 ③ redis中如何使用lua ④ EVAL 说明&#…

【性能优化】JVM调优与写出JVM友好高效的代码

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

2023年11个最佳免费WordPress主题

如果您刚刚开始使用 WordPress&#xff0c;您可能会很自然地认为&#xff0c;只要免费的WordPress主题看起来像您想要的网站主题&#xff0c;那么它就很合适。不幸的是&#xff0c;事情并没有那么简单。这就是为什么在今天的文章中&#xff0c;我们概述了一份可靠的标准清单&am…

旅行商问题(枚举,回溯,动态规划,贪心,分支界限)

文章目录 问题描述暴力枚举回溯法动态规划法贪心法分支界限法 问题描述 假设有一个货郎担要拜访n个城市&#xff0c;他必须选择所要走的路程&#xff0c;路程的限制时每个城市只能拜访一次&#xff0c;而且最后要走到原来出发的城市&#xff0c;要求路径长度。 旅行商问题将要…

爬虫逆向你应该懂得Javascript知识

背景 大家在学习爬虫逆向的时候&#xff0c;一般都会涉及到对js源文件进行代码扣去&#xff0c;但是有的时候&#xff0c;你最好有js基础&#xff0c;能发现加密或者解密在那个位置&#xff0c;或者是能用python改写js代码&#xff0c;这就对个人的Javascript的能力有一定要求…

python如何快速查找到想要的文档

字多不看版&#xff0c;直接体验 待补充 演示代码 # -*- coding:UTF-8 -*-# region 导入必要的依赖包 import os import subprocess from enum import Enum模块名 pyperclip try:import pyperclip # 需要安装 pyperclip 模块&#xff0c;以支持粘贴板操作 except ImportEr…

激光雷达与惯导标定 | Lidar_IMU_Init : 编译

激光雷达与惯导标定&#xff1a;Lidar_IMU_Init 编译 功能包安装安装ceres-solver-2.0.0 &#xff08;注意安装2.2.0不行&#xff0c;必须要安装2.0.0&#xff09; LI-Init是一种鲁棒、实时的激光雷达惯性系统初始化方法。该方法可校准激光雷达与IMU之间的时间偏移量和外部参数…

【Spring】 IoCDI

回顾 企业命名规范 大驼峰:BookDao(首字母都大写) 类名 小驼峰:bookDao(第一个字母小写) 方法名 蛇形:book_dao(小写下划线_) 数据库 串形:book-dao(小写连字符-) 项目文件夹 各种注解 学习Spring MVC, 其实就是学习各种Web开发需要⽤的到注解 a. RequestMapping: 路由…

(一)C语言之入门:使用Visual Studio Community 2022运行hello world

使用Visual Studio Community 2022运行c语言的hello world 一、下载安装Visual Studio Community 2022 与 新建项目二、编写c helloworld三、编译、链接、运行 c helloworld1. 问题记录&#xff1a;无法打开源文件"stdio.h"2. 问题记录&#xff1a;调试和执行按钮是灰…

Leetcode算法系列| 1. 两数之和(四种解法)

目录 1.题目2.题解解法一&#xff1a;暴力枚举解法二&#xff1a;哈希表解法解法三&#xff1a;双指针(有序状态)解法四&#xff1a;二分查找(有序状态) 1.题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数…

前端 vue 面试题(二)

文章目录 如何让vue页面重新渲染组件间通信vue为什么要mutation、 action操作插槽、具名插槽、作用域插槽vue编译使用的是什么库&#xff1f;vue怎么实现treeshakingwebpack实现treeshaking为什么只有es module 能支持 tree shaking mixin 的作用mixin的底层原理nexTick原理vue…

小辰的智慧树(差分+前缀和)

登录—专业IT笔试面试备考平台_牛客网 1.考虑总长度之和不能超过m&#xff0c;2考虑限制每棵树高度不能低于ci&#xff0c;如果用二分最短输能截到的高度&#xff0c;还要另外去判断&#xff0c;是否每棵树mid都能严格大于ci &#xff0c;这样容易超时&#xff0c;换个角度&…

14 redis全量复制与部分复制

1、设置主服务器的地址和端口 首先是在从服务器设置需要同步的主服务器信息&#xff0c;包括机器IP, 端口。 主从复制的开启&#xff0c;完全是在从节点发起的。不需要我们在主节点做任何事情。 从节点开启主从复制&#xff0c;有3种方式 配置文件&#xff1a;在从服务器的配…

使用【画图】软件修改图片像素、比例和大小

打开电脑画图软件&#xff0c;点击开始 windows附件 画图 在画图软件里选择需要调整的照片&#xff0c;点击文件 打开 在弹出窗口中选择照片后点击打开 照片在画图软件中打开后&#xff0c;对照片进行调整。按图中顺序进行 确定后照片会根据设定的值自动调整 保存…