GoReplay 中间件(Middleware)实战指南:自定义 HTTP 流量改写、过滤与协议对接
性能测试可观测性网络【免费下载链接】goreplayGoReplay is an open-source tool for capturing and replaying live HTTP traffic into a test environment in order to continuously test your system with real data. It can be used to increase confidence in code deployments, configuration changes and infrastructure changes.项目地址https://gitcode.com/gh_mirrors/go/goreplay点击查看免费下载GoReplay 的中间件Middleware机制允许你编写任意语言的独立程序通过标准输入输出STDIN/STDOUT接入流量链路对捕获到的 HTTP 请求与响应进行实时改写、过滤、数据脱敏或协议适配。本文以官方文档 docs/Middleware.md 为主体结合仓库源码与多语言示例完整讲解中间件的通信协议、启用方式、Node.js 辅助库、token 映射等高级用例帮助你在真实回放场景中落地自定义逻辑。中间件是什么Gor 流量链路上的可编程节点在 GoReplay 中中间件是一个独立于 Gor 主进程运行的程序它从STDIN接收请求与响应负载处理后在STDOUT发出修改后的请求。因为读写都走标准 IO中间件可以用任何语言编写只要该语言能读写标准输入输出即可例如 Bash、Python、Ruby、Node.js、Clojure 或 Go仓库中的 examples/middleware 目录提供了上述全部语言的示例。中间件适合实现这类自定义逻辑剥离隐私数据如 Authorization、Cookie 中的敏感字段基于请求/响应内容的高级改写如动态 token 映射、URL 路径重写对接 oAuth 等认证体系的请求适配按自定义规则过滤流量丢弃不需要回放的请求。官方文档给出了中间件在流量链路中的位置ASCII 架构图原样复刻如下Original request -------------- -----------------------STDIN---------- | | Gor input | | Middleware | -----------------------STDIN---------- | Original response (1) ------------ | ^ ------------- Modified request v | | Gor output ---------STDOUT----------------- | ------------ | | | | Replayed response | ------------------STDIN---------------------其中需要注意两点(1) 原始响应Original response只有在指定--input-raw-track-response选项时才会被发送到中间件想要拿到回放响应Replayed response需要同时启用--output-http-track-response选项见 middleware/README.md。启用中间件--middleware 选项中间件通过 Gor 的--middleware命令行选项启用值为可执行文件的路径支持带参数的命令。该选项在 settings.go 中定义gor --input-raw :80 --middleware /opt/middleware_executable --output-http http://staging.server即抓取 80 端口的真实流量 → 送入中间件程序处理 → 将处理后的请求回放到 staging 服务器。与中间件强相关的两个追踪选项也在 settings.go 与 settings.go 中声明选项默认值说明--input-raw-track-responsefalse开启后 Gor 在请求之外同时追踪响应响应会提供给中间件和文件输出--output-http-track-responsefalse开启后 HTTP 输出的响应会提供给 stdout、file 等所有输出含中间件--output-binary-track-responsefalseBinary 输出的响应同样可被追踪并交给中间件在编写中间件前先确认选项字面值与测试用例保持一致仓库 middleware_test.go 中被注释的TestTokenMiddleware展示了测试载荷里1/2/3三种消息类型的编排方式可用于自测中间件行为。源码视角中间件进程如何被拉起中间件的实现位于 middleware.go。NewMiddleware会把你传入的字符串按空格拆分用exec.CommandContext启动子进程middleware.go子进程的StdoutPipe挂在m.StdoutGor 从这里读回处理结果子进程的StdinPipe挂在m.StdinGor 从这里写入消息子进程的Stderr直接连接到 Gor 自身的os.Stderr所以中间件内日志请写到 STDERR避免污染 STDOUT 上的数据通道。消息写入子进程的编码逻辑在copy方法middleware.go每条消息把Meta元信息与DataHTTP 负载拼接后整体做 hex 编码末尾追加\n换行符作为消息结束标志。读取侧read方法middleware.go则逐行读取、hex.Decode解码后拆分为Meta与Data两部分。这也从源码上印证了文档所述协议消息为 hex 编码每行一条解码后由「header元信息」和「HTTP payload」两部分组成二者以换行符分隔。通信协议详解hex 编码、元信息头与 payload 类型消息格式所有消息必须hex 编码换行符表示一条消息的结束即每行一条消息。解码后的负载由两部分组成中间以换行符分隔Header元信息行空格分隔的请求元信息HTTP payload未经修改的、从网络中截获的原始 HTTP 请求/响应。请求 payload 示例1 932079936fa4306fc308d67588178d17d823647c 1439818823587396305 GET /a HTTP/1.1 Host: 127.0.0.1响应 payload 示例2 8e091765ae902fef8a2b7d9dd960e9d52222bd8c 1439818823587996305 2782013 HTTP/1.1 200 OK Date: Mon, 17 Aug 2015 13:40:23 GMT Content-Length: 0 Content-Type: text/plain; charsetutf-8注意只有指定--input-raw-track-response时才会收到响应消息。Header 各字段含义元信息行按空格分隔字段含义如下位置含义说明第 1 个值payload 类型1– 请求request2– 原始响应original response3– 回放响应replayed response第 2 个值请求 ID对所有请求全局唯一是时间戳与 Ack 的 sha1但原始响应、回放响应与请求使用同一个 ID因此可据此在请求与响应之间建立关联第 3 个值时间戳请求发起/响应收到的时间第 4 个值延迟 latency仅响应消息存在表示请求开始到完成的时间差请求 ID 是建立「请求 ⇆ 原始响应 ⇆ 回放响应」三方关联的关键官方推荐的 token 映射方案正是依赖它实现的见下文高级示例。HTTP payload 处理规则HTTP payload 是网络截获的原始 HTTP 报文你可以像编辑字符串一样对它做任意操作增删 Header、修改路径、替换 Body 等只需确保修改结果仍然RFC 兼容。三个关键约束处理结束无论是否修改都必须把消息重新 hex 编码后写回 STDOUT且保留原始 header元信息行不变如果希望过滤掉某条请求直接不把它写回 STDOUT 即可对于响应消息即使未做任何修改也必须原样回写Emitting responses back is required, even if you did not touch them。另外GoReplay 与中间件的通信是完全异步的同一请求的原始请求、响应消息不保证先后顺序。如果业务逻辑依赖原始响应或回放响应的顺序中间件程序必须自行维护状态如用请求 ID 做 key 的缓存表examples/middleware/token_modifier.go 就是这种做法的完整范例。最简单的中间件Bash echo官方文档给出的最小中间件Bash 实现原样透传不做任何修改while read line; do echo $line done即逐行读取 hex 消息原样输出回 STDOUT。更完整的 Bash 示例见 examples/middleware/echo.sh它利用xxd工具做 hex 解码/编码按 header 首字符区分消息类型并打日志再原样编码回写。注意脚本中对GOR_TEST环境变量的判断——日志一律写入 STDERR2 echo因为 STDOUT/STDIN 已被数据通信占用。仓库内 middleware_test.go 的TestMiddlewareEarlyClose正是把 echo.sh 当作测试中间件验证消息经中间件往返后Data字节保持完全一致。多语言实现示例仓库的 examples/middleware 目录提供了多种语言的 echo 类中间件可作编写范本语言文件要点Node.jsexamples/middleware/echo.js用readline逐行读取实现按消息类型/请求 ID 过滤如屏蔽/api开头的 oAuth 请求及其响应Python 3examples/middleware/echo.pyfileinput逐行读取bytes.fromhex解码按\r\n\r\n切分 headers 与 body日志写 STDERRRubyexamples/middleware/echo.rbSTDIN.gets逐行读取[data].pack(H*)解码、unpack(H*)编码回写Clojureexamples/middleware/echo.clj基于 Apache Commons Codec 的Hex类编解码transform-http-msg函数即自定义逻辑的注入点Goexamples/middleware/token_modifier.go完整可运行的 token 映射中间件见下文高级示例用bufio.Scanner读 STDIN、encoding/hex编解码这些示例共同印证了协议的核心任何能读写标准 IO 的语言都能成为中间件。Node.js 中间件开发包为了简化 Node.js 场景下的中间件开发仓库提供了 middleware/middleware.js 辅助库发布为 npm 包goreplay_middlewaremiddleware/README.md 对其用法做了完整说明。初始化与事件模型var gor require(goreplay_middleware); // init 会初始化 STDIN 监听器 gor.init();核心思想是编写回调函数响应事件事件类型有四种request—— 请求对应 payload 类型1response—— 原始响应对应2replay—— 回放响应对应3message—— 所有消息都触发。回调通过gor.on(event, callback)注册。回调收到一个data对象包含data.httpBuffer类型的原始 HTTP payloaddata.meta长度为 4 的数组依次为[类型(1|2|3), uuid, 时间戳, latency]请求的 latency 为 0响应的时间戳是请求发起时间data.ID唯一请求 ID同一请求的request/response/replay事件共享该 ID。返回语义回调末尾必须return data即使未修改请求否则该请求会被过滤掉。多个回调串联时后一个回调会收到前一个回调返回的可能已被修改的data若任一回调返回空消息即被过滤。TEST_filtermiddleware/middleware.js正是对这个行为的单元验证。// 合法事件为 request、response原始响应、replay回放响应、message全部事件 gor.on(request, function(data) { // data 包含消息及其元信息 data // Buffer 类型的原始 HTTP payload // 示例换行符特意显示 // GET / HTTP/1.1\r\n // User-Agent: Golang\r\n // \r\n data.http // meta 为长度 4 的数组 // 1. 请求类型 - 1、2 或 3对应 request、response、replay // 2. uuid - 请求唯一标识响应的 ID 与其请求相同 // 3. 请求发起时间戳对响应而言同样取请求发起时间 // 4. latency - 请求开始到结束的时间差对 request 为 0 data.meta // 唯一请求 ID同一请求的 request、response、replay 事件保持一致 data.ID // 即使没有修改请求也必须在函数末尾返回 data如果什么都不返回请求将被过滤 return data })请求与响应的关联映射on函数可以接收请求 ID 作为第二个参数从而把相关的请求与响应映射到一起。下面的示例检查原始响应与回放响应的 HTTP 状态码是否一致用于校验回放流量是否出现错误// 校验回放流量是否无错的最基本方式 gor.on(request, function(req) { gor.on(response, req.ID, function(resp) { gor.on(replay, req.ID, function(repl) { if (gor.httpStatus(resp.http) ! gor.httpStatus(repl.http)) { // 注意日志使用 STDERR它会实际输出到 Gor 的 STDOUT。 // 这是因为 STDIN 和 STDOUT 已用于进程通信。 // 也可以换成写入文件的日志器。 console.error(${gor.httpPath(req.http)} STATUS NOT MATCH: Expected ${gor.httpStatus(resp.http)} got ${gor.httpStatus(repl.http)}) } return repl; }) return resp; }) return req })searchResponses提取并映射两侧响应中的动态值对于认证系统或 XSRF 防护这类会生成唯一 token的场景token 值在原始响应和回放响应中必然不同直接改写请求无法生效。此时需要用searchResponses从两类响应中分别提取对应值建立映射后改写请求。searchResponses(requestId, regexpPattern, callback)的三个参数requestId请求 IDregexpPattern用于在响应中搜索目标值的正则必须包含捕获组callback(originalMatched, replayedMatched)同时拿到原始响应与回放响应中匹配到的值。// 对比 response 与 replayed response 的 HTTP 头并映射值 let tokMap {}; gor.on(request, function(req) { let tok gor.httpHeader(req.http, Auth-Token); if (tok tokMap[tok]) { req.http gor.setHttpHeader(req.http, Auth-Token, tokMap[tok]) } gor.searchResponses(req.ID, X-Set-Token: (\w)$, function(respTok, replTok) { if (respTok replTok) tokMap[respTok] replTok; }) return req; })searchResponses的实现见 middleware/middleware.js内部先以response 请求 ID 订阅原始响应命中正则后再以replay 请求 ID 订阅回放响应最后把两侧捕获组值交给回调。为了性能它在正则匹配前先用Buffer.indexOf做快速预筛把捕获组之前的字面前缀提取出来做 indexOf 检查避免无谓的字符串转换。文档特别提醒如果正则不含捕获组searchResponses会直接在 STDERR 报错。HTTP 负载处理 API 速查包内提供了以下处理原始 HTTP payload 的函数完整实现见 middleware/middleware.js函数说明示例init初始化中间件对象开始读取 STDINgor.init()httpPath获取请求 URL 路径gor.httpPath(req.http)httpMethod获取 HTTP 方法GET、POST 等gor.httpMethod(req.http)setHttpPath更新 URL 路径req.http gor.setHttpPath(req.http, newPath)httpPathParam获取 URL 路径参数gor.httpPathParam(req.http, queryParam)setHttpPathParam设置 URL 参数不存在则追加req.http gor.setHttpPathParam(req.http, queryParam, value)httpStatus获取响应状态码gor.httpStatus(resp.http)setHttpStatus设置响应状态码req.http gor.setHttpStatus(req.http, newStatus)httpHeaders获取全部 Header返回对象gor.httpHeaders(req.http)httpHeader获取单个 HTTP Header大小写不敏感gor.httpHeader(req.http, Content-Length)setHttpHeader设置 Header不存在则新增返回修改后的 payloadreq.http gor.setHttpHeader(req.http, X-Replayed, 1)deleteHttpHeader删除 Headerreq.http gor.deleteHttpHeader(req.http, name)httpBody获取 HTTP Bodygor.httpBody(req.http)setHttpBody设置 Body并自动修正Content-Length返回修改后的 payloadreq.http gor.setHttpBody(req.http, Buffer.from(hello!))httpBodyParam获取 POST body 参数gor.httpBodyParam(req.http, param)setHttpBodyParam设置 POST body 参数req.http gor.setHttpBodyParam(req.http, param, value)httpCookie获取 Cookiegor.httpCookie(req.http, SESSSION_ID)setHttpCookie设置 Cookie返回修改后的 payloadreq.http gor.setHttpCookie(req.http, iam, cuckoo)deleteHttpCookie删除 Cookie返回修改后的 payloadreq.http gor.deleteHttpCookie(req.http, iam)使用建议文档原话的要点完全可以使用标准Buffer函数如indexOf自行处理 HTTP payload修改 Body 后必须同步更新Content-LengthsetHttpBody会自动处理修改任何 Header 后行结束符必须是\r\n。高级实战随机 token 的动态映射token_modifier.go文档用一个完整的场景说明了中间件的威力假设认证系统会随机生成访问令牌access token后续访问安全内容都依赖该令牌。由于令牌值无法预知朴素的做法无中间件或中间件只看请求 payload必然失败——因为回放服务器有自己的令牌与源服务器不同步。解决方案是让中间件同时关注回放服务器与源服务器的响应存储originalToken → replayedToken的别名映射并改写所有携带该令牌的请求使之使用回放别名。examples/middleware/token_modifier.go 实现了上述方案可独立运行go run ./examples/middleware/token_modifier.go。其核心流程维护两张表originalTokensrequestID → 原始响应中的 token与tokenAliasesoriginalToken → replayedToken从 STDIN 逐行读取 hex 消息解码后按buf[0]判断消息类型1请求 /2原始响应 /3回放响应从 header 中取出请求 IDmeta[1]收到请求1若路径是/token记录该请求 ID 等待 token 返回否则检查 URL 中的token参数若命中tokenAliases中的映射就用proto.SetPathParam把原始 token 改写为回放别名再编码写回 STDOUT收到原始响应2若该请求 ID 被登记从响应 Bodyproto.Body中取出 token 存入originalTokens收到回放响应3根据请求 ID 找到对应原始 token将映射originalToken → replayedToken写入tokenAliases完成学习闭环。整个方案的关键支撑点同一请求的三类消息共享请求 ID协议第 2 个字段才能把「原始响应」与「回放响应」关联到同一请求上异步通信不保证顺序因此用 map 缓存状态而不是依赖到达顺序底层复用 proto/proto.go 中的Path、PathParam、SetPathParam、Body等函数如 proto/proto.go这些正是 Node.js 辅助库各http*函数的 Go 侧对应实现。token_modifier.go的process函数即一个标准的中间件状态机可作为实现任何「需要结合响应内容改写后续请求」逻辑的模板例如 CSRF token 同步、会话 ID 映射等。实战扩展HTTP 请求中的 PII 数据脱敏针对合规需求middleware/README.md 还提供了PII个人身份信息数据掩码的完整实现思路基于指定的 Header 与 JSON 路径把请求中的敏感数据替换为 Faker 生成的仿真数据。完整示例const gor require(goreplay_middleware); const faker require(faker); // 初始化中间件 gor.init(); // PII 掩码配置 const maskConfig { headers: [ { name: Authorization, type: token }, { name: X-API-Key, type: token }, { name: X-User-Email, type: email }, { name: X-User-Name, type: name }, ], jsonPaths: [ { path: $.user.email, type: email }, { path: $.user.name, type: name }, { path: $.user.phone, type: phone }, { path: $.user.address, type: address }, ], }; // 按类型生成掩码值 function maskValue(type) { switch (type) { case email: return faker.internet.email(); case name: return faker.name.findName(); case phone: return faker.phone.phoneNumber(); case address: return faker.address.streetAddress(); case token: return faker.random.alphaNumeric(32); default: return ***; } } // 掩码中间件逻辑 gor.on(message, (data) { // 掩码 Header maskConfig.headers.forEach((header) { const value gor.httpHeader(data.http, header.name); if (value) { data.http gor.setHttpHeader(data.http, header.name, maskValue(header.type)); } }); // 掩码 JSON 字段 const body gor.httpBody(data.http); if (body) { try { const jsonBody JSON.parse(body.toString()); maskConfig.jsonPaths.forEach((field) { const value eval(jsonBody${field.path.slice(1)}); if (value) { eval(jsonBody${field.path.slice(1)} maskValue(field.type)); } }); data.http gor.setHttpBody(data.http, Buffer.from(JSON.stringify(jsonBody))); } catch (error) { console.error(Error parsing JSON body:, error); } } return data; });配置与行为要点maskConfig.headers需要掩码的 Header 数组每项包含nameHeader 名与type数据类型如email、name、tokenmaskConfig.jsonPaths需要掩码的 JSON 字段数组每项包含path如$.user.email与type如email、name、phone、addressmaskValue(type)按类型用 Faker 生成仿真数据邮箱、姓名、电话、地址、32 位随机 token可通过扩展switch支持更多类型或自定义掩码策略处理流程① 遍历maskConfig.headers掩码 Header 值 → ② 解析 JSON Body若存在按jsonPaths逐字段替换为掩码值 → ③ 用setHttpBody写回自动修正Content-Length→ ④ 返回修改后的消息。该示例用message事件同时覆盖请求与响应。文档特别提示示例通过eval动态访问/修改 JSON 对象务必校验路径合法性以避免安全风险使用前需安装依赖goreplay_middleware与faker再随 GoReplay 一并运行。小结GoReplay 的中间件机制把「协议处理」与「业务逻辑」彻底解耦Gor 负责抓包、追踪、回放中间件只负责按约定的 hex 协议消费和产出消息。掌握本指南后你可以用任意语言Bash/Python/Ruby/Node.js/Clojure/Go编写中间件实现请求改写、过滤、脱敏借助--input-raw-track-response与--output-http-track-response打通「原始响应 回放响应」双向数据流基于请求 ID 实现跨请求的状态关联如 token 映射使用 Node.js 辅助库goreplay_middleware快速完成 HTTP 层的读写操作或直接阅读 middleware.go 与 proto/proto.go 理解底层编码与解析细节。如需继续深入可结合仓库内 docs/Request-filtering.md、docs/Rate-limiting.md 与 docs/Request-rewriting.md 了解内置的过滤、限速与改写能力它们与中间件共同构成完整的流量治理体系。赞分享性能测试可观测性网络【免费下载链接】goreplayGoReplay is an open-source tool for capturing and replaying live HTTP traffic into a test environment in order to continuously test your system with real data. It can be used to increase confidence in code deployments, configuration changes and infrastructure changes.项目地址https://gitcode.com/gh_mirrors/go/goreplay点击查看免费下载相关推荐bsdiff完全指南10个实用技巧快速掌握二进制文件补丁制作bsdiff完全指南10个实用技巧快速掌握二进制文件补丁制作 bsdiff是一款高效的二进制文件补丁制作工具能够帮助开发者和系统管理员快速生成和应用文件差异开发工具CLISwin Transformer v2核心架构解析窗口注意力机制的终极优化指南Swin Transformer v2核心架构解析窗口注意力机制的终极优化指南 Swin Transformer v2作为视觉Transformer领域的革命async-http-client自定义协议性能与HTTP对比的终极指南async http client自定义协议性能与HTTP对比的终极指南 在当今高并发的互联网应用中 异步HTTP客户端 已成为提升系统性能的关键技术。as后端网络异步编程上一篇DaoCloud公开镜像仓库终极加速指南如何10倍提升镜像下载速度下一篇5美元玩转分布式计算用Raspberry Pi Zero搭建Docker Swarm的完整教程 — gh_mirrors/do/docker-arm项目实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考