Anubis 反向代理的分号查询参数兼容:gitweb 烟雾测试与 Rewrite 模式迁移实战 📅 发布时间:2026/9/20 21:05:12 👁 浏览次数: Anubis 反向代理的分号查询参数兼容gitweb 烟雾测试与 Rewrite 模式迁移实战【免费下载链接】anubisWeighs the soul of incoming HTTP requests to stop AI crawlers项目地址: https://gitcode.com/gh_mirrors/anubis4/anubis导读本篇技术指南围绕仓库中 test/gitweb/README.md 所记录的 smoke test 展开核心主题是Anubis 在 v1.26.0 从 Go 标准库httputil.ReverseProxy.Director已废弃迁移到Rewrite模式后如何保证以分号;分隔 URL 查询参数的旧式 Web 应用典型如 gitweb 的/?prepo.git;asummary仍然能够正常反代。读完本文你将理解Rewrite模式重编码查询串的底层行为、它为何会静默丢弃;参数以及 Anubis 通过端到端烟雾测试与源码级修复双重手段守住这一兼容性的完整实践。背景gitweb 与它的世纪之交遗留行为gitweb 是 Git 官方生态中的 Perl CGI 脚本为浏览器提供 git 仓库的 Web 界面。它诞生于世纪之交a CGI script from before the turn of the century因此带着一个独特的遗留行为使用分号;作为 URL 查询参数分隔符例如请求一个仓库的摘要页时使用/?ptesting.git;asummary在标准 HTTP 实践中查询参数的分隔符是如?a1b2但早期 CGI 应用常以;分隔。这个行为在黑客圈子里还有一种特殊地位它曾是 WAF 绕过技术——一些配置不佳的 Web 应用会接受分号分隔参数而 WAF 只检查分隔符导致规则失明。gitweb 正是这类按分号解析参数的古老应用之一。无论历史成因如何一个反代在转发请求到 gitweb 时必须原样保留请求行中的原始查询串raw query否则 gitweb 就读不到ptesting.git这个参数。问题根源Director 到 Rewrite 的迁移Anubis 在 v1.26.0 时将目标上游反向代理从已废弃的httputil.ReverseProxy.Director迁移到了Rewrite。这一迁移的背景是 Go 1.26 兼容性与 API 现代化见 docs/docs/CHANGELOG.md 中 v1.26.0 的变更记录。Go 官方对Rewrite的定位是在正确性和遵循 HTTP 实践方面更激进——它严格按照今天 HTTP 世界的规范处理请求而不是迁就多年前的旧行为。其中一项激进行为正是Rewrite 模式在构造出站请求时会用url.ParseQuery重新编码查询字符串从而静默丢弃以;分隔的参数。后果是灾难性的当 Anubis 把/?ptesting.git;asummary转发给 gitweb 时如果参数被丢弃gitweb 将收不到ptesting.git于是回退到根路径/渲染项目列表页。用户看到的是一个摘要页实为首页的错误响应——请求悄悄失败了。这个问题被记录为 docs/docs/CHANGELOG.md 中 v1.26.1 的修复项修复从ReverseProxy.Director已废弃迁移到ReverseProxy.Rewrite时丢失的分号分隔查询参数支持重新启用对 gitweb 这类上游的支持并添加功能测试防止复发。烟雾测试设计如何证明分号参数没被丢掉防复发的手段就是 test/gitweb/README.md 所描述的这套烟雾测试。它的聪明之处在于利用 gitweb 的失败模式本身作为断言依据如果 Anubis 丢掉了分号参数gitweb 的summary响应会逐字节等同于首页/的响应因为两者都回退到项目列表渲染因此测试同时断言两件事/?ptesting.git;asummary返回的正文不等于首页正文——证明参数确实到达了 gitweb摘要页正文中包含指向/?ptesting.git;acommit的链接片段——证明 gitweb 真的渲染了该仓库而不是兜底页面。具体实现见 test/gitweb/test.mjs它以固定 UAMozilla/5.0 (compatible; AnubisGitwebSmoke/1.0)请求http://localhost:8005下的首页与摘要页逐个断言状态码为 200、正文互不相同、且包含期望的 commit 链接片段任一断言失败即process.exit(1)。测试通过redirect: manual防止自动跟随重定向确保挑战流程之外的响应语义得到严格检查。测试环境搭建docker-compose 三服务编排烟雾测试跑在一个 test/gitweb/docker-compose.yaml 定义的本地环境里包含三个服务服务镜像作用anubisko.local/anubis被测反代监听:8005目标指向http://gitweb:80repojgiannuzzi/gitolitegitolite 仓库托管通过共享卷repo-data向 gitweb 暴露 git 裸仓库gitwebmlan/gitweb真实 gitweb 上游depends_on: repo以只读方式挂载仓库卷Anubis 侧的关键环境变量BIND: :8005绑定端口与 test.mjs 中的BASE一致TARGET: http://gitweb:80上游目标测试中即 gitwebUSE_REMOTE_ADDRESS: true信任真实远端地址本地测试场景POLICY_FNAME: /etc/techaro/anubis.yaml策略文件挂载路径。策略文件 test/gitweb/anubis.yaml 极简但完整定义了唯一一条规则bots: - name: challenge user_agent_regex: CHALLENGE action: CHALLENGE status_codes: CHALLENGE: 200 DENY: 403user_agent_regex: CHALLENGE是 Anubis 的无匹配默认动作语法所有不匹配任何特定规则的请求都落入CHALLENGE动作此处在策略模型中默认对陌生 UA 执行挑战而测试 UA 不携带任何已被放行的 cookie从而可以验证挑战通过前的请求仍能被正确反代到上游这一语义。status_codes段则显式声明了 CHALLENGE 响应应返回 200、DENY 返回 403保证测试脚本对状态码的断言与策略语义一致。测试执行流程test.sh 与指数退避重试test/gitweb/test.sh 是整个测试的驱动器流程为设置VERSION与KO_DOCKER_REPOko.localsource ../lib/lib.sh调用build_anubis_ko用 ko 构建 Anubis 容器镜像该函数定义在 test/lib/lib.sh会先npm ci npm run assets生成 Web 资产再ko build ./cmd/anubis --localdocker compose up -d拉起三服务backoff-retry node ./test.mjs运行烟雾测试。最后一步的backoff-retry是仓库自带的工具 utils/cmd/backoff-retry/main.go默认从 250ms 起步、最多重试 5 次、每次失败等待时间翻倍用于容忍容器启动阶段的短暂竞态。整个lib.sh还通过trap cleanup EXIT SIGINT保证测试结束后清理 compose 资源。源码深处的修复makeReverseProxy 的原始查询串还原测试所守护的修复实现位于 cmd/anubis/main.go 的makeReverseProxyL140-L216。该函数手工构造httputil.ReverseProxy并自定义Rewrite回调源码注释完整记载了修复动机Rewrite 模式会通过url.ParseQuery重新编码出站查询从而静默丢弃以;分隔的参数。某些上游典型如 gitweb 的/?prepo.git;asummary使用;作为查询分隔符因此这里将客户端的原始查询串逐字还原以匹配此前NewSingleHostReverseProxy的行为。这修复了 issue #1763。核心逻辑是if tq : targetUri.RawQuery; tq || r.In.URL.RawQuery { r.Out.URL.RawQuery tq r.In.URL.RawQuery } else { r.Out.URL.RawQuery tq r.In.URL.RawQuery }即目标 URL 自带的查询串与客户端原始查询串按需以拼接且客户端部分逐字保留r.In.URL.RawQuery不被解析重编码从根上规避了ParseQuery丢弃分号参数的问题。同一回调中还包含两个与迁移配套的细节保留入站 Hostr.Out.Host r.In.Host。注释说明SetURL会清空Out.Host此处恢复入站 Host 以匹配旧NewSingleHostReverseProxy的默认行为除非显式设置了targetHost透传转发头Rewrite 模式在回调执行前会剥离转发头而 Anubis 在上游由 internal/headers.go 的XForwardedForUpdate等中间件负责设置Forwarded、X-Forwarded-For、X-Forwarded-Host、X-Forwarded-Proto因此回调中将这些头从入站请求拷贝到出站请求保证目标仍能看到完整的转发信息。单元测试从源头锁定行为除端到端烟雾测试外同层修复还有单元测试把关cmd/anubis/main_test.go 的TestMakeReverseProxy用httptest起了一个回显服务器向代理发起请求并断言上游收到的Host、路径、查询串与转发头。其中专门有一个用例name: semicolon-delimited query is preserved, reqPath: /?ptesting.git;asummary,该用例直接断言上游收到的RawQuery与客户端原始请求一致——它把分号参数不被丢弃从端到端场景下沉为可独立运行的代理层契约任何未来对Rewrite回调的改动一旦破坏此行为单测会立即失败。从测试到生产给你的反代场景带来的启示这套测试对自建反代或网关的维护者有直接借鉴意义升级 Go 标准库反代 API 时行为差异清单要提前盘点Director与Rewrite在查询串、Host、转发头处理上的差异是真实存在的坑迁移前应像 Anubis 一样用单测锁定每一条语义针对遗留上游设计失败特征断言gitweb 的丢参数即回退首页特征让测试可以仅凭正文比较就做出判定无需 mock 内部实现——这是端到端测试中非常实用的技巧双层防线单元测试锁源码行为cmd/anubis/main_test.go烟雾测试验证真实上游链路test/gitweb/test.mjs修复 #1763 正是依靠这一组合防止回归。如果你正在部署 Anubis 并需要为 gitweb、或任何使用;分隔查询参数的旧式 CGI 应用做反代本仓库的修复与测试就是现成的兼容性保障从 cmd/anubis/main.go 的makeReverseProxy可以看到如何逐字透传原始查询串从 test/gitweb/ 的整套测试可以看到如何持续验证这一行为不再复发。【免费下载链接】anubisWeighs the soul of incoming HTTP requests to stop AI crawlers项目地址: https://gitcode.com/gh_mirrors/anubis4/anubis创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考