深入理解nginx的userid模块[上]
深入理解nginx的userid模块[下]
4. 源码分析
4.1 模块的初始化
4.1.1 ngx_http_userid_add_variables函数添加自定义变量
本函数是在preconfirguration阶段被调用的,用来向nginx框架注册uid_got
、uid_set
、uid_reset
三个变量。
-
uid_got: cookie的名字和接收到的客户端的标识。
-
uid_set: cookie的名字和发送的客户端的标识。
-
uid_reset: 如果这个变量被设置为不是"0"的非空字符串,客户端的标识将被重置。如果被设置为"log",那么将导致重置客户端标识的日志被写入error_log。
4.1.2 ngx_http_userid_init函数
本函数是在postconfiguration阶段被调用的,让ngx_http_userid_module作为filter形式挂载到filter链中。源码如下:
static ngx_int_t
ngx_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;
}
从源码可以看到,本模块作为一个header filter挂载到了header过滤链中。在nginx向客户端发送http头响应的时候,会调用这是挂载的ngx_http_userid_filter函数进行相应的处理。
4.1.3 ngx_http_userid_init_worker worker初始化函数
本函数是在nginx对worker进行进行初始化的时候进行调用的,调用顺序是发生在ngx_http_userid_init函数之后的。源码如下:
static ngx_int_t
ngx_http_userid_init_worker(ngx_cycle_t *cycle)
{
struct timeval tp;
ngx_gettimeofday(&tp);
/* use the most significant usec part that fits to 16 bits */
start_value = (((uint32_t) tp.tv_usec / 20) << 16) | ngx_pid;
return NGX_OK;
}
这里设置了一个start_value的变量值,未来这个值将作为userid的一部分。
4.2 ngx_http_userid_filter header过滤函数
本模块的真正处理逻辑是在ngx_http_userid_filter函数中来实现的。当nginx开始向客户端发送HTTP响应头的时候,就会开始调用header 过滤链,而本模块就会得到处理响应信息的机会,对HTTP响应头进行处理。
如果本模块配置了enable为v1或者v2(默认),那么就执行处理逻辑,否则直接跳过本模块的逻辑。源码如下:
static ngx_int_t
ngx_http_userid_filter(ngx_http_request_t *r)
{
ngx_http_userid_ctx_t *ctx;
ngx_http_userid_conf_t *conf;
/* 如果是subrequest,则跳过本模块的逻辑 */
if (r != r->main) {
return ngx_http_next_header_filter(r);
}
conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
/* 如果enable不是v1和on(就是v2),则跳过本模块的逻辑 */
if (conf->enable < NGX_HTTP_USERID_V1) {
return ngx_http_next_header_filter(r);
}
/* 获取或者创建一个新的模块上下文 */
ctx = ngx_http_userid_get_uid(r, conf);
if (ctx == NULL) {
return NGX_ERROR;
}
/* 在响应头的cookie中输出userid */
if (ngx_http_userid_set_uid(r, ctx, conf) == NGX_OK) {
return ngx_http_next_header_filter(r);
}
return NGX_ERROR;
}
4.3 ngx_http_userid_get_uid获取或者创建模块上下文
本函数检查是否模块上下文已经存在,如果没有创建则创建一个新的模块上下文,最后返回上下文的指针,源码如下:
ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
if (ctx) {
return ctx;
}
if (ctx == NULL) {
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_userid_ctx_t));
if (ctx == NULL) {
return NULL;
}
ngx_http_set_ctx(r, ctx, ngx_http_userid_filter_module);
}
接着,分析http请求头中的cookie是否有配置文件中指定名字的cookie,如果存在,则提取出这个cookie的值,并且进行base64解码,源码如下:
/* 从请求头中获取cookie的值 */
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);
/* 本模块限制了userid的cookie字段值的长度不能少于22位 */
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;
/* 对userid的值进行base64解码,得到解码后的userid */
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;
4.4 ngx_http_userid_set_uid 在http响应头中输出userid
4.4.1 ngx_http_userid_create_uid 创建userid
首先,在本函数中检查如果已经设置过userid了(可能是在获取uid_set变量的时候已经创建过了),那么则直接跳过。
if (ctx->uid_set[3] != 0) {
return NGX_OK;
}
其次,如果客户端带了userid字段,并且userid_reset变量没有打开重置的标记,则直接返回客户端设置的userid字段的内容,源码如下:
if (ctx->uid_got[3] != 0) { /* 表示客户端请求的时候带了userid过来 */
/* 检查userid_reset变量的标记是否需要重置本次响应的userid */
vv = ngx_http_get_indexed_variable(r, ngx_http_userid_reset_index);
if (vv == NULL || vv->not_found) {
return NGX_ERROR;
}
if (vv->len == 0 || (vv->len == 1 && vv->data[0] == '0')) {
/* userid_reset变量为空,或者是'0',则表示不重置userid,
而是使用客户端请求的userid
*/
if (conf->mark == '\0'
|| (ctx->cookie.len > 23
&& ctx->cookie.data[22] == conf->mark
&& ctx->cookie.data[23] == '='))
{
return NGX_OK;
}
/* 这里通过赋值,将返回客户端设置的userid */
ctx->uid_set[0] = ctx->uid_got[0];
ctx->uid_set[1] = ctx->uid_got[1];
ctx->uid_set[2] = ctx->uid_got[2];
ctx->uid_set[3] = ctx->uid_got[3];
return NGX_OK;
} else {
ctx->reset = 1;
/* userid_reset = log,则记录日志到error_log */
if (vv->len == 3 && ngx_strncmp(vv->data, "log", 3) == 0) {
ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
"userid cookie \"%V=%08XD%08XD%08XD%08XD\" was reset",
&conf->name, ctx->uid_got[0], ctx->uid_got[1],
ctx->uid_got[2], ctx->uid_got[3]);
}
}
}
根据配置的版本类型,生成v1版本的userid, userid为 service + time + start_value + 序列值,如下:
if (conf->enable == NGX_HTTP_USERID_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;
}
根据配置的版本类型,生成v2版本的userid, userid的第一部分取自IP地址的一部分,或者用配置的userid_service值,第二部分是时间,第三部分是start_value,第四部分是序列值,源码如下:
if (conf->service == NGX_CONF_UNSET) {
c = r->connection;
if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
return NGX_ERROR;
}
switch (c->local_sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
p = (u_char *) &ctx->uid_set[0];
*p++ = sin6->sin6_addr.s6_addr[12];
*p++ = sin6->sin6_addr.s6_addr[13];
*p++ = sin6->sin6_addr.s6_addr[14];
*p = sin6->sin6_addr.s6_addr[15];
break;
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
case AF_UNIX:
ctx->uid_set[0] = 0;
break;
#endif
default: /* AF_INET */
sin = (struct sockaddr_in *) c->local_sockaddr;
ctx->uid_set[0] = sin->sin_addr.s_addr;
break;
}
} else {
ctx->uid_set[0] = htonl(conf->service);
}
ctx->uid_set[1] = htonl((uint32_t) ngx_time());
ctx->uid_set[2] = htonl(start_value);
ctx->uid_set[3] = htonl(sequencer_v2);
sequencer_v2 += 0x100;
if (sequencer_v2 < 0x03030302) {
sequencer_v2 = 0x03030302;
}
4.4.2 合成响应的userid cookie值
合成的cookie内容包括如下信息:
- 添加cookie名字
- 添加userid的base64值,如果配置了mark,则末尾设置mark字符
- 如果user_expire中配置了超时expire,则添加 expires=xxxx的cookie超时信息
- 如果user_flags中配置了secure,则添加secure属性到cookie中
- 如果user_flags中配置了httponly,则添加httponly属性到cookie中
- 如果user_flags中配置了samesite=strict,则添加samesite=strict属性到cookie中
- 如果user_flags中配置了samesite=lax,则添加samesite=lax属性到cookie中
- 如果user_flags中配置了samesite=none,则添加samesite=none属性到cookie中
在合成之前,本模块首先需要计算实际需要的内存空间大小,然后再逐个字段生成到目标cookie中,源码如下:
/* 计算需要的内存空间的大小 */
len = conf->name.len + 1 + ngx_base64_encoded_length(16) + conf->path.len;
if (conf->expires) {
len += sizeof(expires) - 1 + 2;
}
if (conf->domain.len) {
len += conf->domain.len;
}
if (conf->flags & NGX_HTTP_USERID_COOKIE_SECURE) {
len += sizeof("; secure") - 1;
}
if (conf->flags & NGX_HTTP_USERID_COOKIE_HTTPONLY) {
len += sizeof("; httponly") - 1;
}
if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_STRICT) {
len += sizeof("; samesite=strict") - 1;
}
if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_LAX) {
len += sizeof("; samesite=lax") - 1;
}
if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_NONE) {
len += sizeof("; samesite=none") - 1;
}
/* 为cookie分配内存 */
cookie = ngx_pnalloc(r->pool, len);
if (cookie == NULL) {
return NGX_ERROR;
}
/* 往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++ = '=';
}
if (conf->expires == NGX_HTTP_USERID_MAX_EXPIRES) {
p = ngx_cpymem(p, expires, sizeof(expires) - 1);
} else if (conf->expires) {
p = ngx_cpymem(p, expires, sizeof("; expires=") - 1);
p = ngx_http_cookie_time(p, ngx_time() + conf->expires);
}
p = ngx_copy(p, conf->domain.data, conf->domain.len);
p = ngx_copy(p, conf->path.data, conf->path.len);
if (conf->flags & NGX_HTTP_USERID_COOKIE_SECURE) {
p = ngx_cpymem(p, "; secure", sizeof("; secure") - 1);
}
if (conf->flags & NGX_HTTP_USERID_COOKIE_HTTPONLY) {
p = ngx_cpymem(p, "; httponly", sizeof("; httponly") - 1);
}
if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_STRICT) {
p = ngx_cpymem(p, "; samesite=strict", sizeof("; samesite=strict") - 1);
}
if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_LAX) {
p = ngx_cpymem(p, "; samesite=lax", sizeof("; samesite=lax") - 1);
}
if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_NONE) {
p = ngx_cpymem(p, "; samesite=none", sizeof("; samesite=none") - 1);
}
set_cookie = ngx_list_push(&r->headers_out.headers);
if (set_cookie == NULL) {
return NGX_ERROR;
}
4.4.3 将userid cookie添加到http响应头中
源码如下:
/* 添加一个新的http header */
set_cookie = ngx_list_push(&r->headers_out.headers);
if (set_cookie == NULL) {
return NGX_ERROR;
}
/* 将cookie值写入上面添加的http header中, header名字为Set-Cookie */
set_cookie->hash = 1;
ngx_str_set(&set_cookie->key, "Set-Cookie");
set_cookie->value.len = p - cookie;
set_cookie->value.data = cookie;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"uid cookie: \"%V\"", &set_cookie->value);
if (conf->p3p.len == 0) {
return NGX_OK;
}
/* 如果配置了p3p,那么再添加一个P3P的HTTP头 */
p3p = ngx_list_push(&r->headers_out.headers);
if (p3p == NULL) {
return NGX_ERROR;
}
p3p->hash = 1;
ngx_str_set(&p3p->key, "P3P");
p3p->value = conf->p3p;
4.5 自定义变量的获取和设置
4.5.1 uid_got
uid_got是一个只读变量,用来获取当前请求的客户端请求头中发送过来的userid的信息。源码如下:
static ngx_int_t
ngx_http_userid_got_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_http_userid_ctx_t *ctx;
ngx_http_userid_conf_t *conf;
conf = ngx_http_get_module_loc_conf(r->main, ngx_http_userid_filter_module);
/* 如果没有启用本模块,则返回未找到标记 */
if (conf->enable == NGX_HTTP_USERID_OFF) {
v->not_found = 1;
return NGX_OK;
}
/* 获取模块上下文或者创建新的模块上下文 */
ctx = ngx_http_userid_get_uid(r->main, conf);
if (ctx == NULL) {
return NGX_ERROR;
}
/* 如果客户端请求头中带了userid的cookie信息,则返回这个信息 */
if (ctx->uid_got[3] != 0) {
/* 对ud_got进行打印输出,格式name=00001111222233334444555566667777*/
return ngx_http_userid_variable(r->main, v, &conf->name, ctx->uid_got);
}
/* 客户端请求头中没有userid的cookie信息,返回未找到 */
v->not_found = 1;
return NGX_OK;
}
4.5.2 uid_set
uid_got是一个只读变量,用于获取当前请求的响应头中设置的userid的信息,源码如下:
static ngx_int_t
ngx_http_userid_set_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_http_userid_ctx_t *ctx;
ngx_http_userid_conf_t *conf;
conf = ngx_http_get_module_loc_conf(r->main, ngx_http_userid_filter_module);
/* 如果没有启用本模块,则返回未找到标记 */
if (conf->enable < NGX_HTTP_USERID_V1) {
v->not_found = 1;
return NGX_OK;
}
/* 获取模块上下文或者创建新的模块上下文 */
ctx = ngx_http_userid_get_uid(r->main, conf);
if (ctx == NULL) {
return NGX_ERROR;
}
/* 创建一个新的userid */
if (ngx_http_userid_create_uid(r->main, ctx, conf) != NGX_OK) {
return NGX_ERROR;
}
if (ctx->uid_set[3] == 0) {
v->not_found = 1;
return NGX_OK;
}
/* 对ud_set进行打印输出,格式name=00001111222233334444555566667777*/
return ngx_http_userid_variable(r->main, v, &conf->name, ctx->uid_set);
}
4.5.3 uid_reset
该变量是一个可写变量,含义见 4.1.1节的说明,配置代码如下:文章来源:https://www.toymoban.com/news/detail-855692.html
var->get_handler = ngx_http_userid_set_variable;
var = ngx_http_add_variable(cf, &ngx_http_userid_reset,
NGX_HTTP_VAR_CHANGEABLE); /* 设置可修改 */
if (var == NULL) {
return NGX_ERROR;
}
var->get_handler = ngx_http_userid_reset_variable; /* 设置不可读 */
n = ngx_http_get_variable_index(cf, &ngx_http_userid_reset);
if (n == NGX_ERROR) {
return NGX_ERROR;
}
ngx_http_userid_reset_index = n; /* 设置模块快速查询该变量的索引*/
这里设置了get_handler为ngx_http_userid_reset_variable,而该函数在被进行变量获取的调用时候直接返回null,意味着该变量不可读,如下:文章来源地址https://www.toymoban.com/news/detail-855692.html
static ngx_int_t
ngx_http_userid_reset_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
*v = ngx_http_variable_null_value;
return NGX_OK;
}
到了这里,关于深入理解nginx的userid模块[下]的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!