深入理解nginx的userid模块[上]
深入理解nginx的userid模块[下]

4. 源码分析

4.1 模块的初始化

4.1.1 ngx_http_userid_add_variables函数添加自定义变量

  本函数是在preconfirguration阶段被调用的,用来向nginx框架注册uid_gotuid_setuid_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内容包括如下信息:

  1. 添加cookie名字
  2. 添加userid的base64值,如果配置了mark,则末尾设置mark字符
  3. 如果user_expire中配置了超时expire,则添加 expires=xxxx的cookie超时信息
  4. 如果user_flags中配置了secure,则添加secure属性到cookie中
  5. 如果user_flags中配置了httponly,则添加httponly属性到cookie中
  6. 如果user_flags中配置了samesite=strict,则添加samesite=strict属性到cookie中
  7. 如果user_flags中配置了samesite=lax,则添加samesite=lax属性到cookie中
  8. 如果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节的说明,配置代码如下:

   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,意味着该变量不可读,如下:

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

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐