跳转至

sorted-args: NGINX HTTP 查询字符串参数规范化

需要 GetPageSpeed NGINX Extras 订阅的专业计划(或更高版本)。

安装

您可以在任何基于 RHEL 的发行版中安装此模块,包括但不限于:

  • RedHat Enterprise Linux 7、8、9 和 10
  • CentOS 7、8、9
  • AlmaLinux 8、9
  • Rocky Linux 8、9
  • Amazon Linux 2 和 Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-sorted-args
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-sorted-args

通过在 /etc/nginx/nginx.conf 顶部添加以下内容来启用该模块:

load_module modules/ngx_http_sorted_args_module.so;

本文档描述了 nginx-module-sorted-args v3.0.1,于 2026 年 5 月 3 日发布。


一个强大的 NGINX 模块,通过按字母数字顺序对 HTTP 请求查询字符串参数进行规范化。该模块提供了一致的、规范的查询字符串表示,无论原始参数顺序如何,使其非常适合缓存键生成、请求去重和 URL 规范化。

概述

不同顺序的相同查询参数的不同 URL 将生成相同的规范化查询字符串:

  • /index.php?b=2&a=1&c=3
  • /index.php?b=2&c=3&a=1
  • /index.php?c=3&a=1&b=2
  • /index.php?c=3&b=2&a=1

上述所有内容将生成相同的规范化查询字符串:a=1&b=2&c=3

此规范化可以通过 $sorted_args 变量访问,该变量可用于缓存键、日志记录和其他 NGINX 上下文。

特性

  • 按键自然排序 查询参数,然后按值排序(例如,item2 < item10
  • 空值剥离 — 像 ?a= 这样的参数会被自动移除
  • 黑名单模式 (sorted_args_ignore_list) 用于从排序输出中排除特定参数
  • 白名单模式 (sorted_args_allow_list) 仅保留特定参数,丢弃所有其他参数
  • 通配符模式 — 使用 utm_* 匹配所有 UTM 参数,*_id 匹配后缀
  • 去重 (sorted_args_dedupe) — 仅保留重复键的第一个或最后一个出现
  • 可选的 $args 覆盖 自动用排序参数替换原始查询字符串
  • 位置级配置,继承自服务器和主上下文
  • 高效实现,使用 NGINX 的原生队列排序
  • 不区分大小写 的参数名称匹配以进行过滤
  • 重复检测 在过滤列表中

配置

变量

$sorted_args

返回按参数名称字母数字顺序排序的查询字符串参数,然后按值排序。参数用 & 连接,并保持其原始 URL 编码。

示例:

请求: /page?zebra=1&apple=2&banana=3
$sorted_args: apple=2&banana=3&zebra=1

行为: - 空查询字符串返回空字符串 - 没有值的参数(例如,?param)作为 param 包含 - 具有空值的参数(例如,?param=)会被自动剥离 - 同一参数的多个值会单独排序 - 自然排序:p=1p=2p=10 正确排序(不是 p=1p=10p=2) - 参数名称的不区分大小写排序

指令

sorted_args_ignore_list

语法: sorted_args_ignore_list pattern [pattern ...];

默认值:

上下文: httpserverlocationif

描述:

指定一个或多个模式,以从 $sorted_args 变量中排除(黑名单模式)。这对于在保留其他参数的同时,从缓存键中删除缓存破坏参数(如时间戳、版本号或跟踪 ID)非常有用。

模式类型: - name — 精确匹配(不区分大小写) - name* — 前缀匹配(匹配 namename_fooname123 等) - *name — 后缀匹配(匹配 foo_namebar_name 等) - *name* — 包含匹配(匹配任何包含 name 的参数)

列表中的重复模式会被自动移除。

示例:

location /api {
    # 过滤精确名称和所有 utm_* 跟踪参数
    sorted_args_ignore_list timestamp version _ utm_* fb_*;

    proxy_cache_key "$uri$sorted_args";
    proxy_pass http://backend;
}

在此示例中,请求 /api?user=123&timestamp=1234567890&utm_source=google&utm_medium=cpc 将生成 $sorted_argsuser=123,同时过滤掉 timestamp 和所有 UTM 参数。

sorted_args_allow_list

语法: sorted_args_allow_list pattern [pattern ...];

默认值:

上下文: httpserverlocationif

描述:

指定一个或多个模式以 保留$sorted_args 变量中(白名单模式)。所有不匹配任何模式的参数将被排除。这在您想严格控制允许哪些查询参数通过以进行缓存时非常有用。

模式类型: - name — 精确匹配(不区分大小写) - name* — 前缀匹配(匹配 namename_fooname123 等) - *name — 后缀匹配(匹配 foo_namebar_name 等) - *name* — 包含匹配(匹配任何包含 name 的参数)

当同时配置 sorted_args_allow_listsorted_args_ignore_list 时,首先应用白名单(仅保留允许的参数),然后应用黑名单以过滤掉任何剩余的不需要的参数。

示例:

location /api {
    # 仅允许分页和排序参数
    sorted_args_allow_list page* sort* limit;

    proxy_cache_key "$uri$sorted_args";
    proxy_pass http://backend;
}

在此示例中,请求 /api?page=1&page_size=10&sort=asc&timestamp=123 将生成 $sorted_argslimit=10&page=1&page_size=10&sort=asctimestamp 参数被丢弃,因为它不匹配任何模式。

sorted_args_overwrite

语法: sorted_args_overwrite on | off;

默认值: off

上下文: httpserverlocationif

描述:

启用时,此指令自动用排序(并可选过滤)的查询参数覆盖内置的 $args 变量。当您希望所有下游处理(代理、日志记录、重定向)使用规范化的查询字符串而无需显式引用 $sorted_args 时,这非常有用。

覆盖发生在重写阶段,因此所有后续阶段将看到 $args 中的排序参数。

示例:

location /api {
    sorted_args_overwrite on;
    sorted_args_ignore_list timestamp version;

    # $args 现在自动排序和过滤
    proxy_pass http://backend$uri?$args;
}

在此示例中,请求 /api?z=1&a=2&timestamp=123 将被代理为 /api?a=2&z=1 — 已排序并过滤掉 timestamp

sorted_args_dedupe

语法: sorted_args_dedupe first | last | off;

默认值: off

上下文: httpserverlocationif

描述:

控制如何处理重复的参数键。当多个参数具有相同的键(例如,?a=1&a=2&a=3)时,此指令确定保留哪个值。

  • first — 仅保留每个键的第一个出现
  • last — 仅保留每个键的最后一个出现
  • off — 保留所有出现(默认行为)

这对于规范化可能多次指定相同参数的 URL 非常有用,确保缓存键一致。

示例:

location /search {
    sorted_args_dedupe first;

    proxy_cache_key "$uri$sorted_args";
    proxy_pass http://backend;
}

在此示例中,请求 /search?q=foo&q=bar&q=baz 将生成 $sorted_argsq=foo,仅保留第一个值。使用 sorted_args_dedupe last,它将生成 q=baz

使用示例

基本缓存键规范化

规范化缓存键,无论参数顺序如何:

http {
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m;

    server {
        listen 80;

        location / {
            proxy_cache my_cache;
            proxy_cache_key "$scheme$host$uri$sorted_args";
            proxy_pass http://backend;
        }
    }
}

自动参数覆盖

自动用排序参数重写 $args 以进行所有下游处理:

server {
    listen 80;

    location /api {
        sorted_args_overwrite on;
        sorted_args_ignore_list timestamp _;

        # 所有这些现在自动使用排序、过滤的参数
        proxy_pass http://backend;
        # 相当于: proxy_pass http://backend$uri?$sorted_args;
    }
}

过滤缓存破坏参数

从缓存键中移除时间戳和跟踪参数:

location /static {
    sorted_args_ignore_list _ t timestamp v version;

    proxy_cache zone;
    proxy_cache_key "$uri$sorted_args";
    proxy_pass http://cdn;
}

白名单模式(仅允许特定参数)

当查询参数可能导致大量服务器端处理时,使用白名单严格控制允许哪些参数通过以进行缓存:

location /search {
    # 仅这些参数影响缓存键;所有其他参数都被丢弃
    sorted_args_allow_list q page limit category;

    proxy_cache zone;
    proxy_cache_key "$uri$sorted_args";
    proxy_pass http://search_backend;
}

在此示例中,请求 /search?q=nginx&page=1&debug=true&nocache=1 将仅使用 category=&limit=&page=1&q=nginx 进行缓存,有效地忽略任何缓存破坏或调试参数。

结合白名单和黑名单

您可以同时使用这两个指令以实现更细粒度的控制。首先应用白名单,然后应用黑名单:

location /api {
    # 首先,仅保留这些参数
    sorted_args_allow_list user_id action page limit timestamp;

    # 然后,从允许的集合中移除时间戳
    sorted_args_ignore_list timestamp;

    proxy_cache zone;
    proxy_cache_key "$uri$sorted_args";
    proxy_pass http://api_backend;
}

记录规范化查询字符串

在访问日志中包含排序的查询字符串:

http {
    log_format detailed '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent" '
                        'args="$args" sorted_args="$sorted_args"';

    server {
        access_log /var/log/nginx/access.log detailed;
        # ...
    }
}

特定位置过滤

不同位置可以有不同的过滤列表:

server {
    # 默认:过滤常见跟踪参数
    sorted_args_ignore_list _ utm_source utm_medium utm_campaign;

    location /api {
        # API:还过滤版本和时间戳
        sorted_args_ignore_list _ utm_source utm_medium utm_campaign version t;
        proxy_pass http://api_backend;
    }

    location /content {
        # 内容:仅过滤跟踪
        proxy_pass http://content_backend;
    }
}

完整示例

pid         logs/nginx.pid;
error_log   logs/error.log warn;

worker_processes  auto;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" "$http_user_agent" '
                    'args="$args" sorted_args="$sorted_args"';

    access_log  logs/access.log  main;

    proxy_cache_path /tmp/cache
                     levels=1:2
                     keys_zone=zone:10m
                     inactive=10d
                     max_size=100m;

    server {
        listen       8080;
        server_name  localhost;

        # 过滤跟踪和缓存破坏参数
        location /filtered {
            sorted_args_ignore_list v _ time timestamp;

            proxy_set_header Host "backend";
            proxy_pass http://localhost:8081;

            proxy_cache zone;
            proxy_cache_key "$uri$sorted_args";
            proxy_cache_valid 200 1m;
        }

        # 使用排序参数而不过滤
        location / {
            proxy_pass http://localhost:8081;

            proxy_cache zone;
            proxy_cache_key "$uri$sorted_args";
            proxy_cache_valid 200 10m;
        }
    }

    # 用于测试的后端服务器
    server {
        listen       8081;

        location / {
            return 200 "args: $args\nsorted_args: $sorted_args\n";
        }
    }
}

工作原理

  1. 参数提取:模块从 r->args 中解析查询字符串,按 &= 拆分
  2. 队列构建:每个参数以其键、值和完整的键值对存储在队列结构中
  3. 排序:使用自然比较函数对参数进行排序:
  4. 主排序:参数名称(不区分大小写,自然顺序)
  5. 次排序:完整参数字符串(key=value,自然顺序)
  6. 自然顺序意味着嵌入的数字按数值比较:item2 < item10
  7. 空值剥离:具有 = 但没有值的参数(如 ?a=)会被移除
  8. 白名单过滤:如果配置了 sorted_args_allow_list,仅保留匹配模式的参数
  9. 黑名单过滤:匹配 sorted_args_ignore_list 中模式的参数被排除
  10. 去重:如果启用了 sorted_args_dedupe,仅保留每个键的第一个或最后一个出现
  11. 重构:将排序、过滤后的参数用 & 连接形成最终字符串

测试

该项目使用 Test::Nginx 进行其测试套件,运行在 Docker 中以实现可重复构建。

先决条件

  • Docker

运行测试

运行完整的测试套件:

make tests

运行特定的测试文件:

make tests T=t/sorted_args.t

禁用 HUP 模式以进行调试(较慢但更独立):

make tests HUP=0

使用不同的 NGINX 版本:

make tests NGINX_VERSION=release-1.26.2

在测试容器中打开交互式 shell 以进行调试:

make shell

测试覆盖率

测试套件验证: - ✅ 基本排序功能 - ✅ 自然/数字排序(例如,p=1p=2p=10 按正确顺序) - ✅ 类数组参数(例如,c[]=1&c[]=2) - ✅ 黑名单过滤(sorted_args_ignore_list) - ✅ 白名单过滤(sorted_args_allow_list) - ✅ 结合使用白名单和黑名单 - ✅ 通配符模式:前缀(utm_*)、后缀(*_id)、包含(*token*) - ✅ 去重:sorted_args_dedupe firstlast - ✅ 空查询字符串处理 - ✅ 空值剥离(?a=&b=2b=2) - ✅ 缓存键使用 - ✅ 位置级配置继承 - ✅ sorted_args_overwrite 指令 - ✅ 不区分大小写的参数匹配(白名单和黑名单均如此) - ✅ 无值参数与空值参数 - ✅ 重复参数处理 - ✅ 特殊字符保留 - ✅ 格式错误的查询字符串(连续的 & 符号) - ✅ 带有多个等号的参数 - ✅ E2E:在缓存键评估之前修改 $args(重写阶段时机) - ✅ E2E:重新排序的参数生成相同的缓存键 - ✅ E2E:重写指令看到被覆盖的 $args

性能考虑

  • 排序的查询字符串每个请求计算一次,并缓存于请求上下文中
  • 排序使用 NGINX 高效的基于队列的算法
  • 过滤使用不区分大小写的字符串比较
  • 内存分配来自请求池,因此无需显式清理

限制

  • 参数值未解码/编码;保留原始编码
  • 过滤对参数名称不区分大小写,但在输出中保留原始大小写
  • 具有空值的参数(例如,?a=)始终被剥离;使用 ?a(无等号)作为标志