一个交易网站开发的成本是多少,经典网页传奇,百度关键词优化服务,网站建设项目单子来源我们公司在用nginx的userid模块作为简单的用户请求追踪使用。这个模块其实并不能真正记录用户的请求状态#xff0c;只能作为一个辅助使用。但是在一些场景下会有一些异常。下面我们简单介绍一下这个模块到底做了什么。
userid 模块简介
官网说明文档 ngx_http_userid_modul…我们公司在用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, CPCUR ADM OUR NOR STA NID;配置说明userid on |v1 | log | off;userid开关userid_name uid;userid (cookie)名userid_domain example.com;userid (cookie) domainuserid_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: uidCrINEGWBDAFNOTILCEHMAg; expiresThu, 18-Dec-25 03:20:33 GMT; domainexample.com; path/
p3p: policyref/w3c/p3p.xml, CPCUR 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 bself.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 cookienginx响应头不会再次进行返回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_moduleuserid属于ngx_http_userid_filter_module。 userid在add_headerngx_http_userid_filter_module的上面执行顺序是先执行add_header再执行userid。由于这两个都控制header的filter所以按照优先级来看userid的优先级更高。
结语
以上就是全部内容了。这个简单的nginx http filter模块依然涉及很多nginx内部的框架逻辑大部分都是自己阅读的难免会有纰漏恳请各位大佬斧正~