nginx userid到底做了啥?

我们公司在用nginx的userid模块作为简单的用户请求追踪使用。这个模块其实并不能真正记录用户的请求状态,只能作为一个辅助使用。但是在一些场景下会有一些异常。下面我们简单介绍一下这个模块到底做了什么。

userid 模块简介

官网说明文档
ngx_http_userid_module

官网示例

userid         on;
userid_name    uid;
userid_domain  example.com;
userid_path    /;
userid_expires 365d;
userid_p3p     'policyref="/w3c/p3p.xml", CP="CUR ADM OUR NOR STA NID"';
配置说明
userid on |v1 | log | off;userid开关
userid_name uid;userid (cookie)名
userid_domain example.com;userid (cookie) domain
userid_path /;userid (cookie) 路径
userid_expires 365d;userid (cookie) 过期时间
userid_p3p ‘policyref=“/w3c/p3p.xml”, CP=“CUR ADM OUR NOR STA NID”’;p3p header 标记

简单来说这个模块的作用就是当客户端的请求cookie中,未携带userid字段,或者userid字段不合法时,nginx在response中会加一个Set-Cookie 的 header。如果配置了p3p,会额外返回p3p的header

set-cookie: uid=CrINEGWBDAFNOTILCEHMAg==; expires=Thu, 18-Dec-25 03:20:33 GMT; domain=example.com; path=/
p3p: policyref="/w3c/p3p.xml", CP="CUR ADM OUR NOR STA NID"

这样同一个客户端将会获得相同的uid,可以作为用户请求追踪的请求特征。但是要注意的是这个cookie的设置逻辑很简单,并且没有用户的登录态吧,所以并不可靠。如果用户使用不同浏览器或者无痕访问就会获得不同的uid,通过他来进行uv等数据统计,获得的结果会虚高。

nginx官网对userid模块的介绍比较简单,我们可以看下他的源码来分析一下他的生成和校验逻辑细节。

我们以文章发布时候最新的1.24版本的nginx源码为例

nginx github路径

userid filter核心函数

nginx userid 是一个 http filter 模块,请求进来后通过调用 ngx_http_userid_filter 这个函数来执行 userid的逻辑,ngx_http_userid_filter这个函数主要调用了 ngx_http_userid_get_uid 和 ngx_http_userid_set_uid。分别用于获取和生成userid

userid的生成逻辑

我们先看下ngx_http_userid_get_uid 这个获取uid的函数。我节选一些核心代码

static ngx_http_userid_ctx_t *
ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf)
{ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);...cookie = ngx_http_parse_multi_header_lines(r, r->headers_in.cookie,&conf->name, &ctx->cookie);if (cookie == NULL) {return ctx;}ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"uid cookie: \"%V\"", &ctx->cookie);if (ctx->cookie.len < 22) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"client sent too short userid cookie \"%V\"",&cookie->value);return ctx;}src = ctx->cookie;/** we have to limit the encoded string to 22 characters because*  1) cookie may be marked by "userid_mark",*  2) and there are already the millions cookies with a garbage*     instead of the correct base64 trail "=="*/src.len = 22;dst.data = (u_char *) ctx->uid_got;if (ngx_decode_base64(&dst, &src) == NGX_ERROR) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"client sent invalid userid cookie \"%V\"",&cookie->value);return ctx;}ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"uid: %08XD%08XD%08XD%08XD",ctx->uid_got[0], ctx->uid_got[1],ctx->uid_got[2], ctx->uid_got[3]);return ctx;
}

首先通过 ngx_http_parse_multi_header_lines 查找cookie中 uid的字段值,存到ctx的结构体中。

 cookie = ngx_http_parse_multi_header_lines(r, r->headers_in.cookie,&conf->name, &ctx->cookie);

ngx_http_parse_multi_header_lines这个函数虽然叫分析header,但是我看了下他的代码实现更像是解析cookie的。它传入3个参数,存放header(其实是cookie,如果请求中有多个cookie header字段,那么就会对应多个数组元素)的数组,cookie字段名的字符串,以及要将查找出来的字符串存放到的位置。返回值cookie字段所在的header数组的index,没查到则返回 NGX_DECLINED,是一个负值。这个函数的返回值在这里没啥太大作用。

拿到 uid之后,就做了两个简单的操作,一个是长度是否小于22,另一是base64解码,解码的时候只会取uid的前22个字符,所以只要前22个字符合法就可以,并存到ctx->uid_got。

异常的话分别会打error log client sent too short userid cookie 或者 client sent invalid userid cookie 。

生成uid

ngx_http_userid_set_uid会先通过调用 ngx_http_userid_create_uid来生成uid。

ngx_http_userid_create_uid会将uid的四个int数据存到 ctx->uid_set中,uid_set和uid_got一样都是一个长度为4的int数组,如果ctx->uid_got已经有数据了,就会直接复制到uid_set中。

如果uid_got中没有的话就会生成uid。根据配置中的userid的on和v1的区别,生存逻辑略有不同。v1的生成逻辑比较简单。

	        if (conf->service == NGX_CONF_UNSET) {ctx->uid_set[0] = 0;} else {ctx->uid_set[0] = conf->service;}ctx->uid_set[1] = (uint32_t) ngx_time();ctx->uid_set[2] = start_value;ctx->uid_set[3] = sequencer_v1;sequencer_v1 += 0x100;

uid_set[0] 是个固定值,uid_set[2]每个worker是固定的。

默认的on的逻辑稍微复杂一些,比如uid_set[0]使用了监听连接地址。但是总得来看他们的生成逻辑差不太多,如果你一直使用同一个nginx,同一个worker接收请求,会发现生成出来的uid有很多位是一直不变的。uid_set[1] 和 uid_set[3]分别是nginx的当前时间和一个计数器,uid的生成更接近一个顺序增加产生的,由于里面包含时间信息,几乎不用担心uid冲突。

uid 信息提取

根据上面的生成逻辑,我们可以知道nginx userid 模块生成的cookie是有服务端地址和生成时间的,我们可以写一个简单的脚本来分析这个cookie。 下面是一段python3代码

import base64
import datetimeclass CookieUID(object):def __init__(self, cookie_uid):self.cookie_uid = cookie_uidself.b_cookie_uid = b''self.check_and_b64decode()def check_and_b64decode(self):if len(self.cookie_uid) != 22 and len(self.cookie_uid) != 24:raise ValueError('cookie uid 的长度需要时22或者24')if len(self.cookie_uid) == 22:self.cookie_uid += '=='elif self.cookie_uid[-2:] != '==':raise ValueError('24字节的cookie_uid 需要以 == 结尾')self.b_cookie_uid = base64.b64decode(self.cookie_uid)def print_info(self):self.print_server_addr()self.print_generated_date()def print_server_addr(self):print('server_addr: ', end='')for i in range(4):print(self.b_cookie_uid[i], end='')if i < 3:print('.', end='')else:print('')def print_generated_date(self):generated_timestamp = int.from_bytes(self.b_cookie_uid[4:8])print('cookie uid generate time: ', datetime.datetime.fromtimestamp(generated_timestamp))if __name__ == '__main__':cookie_uid = CookieUID('fwAAAWWFOcoflzElAwMGAg==')

输出结果是

server_addr: 127.0.0.1
cookie uid generate time:  2023-12-22 15:24:58

写入uid

ngx_http_userid_set_uid 调用完生成userid_create_uid 之后就进行生产cookie的操作。
他会先计算一下将要生产的cookie长度,然后申请一块内存。

cookie = ngx_pnalloc(r->pool, len);

然后将要生成的cookie数据写入或拷贝到cookie的内存中,第一段写入的就是userid对应的cookie

    p = ngx_copy(cookie, conf->name.data, conf->name.len);*p++ = '=';if (ctx->uid_got[3] == 0 || ctx->reset) {src.len = 16;src.data = (u_char *) ctx->uid_set;dst.data = p;ngx_encode_base64(&dst, &src);p += dst.len;if (conf->mark) {*(p - 2) = conf->mark;}} else {p = ngx_cpymem(p, ctx->cookie.data, 22);*p++ = conf->mark;*p++ = '=';}

他会先检查之前ctx->uid_got有没有获取到数据,有的话就直接拷贝之前存在ctx->cookie的数据,并且只会拷贝22个字符。没有的话,就通过之前create生成到ctx->uid_set中的字节通过base64变成成字符串。之后会写入一写其他cookie字段,比如配置中配的domain之类的。

最后通过 ngx_list_push申请header的链表节点结构体,将value指向之前生成的cookie数据上。

	set_cookie = ngx_list_push(&r->headers_out.headers);set_cookie->hash = 1;ngx_str_set(&set_cookie->key, "Set-Cookie");set_cookie->value.len = p - cookie;set_cookie->value.data = cookie;

p3p因为是一个单独的header,所以他也是通过 ngx_list_push 这种方式新增一个header节点。

写到这里其实有个疑问,按照这个模块的逻辑,不管之前请求中是否携带userid,响应头中都会进行set-cookie的操作,这个跟我们实际的现象不太相符。实际中如果有合法的userid cookie,nginx响应头不会再次进行返回set-cookie的header了,这需要后续仔细看下。

uid的插入时机

然后我们在使用中遇到一个问题是,nginx生成的uid是否能通过某些手段控制他的生成呢?比如满足某些情况通过add_header 将其set-cookie置空。这就涉及到nginx模块的执行循序问题。

nginx的header模块执行顺序是通过一个单向链表来实现,每个模块在初始化的时候,会将自己放到链表的头部

	static ngx_int_tngx_http_userid_init(ngx_conf_t *cf){ngx_http_next_header_filter = ngx_http_top_header_filter;ngx_http_top_header_filter = ngx_http_userid_filter;return NGX_OK;}

nginx在处理请求时会遍历这个链表,依次执行对应的filter模块。所以模块初始化的逆序就是各个filter模块的执行顺序。而模块的初始化是在nginx编译的时候进行的,所以可以通过configure生成的ngx_modules.c的顺序来判断filter模块执行顺序。还是以add_header 和 userid为例。add_header属于ngx_http_header_filter_module,userid属于ngx_http_userid_filter_module。
在这里插入图片描述
userid在add_header(ngx_http_userid_filter_module)的上面,执行顺序是先执行add_header再执行userid。由于这两个都控制header的filter,所以按照优先级来看userid的优先级更高。

结语

以上就是全部内容了。这个简单的nginx http filter模块依然涉及很多nginx内部的框架逻辑,大部分都是自己阅读的,难免会有纰漏,恳请各位大佬斧正~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【三维生成与重建】ZeroRF:Zero Pretraining的快速稀疏视图360°重建

系列文章目录 题目&#xff1a;ZeroRF: Fast Sparse View 360◦ Reconstruction with Zero Pretraining 任务&#xff1a;稀疏重建&#xff1b;拓展&#xff1a;Image to 3D、文本到3D 作者&#xff1a;Ruoxi Shi* Xinyue Wei* Cheng Wang Hao Su &#xff0c;来自UC San Dieg…

为实体服务器配置Ubuntu

简介 我们在使用虚拟机时&#xff0c;直接在网上找到镜像然后下载到本地&#xff0c;在VMware创建实例时将该iso文件作为镜像源然后进行基础配置就可以轻松安装配置好Linux虚拟机。 在为实体服务器安装Linux系统&#xff0c;同样的&#xff0c;我们也需要镜像源&#xff08;即…

Jmeter多种定时器实现方法解析

1、固定定时器&#xff08;Constant Timer&#xff09; 用法(场景)&#xff1a;更真实的模拟用户场景&#xff0c;需要设置等待时间&#xff0c;或是等待上一个请求的时间才执行&#xff0c;给 sampler 之间的思考时间 备注&#xff1a;如果需要每个步骤均延迟&#xff0c;则…

Python实现AR协方差结构线性回归模型(GLSAR算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 GLSAR是具有AR协方差结构的广义最小二乘法线性回归模型。 本项目通过GLSAR回归算法来构建AR协方差结构…

搭建 ElasticSearch 集群环境

安装基础环境 我们用虚拟机创建出3台机器&#xff0c;修改主机名为s1&#xff0c;s2和s3 # 打开如下文件&#xff0c;修改主机名 vim /etc/hostname # 重启机器 reboot查看centos版本为7.9 [roots1 ~]# cat /etc/centos-release CentOS Linux release 7.9.2009 (AltArch)下载…

【C语言】记录一次自己犯下的低级错误 o(╯□╰)o(局部数组与指针数组的传参、赋值)

在这里分享一下本人犯下的低级错误&#xff0c;希望大家别掉同样的坑 o(╥﹏╥)o 文章目录 事情原委错误分析及解救办法错误一&#xff1a; 使用局部数组arr并将其作为返回值解决方法&#xff1a;使用动态内存分配来创建数组&#xff0c;并在函数结束前手动释放内存。 错误二&…

在nodejs中使用讯飞星火大模型3.0的demo

需求&#xff1a; 在nodejs引入讯飞星火大模型的api接口, 思路 看了一下官方文档 api连接为一个WebSocket Secure&#xff08;WSS&#xff09;连接&#xff0c;具体思路如下&#xff1a; 引入 crypto 和 ws 模块&#xff0c;分别用于生成加密签名和创建 WebSocket 连接。&am…

力扣每日一题day37[113.路径总和ii]

给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum 22 输出&a…

Maven高级篇

Maven依赖管理原则; 可选依赖&#xff1a;隐藏当前项目中的指定的包&#xff0c;如此&#xff0c;别的包引用当前包时&#xff0c;当前包中的指定包就被隐藏了&#xff0c;在别的包中无法看到隐藏的包 排除依赖&#xff1a;指定排除引用包中所包含的依赖&#xff0c;防止与当…

基于Docker环境下的Jenkins搭建及使用

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

2023年12月21日开发正式版v1.2.3更新·本次更新30多个细节优化·完善丰富后台功能·加入演员关联机制

2023年12月21日开发正式版v1.2.3更新本次更新30多个细节优化完善丰富后台功能加入演员关联机制 产品简介 安卓苹果PCH5四端&#xff0c;蜻蜓z暗影版的衍生级版本&#xff0c;2023年优雅草蜻蜓z冬季雪花限定版&#xff0c;不仅继承了蜻蜓z的精良功能&#xff0c;还特色增加了弹…

Win系统修改Nginx配置结合内网穿透实现远程访问多个Web站点

文章目录 1. 下载windows版Nginx2. 配置Nginx3. 测试局域网访问4. cpolar内网穿透5. 测试公网访问6. 配置固定二级子域名7. 测试访问公网固定二级子域名 1. 下载windows版Nginx 进入官方网站(http://nginx.org/en/download.html)下载windows版的nginx 下载好后解压进入nginx目…

找不到msvcp120dll,无法继续执行代码的解决方法大全

当你尝试启动一个应用程序或游戏&#xff0c;并且遭遇到一个错误信息&#xff0c;告诉你“找不到msvcp120dll,无法继续执行代码”或者收到类似的提示&#xff0c;这说明你的操作系统中缺失了一个关键的动态链接库文件&#xff0c;即 msvcp120.dll。这种情况其实并不罕见&#x…

一文解析子网掩码和默认网关,成为网络设置达人

随着互联网的普及&#xff0c;越来越多的人开始接触并使用电脑和网络。然而&#xff0c;对于很多初学者来说&#xff0c;网络设置中的子网掩码和默认网关是两个相对陌生的概念。今天&#xff0c;我们就来深入解析这两个概念&#xff0c;让你轻松掌握网络设置技巧&#xff01; …

从功能测试到测试开发,薪资翻倍,我整理的全网最全学习指南!

在这个吃技术的IT行业来说&#xff0c;我刚入行的时候每天做的也是最基础的工作&#xff0c;但是随着时间的消磨&#xff0c;我产生了对自我和岗位价值和意义的困惑。 一是感觉自己在浪费时间&#xff0c;另一个就是做了快2年的测试&#xff0c;感觉每天过得浑浑噩噩&#xff…

【快速应用开发】Blitz.js简介:Next.js的全栈框架

自我介绍 做一个简单介绍&#xff0c;酒架年近48 &#xff0c;有20多年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0e;因为工作需要&#xff0c;另外也因为兴趣涉猎比较广&#xff0c;为了自己学习建立了三个博客&#xff0c;分别是【全球IT瞭望】&#xff0c;【…

Swift 周报 第四十一期

文章目录 前言新闻和社区2024 年 Swift Student Challenge 公布现推出超过 30 个新的开发者活动 提案正在审查的提案 Swift论坛话题讨论推荐博文关于我们 前言 本期是 Swift 编辑组整理周报的第四十一期&#xff0c;每个模块已初步成型。各位读者如果有好的提议&#xff0c;欢…

深度学习目标检测(2)yolov3设计思想

YOLOv3基础 YOLOv3算法基本思想可以分成两部分&#xff1a; 按一定规则在图片上产生一系列的候选区域&#xff0c;然后根据这些候选区域与图片上物体真实框之间的位置关系对候选区域进行标注。跟真实框足够接近的那些候选区域会被标注为正样本&#xff0c;同时将真实框的位置…

Sentinel 流量治理组件教程

前言 官网首页&#xff1a;home | Sentinel (sentinelguard.io) 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形…

macOS 开发 - MASShortcut

文章目录 关于 MASShortcut项目结构 快速使用源码学习检测是否有热键冲突处理 Event macOS 开发交流 秋秋群&#xff1a;644096295&#xff0c;V : ez-code 关于 MASShortcut MASShortcut 是一款快捷键管理工具&#xff0c;替代和兼容 ShortcutRecorder github : https://git…