前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Nginx35】Nginx学习:运行信息、响应修改及用户标识模块

【Nginx35】Nginx学习:运行信息、响应修改及用户标识模块

作者头像
硬核项目经理
发布2023-10-25 18:26:04
4160
发布2023-10-25 18:26:04
举报

Nginx学习:运行信息、响应修改及用户标识模块

今天的内容主要是讲三个模块,这三个模块分别可以查看当前 Nginx 的运行状态信息,可以修改返回响应的内容,以及最后一个可以通过 Nginx 生成一个对应客户端的唯一 UID 。这几个功能平时用过的同学可能不多,但是也都非常有意思,有兴趣的小伙伴可以尝试尝试哦。

除了第一个运行信息的配置外,其它配置指令都可以在 http、server、location 下进行配置。

Nginx 运行信息

就像 Redis 中的 info 命令一样,Nginx 直接提供了一个配置指令,可以直接返回当前 Nginx 服务器的一些状态信息。它的名称是 ngx_http_stub_status_module 模块,作用就是提供对基本状态信息的访问。

这个模块不包含在 Nginx 核心模块中,需要通过 --with-http_stub_status_module 参数编译安装。它只有一个配置指令。

stub_status

可以从指定位置访问基本状态信息。

代码语言:javascript
复制
stub_status;

只能在 server、location 下定义,不能在 http 下配置,在 1.7.5 之前的版本中,指令语法需要添加一个任意的参数,例如“stub_status on”。我们就简单来测试一下。

代码语言:javascript
复制
location /status/ {
    stub_status;
}

访问 URL 会返回下面这样的内容。

这些信息的含义是:

  • Active connections,当前活动客户端连接数,包括等待连接数。
  • accepts,接受的客户端连接总数。
  • handled,处理的连接总数。通常,除非已达到某些资源限制(例如,worker_connections 限制),否则参数值与 accept 相同。
  • requests,客户端请求的总数。
  • Reading,nginx 正在读取请求标头的当前连接数。
  • Writing,nginx 将响应写回客户端的当前连接数。
  • Waiting,当前等待请求的空闲客户端连接数。

我们可以再开一个浏览器,随便访问一个页面,然后查看这些信息。

然后我们再来压测一下。

代码语言:javascript
复制
ab -c 10 -n 100 http://192.168.56.88:8035/

压测完成之后,再次查看。

通过这些测试,就可以看出几个变量的不同状态,requests 是每次刷新都会加 1 ,accepts 与 handled 是 TCP 连接的数量,和浏览器或者刷新次数关系不大。有活跃连接时 Active connections 会反映出来 。剩下的最底下三个不是很好看到效果,但当前在访问的请求总会有一个 Writing 。

很多监控软件可能会用到这些内容,可以直接通过这些数据收集到 Nginx 的运行信息,比如使用 Zabbix 就可以方便地将这些信息采集过去,实现全面的监控。

变量

这个模块支持以下嵌入式变量(1.3.14):

  • $connections_active 与 Active connections 相同
  • $connections_reading 与 Reading 相同
  • $connections_writing 与 Writing 相同
  • $connections_waiting 与 Waiting 相同

可以将这些变量记录到日志中,比如:

代码语言:javascript
复制
log_format stub_status 'connections_active=$connections_active connections_reading=$connections_reading connections_writing=$connections_writing connections_waiting=$connections_waiting';

server{
  ………………
  location /status/ {
    access_log logs/35.stub_status.log stub_status;
    stub_status;
  }
}

访问页面后获得的结果是:

代码语言:javascript
复制
// 35.sub_status.log
connections_active=1 connections_reading=0 connections_writing=0 connections_waiting=0

修改响应

这个模块主要是针对 Nginx 处理完成之后,获得的响应内容,然后在响应发出之前对这些响应内容进行修改。它的全名是 ngx_http_sub_module 模块,其实是一个过滤器,它通过将一个指定的字符串替换为另一个来修改响应。

这个模块也是需要独立编译安装的,通过 --with-http_sub_module 配置。我们先来看看它的配置项的说明,最后再一起进行测试。

sub_filter

设置要替换的字符串和替换字符串。

代码语言:javascript
复制
sub_filter string replacement;

忽略大小写匹配要替换的字符串。要替换的字符串 (1.9.4) 和替换字符串可以包含变量。可以在同一配置级别 (1.9.4) 上指定多个 sub_filter 指令。当且仅当当前级别上没有定义 sub_filter 指令时,这些指令才从先前的配置级别继承。

sub_filter_last_modified

允许在替换期间保留原始响应中的“Last-Modified”标头字段,以促进响应缓存。

代码语言:javascript
复制
sub_filter_last_modified on | off;

默认 off ,表示由于响应的内容在处理过程中被修改,头部字段被删除。

sub_filter_once

指示是否查找每个字符串以替换一次或重复。

代码语言:javascript
复制
sub_filter_once on | off;

默认 on 。注意,打开或者默认是只替换一次,也就是只替换第一个,而关闭则是符合条件的文本全部替换。

sub_filter_types

除了“text/html”之外,还可以在具有指定 MIME 类型的响应中进行字符串替换。

代码语言:javascript
复制
sub_filter_types mime-type ...;

默认值是 text/html ,只针对 html 响应内容进行替换。特殊值“*”匹配任何 MIME 类型 (0.8.29)。

修改响应测试

好了,我们来测试一下吧,先准备下面的测试配置。

代码语言:javascript
复制
location  /sub1/ {
  alias html/;
  sub_filter nginx zyblog;
  sub_filter 'welcome to' hello;
  sub_filter_once off;

  #sub_filter_types *;
  #sub_filter this that;
  #return 200 'thisisreturn';
}

直接访问首页,所有的 "Welcome to Nginx" 都被换成了 "hello zyblog" 。不使用 sub_filter_once 或者 sub_filter_once 为 on 的话,就只有 title 标签中的第一个匹配到的被替换。

然后我们尝试打开 /sub1/a.txt ,并把 sub_filter this that; 这行的注释打开,你会发现并没有把文本内容中的 this 替换成 that ,开启 sub_filter_types 为 * 之后,a.txt 的内容就替换成功了。

最后还有一个测试,就是对 return 测试之后,我们会发现替换对于 return 指令也是有效果的。

接下来我们再测试一下代理的响应结果,直接用 FastCGI 测试一下就行。

代码语言:javascript
复制
location ^~ /sub2/ {
  alias html/;

  sub_filter server Ss;

  fastcgi_pass unix:/var/sock/php-fpm/www.sock;
  fastcgi_index  index.php;
  fastcgi_param  SCRIPT_FILENAME  $request_filename;
  include        fastcgi_params;
}

访问一个 PHP 页面,直接输出 print_r($_SERVER); 就好了,我们会发现返回的响应中,第一个 SERVER_NAME 被替换成了 Ss_NAME 。

UserID 客户端用户标识

这个功能是干嘛的?估计用过的同学真的不多了。它能够为一些 pc 站或者 h5 提供 userid 设置和分析功能,方便数据团队统计 uv 。这是什么意思?做过 App 的同学,一定知道,很多情况下为了统计用户,需要提供一个客户端唯一标识。比如说苹果的 uuid 或者 Android 生成的唯一设备标识。JS 前端也可以根据浏览器信息及设备信息生成这样一个 ID 。而 Nginx 的这个模块,则是从服务端生成。

它的全名是 ngx_http_userid_module 模块,用于设置适合客户端识别的 cookie 信息(一个 uid)。可以使用嵌入式变量 uid_got 和 uid_set ,还是先来看它的配置信息,然后再进行测试。

userid

启用或禁用设置 cookie 并记录收到的 cookie。

代码语言:javascript
复制
userid on | v1 | log | off;

默认 off ,几个参数值的意思是:

  • on 启用版本 2 cookie 的设置和接收到的 cookie 的记录
  • v1 启用版本 1 cookie 的设置和接收到的 cookie 的记录
  • 禁用 cookie 的设置,但启用接收到的 cookie 的记录
  • 禁用 cookie 的设置和接收到的 cookie 的记录

userid_domain

定义为其设置 cookie 的域。

代码语言:javascript
复制
userid_domain name | none;

默认值为 none ,表示禁用 cookie 的域设置。

userid_expires

设置浏览器应保留 cookie 的时间。

代码语言:javascript
复制
userid_expires time | max | off;

默认值 off ,参数 max 将导致 cookie 在“2037 年 12 月 31 日 23:55:55 GMT”到期。参数 off 将导致 cookie 在浏览器会话结束时过期。

userid_flags

为 Cookie 定义标志。

代码语言:javascript
复制
userid_flags off | flag ...;

如果该参数未关闭,则为 cookie 定义一个或多个附加标志:secure、httponly、samesite=strict、samesite=lax、samesite=none。

userid_mark

如果该参数不关闭,则启用 cookie 标记机制并设置用作标记的字符。

代码语言:javascript
复制
userid_mark letter | digit | = | off;

默认值是 off 。此机制用于添加或更改 userid_p3p 和/或 cookie 过期时间,同时保留客户端标识符。标记可以是英文字母(区分大小写)、数字或“=”字符中的任何字母。

如果设置了标记,则将其与在 cookie 中传递的客户端标识符的 base64 表示中的第一个填充符号进行比较。如果它们不匹配,则使用指定的标记、到期时间和“P3P”标头重新发送 cookie。

userid_name

设置 cookie 名称。

代码语言:javascript
复制
userid_name name;

默认值就是 uid 。

userid_p3p

设置将与 cookie 一起发送的“P3P”标头字段的值。

代码语言:javascript
复制
userid_p3p string | none;

默认值是 none 。如果指令设置为特殊值 none,则不会在响应中发送“P3P”标头。

userid_path

定义为其设置 cookie 的路径。

代码语言:javascript
复制
userid_path path;

userid_service

如果标识符由多个服务器(服务)发布,则应为每个服务分配自己的编号,以确保客户端标识符是唯一的。

代码语言:javascript
复制
userid_service number;

对于版本 1 cookie,默认值为零。对于版本 2 cookie,默认值是由服务器 IP 地址的最后四个八位字节组成的数字。

变量

  • $uid_got cookie 名称和接收到的客户端标识符。
  • $uid_reset 如果变量设置为非“0”的非空字符串,则重置客户端标识符。特殊值“log”还导致将有关重置标识符的消息输出到 error_log。
  • $uid_set cookie 名称和发送的客户端标识符。

标识测试

上面很多配置其实都是和 Cookie 相关的,我们就不演示了,直接简单地测试一下生成 userid 就好了。

代码语言:javascript
复制
log_format userid 'uid_got=$uid_got uid_reset=$uid_reset uid_set=$uid_set';
server{
  # .....
  location /userid/ {
    alias html/;
    access_log logs/35.userid.log userid;
    
    userid on;
    userid_name uuid;
    set $uid_reset $arg_reset;
  }
}

这段配置中,我们打开了 userid ,然后将 cookie 名称设置为 uuid ,如果不设置的话,默认就是 uid 这个名称。然后根据请求传递过来的 reset 参数设置 $uid_reset 变量的值。

访问页面后,在响应头中会看到下面这样的内容。

代码语言:javascript
复制
Set-Cookie: uuid=wKg4WGMqcy5L3RLuAwMDAg==; path=/

现在明白是啥意思了吧。然后我们再次刷新页面,不会有新的 Cookie 响应回来了,接着给请求添加一个参数,比如:/userid/?reset=1 ,就会发现又返回了 uuid 的 Cookie 信息。

然后查看日志,第一次请求是这样的。

代码语言:javascript
复制
// 35.userid.log
uid_got=uuid=5838A8C0CA692A638C123F2402030303 uid_reset= uid_set=-

使用 uid_reset 之后,uid_set 值也发生了变化。

代码语言:javascript
复制
// 35.userid.log
uid_got=uuid=5838A8C0CA692A638C123F2402030303 uid_reset=1 uid_set=uuid=5838A8C02E732A63EE12DD4B02030303

反解 UserID 的编码内容

大家有没有发现一个问题,上面实际 Cookie 返回的 ID 标识和日志中记录的是不一样的,一个是明显的 Base64 编码,另一个则不太清楚是什么编码。但其实我们也能想到,这个编码和 Base64 那个编码的结果是相对应的。那么,咱们就从 Base64 的编码结果入手,网上有使用 PHP 解码的资料,直接拿过来用。

代码语言:javascript
复制
<?php
$str = 'wKg4WGMqdEZMHhLsAwMJAg==';
$hash = unpack('N*', base64_decode(str_replace(' ',  '+', $str)));

print_r($hash);

$server_ip = long2ip($hash[1]);
$time = date('Y-m-d H:i:s', $hash[2]);
echo $server_ip,',',$time;

// 输出结果
Array
(
    [1] => 3232249944
    [2] => 1663726662
    [3] => 1277039340
    [4] => 50530562
)
192.168.56.88,2022-09-21 02:17:42

可以看出,解码之后再用 unpack() 进行二进制解码,就会获得一个数组。数组的第一个元素是服务器的 IP 地址,第二个参数是生成 userid 时的时间戳。那么第三第四个是什么内容呢?第一次,也是唯一一次,咱们去看一下 Nginx 的源码吧。

代码语言:javascript
复制
// nginx-1.23.0/src/http/modules/ngx_http_userid_filter_module.c 592-638行

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

注意,上面的源码我们看的是 v2 ,也就是 userid on 这种形式的 userid 生成形式,如果使用 v1 的话,则代码是在这段代码的上面,略有不同。从这段代码中我们可以再继续查找第 3 个和第 4 个元素是什么内容。

第3个,这里看到是 start_value 这个变量,继续查找这个变量是在哪里定义的。从源码中可以找到,它是在 ngx_http_userid_init_worker 函数中被赋值的,也是源码中最底下的函数。

代码语言:javascript
复制
start_value = (((uint32_t) tp.tv_usec / 20) << 16) | ngx_pid;

tp.tv_usec 是 Linux 运行的微秒计时器,通过 tp.tv_usec 的位移操作,并与 nix_pid 进行或运算,获得第三个元素的值。

第4个,变量名为 sequencer_v2 ,其实是个递增数字,从 0x03030302 开始,每次增加 0x100,上面解码的内容中,50530562 转 16进制后正是 3030902 这是我快速的刷新了 9 次之后的结果。注意这个元素并不是一直累加,而是根据上面三个参数,如果三个参数相同,才会增加。如果使用 v1 格式的话,则是从十进制数字 1 开始。

至于这个数组里面的内容又是如何与上面日志中的内容匹配上的呢?直接将我们打印出来的数组的第一个元素 3232249944 转换成十六进制,结果为 C0A83858 ,再和日志中 $uid_set 核对一下,看看找到规律了没?

总结

不容易啊,我们竟然去看了 Nginx 的源码,以我这从来没系统学过 C++ 的水平。还好,现代化的编程语言很多东西都是相通的,基础逻辑部分还是能够简单看明白的。但如果要说得更详细就真是力不从心了。

总体来说,运行信息这个模块配置还是很有用的,也能够集成到 Zabbix 等运维软件中。响应修改这个其实用得不多,毕竟大部分情况下我们会直接修改源码了。最后的用户标识模块,场景是有,但是相对客户端生成来说,可能并不是特别的灵活。但如果想要做到统一,比如说不管是苹果还是安卓,甚至是 H5 页面,都使用统一的 userid 的话,直接服务端生成 userid 肯定是最好的选择。

参考文档:

http://nginx.org/en/docs/http/ngx_http_stub_status_module.html

http://nginx.org/en/docs/http/ngx_http_sub_module.html

http://nginx.org/en/docs/http/ngx_http_userid_module.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-10-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农老张 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Nginx学习:运行信息、响应修改及用户标识模块
    • Nginx 运行信息
      • stub_status
        • 变量
      • 修改响应
        • sub_filter
        • sub_filter_last_modified
        • sub_filter_once
        • sub_filter_types
        • 修改响应测试
      • UserID 客户端用户标识
        • userid
        • userid_domain
        • userid_expires
        • userid_flags
        • userid_mark
        • userid_name
        • userid_p3p
        • userid_path
        • userid_service
        • 变量
        • 标识测试
        • 反解 UserID 的编码内容
      • 总结
      相关产品与服务
      腾讯云服务器利旧
      云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档