实战指南:基于 Go 模板动态改写请求体与请求头)
AI 应用API网关LLM 网关后端前端【免费下载链接】axonhub⚡️ Open-source AI Gateway — Use any SDK to call 100 LLMs. Built-in failover, load balancing, cost control end-to-end tracing.项目地址https://gitcode.com/gh_mirrors/ax/axonhub点击查看免费下载请求重写Request Override是 AxonHub 在请求发送至 AI 提供商之前动态修改请求体Body与请求头Headers的核心能力适用于特定模型参数调整、reasoning_effort等功能的跨提供商映射以及自定义元数据注入等场景。本文以 docs/zh/guides/request-override.md 为主线结合网关源码实现完整讲解重写操作类型、模板渲染、数组操作、请求头重写与向后兼容等细节读完即可在渠道Channel级别配置一套可投入生产使用的重写规则。核心概念渠道级重写与两类操作重写是在**渠道Channel**级别配置的作用于请求离开 AxonHub、发往上游 AI 提供商之前。它分为两种类型重写参数Override Parameters修改 JSON 请求体。重写请求头Override Headers修改 HTTP 请求头。从源码结构看两者在网关流水线中是两条独立出站中间件applyOverrideRequestBody与applyOverrideRequestHeaders在 internal/server/orchestrator/orchestrator.go 中被注册且请求头重写位于用户代理User-Agent透传之后执行保证覆盖顺序可预期。核心实现集中在 internal/server/orchestrator/override.go重写操作的类型常量与数据结构定义在 internal/objects/channel.go。模板渲染变量AxonHub 使用 Go 模板Go templates进行动态值渲染支持的变量如下变量描述示例.RequestModel来自客户端原始请求的模型名称。{{.RequestModel}}.Model当前请求中的模型名称可能经过了模型映射。{{.Model}}.ReasoningEffort统一思考等级值none, minimal, low, medium, high, xhigh, max。{{.ReasoningEffort}}.Metadata请求中传递的自定义元数据 Map。{{index .Metadata user_id}}.RequestHeader过滤后的客户端入站请求头。支持规范写法/小写查找并返回第一个值。{{index .RequestHeader X-Trace-Id}}.PromptCacheKey入站请求中的prompt_cache_key未提供时为空字符串。{{.PromptCacheKey}}这些变量对应实现中的RenderContext结构体见 internal/server/orchestrator/override.go。需要注意两点实现细节.RequestModel取的是客户端原始请求中的模型名outbound.state.OriginalModel而.Model是经过模型映射后实际发给提供商的模型名两者可能不同。测试用例 TestOverrideParametersWithMappedRequestModel 专门验证了这一点。.RequestHeader并非原样透传所有请求头buildRequestHeaderMap会过滤掉Authorization、Api-Key、X-Api-Key、Cookie等敏感头完整清单见 llm/httpclient/utils.go 的sensitiveHeaders并同时写入规范写法如X-Trace-Id与小写如x-trace-id两个键所以模板里两种写法都能命中。在 JSON 中嵌入模板值时请使用toJSON函数正确转义引号等特殊字符例如{session_id:{{toJSON .PromptCacheKey}}}。toJSON是源码中注册的唯一自定义模板函数internal/server/orchestrator/override.go内部直接调用json.Marshal。重写操作类型AxonHub 支持以下重写操作操作类型描述适用场景set设置字段值如果字段不存在则创建修改或添加参数set_if_absent仅当目标路径不存在时设置字段值提供可由客户端覆盖的默认值delete删除指定字段移除不需要的参数rename重命名字段从from移动到to字段名映射转换copy复制字段值从from复制到to参数复用array_append把值追加到path数组的末尾在原有数组内容之后注入array_prepend把值追加到path数组的开头在原有数组内容之前注入array_insert把值插入到path数组的指定位置在任意位置插入元素array_remove从path数组中移除匹配项按数组项内的字段值过滤工具或消息set_if_absent和数组操作仅适用于请求体。请求头只支持set、delete、rename、copy。这个限制在代码层被强制执行ValidateOverrideHeadersinternal/server/biz/channel_override.go对出现在请求头操作中的set_if_absent与所有数组操作直接返回校验错误并明确提示array ops only apply to the body。重写参数Override Parameters重写参数定义为一个操作数组每个操作包含以下字段字段类型必需描述opstring是操作类型set、set_if_absent、delete、rename、copy、array_append、array_prepend、array_insert、array_removepathstring条件目标字段路径set、set_if_absent、delete以及所有数组操作必需fromstring条件源字段路径rename和copy必需tostring条件目标字段路径rename和copy必需valuestring条件字段值set、array_append、array_prepend、array_insert必需set_if_absent不能是空值或仅包含空白字符支持模板conditionstring否条件表达式结果为true时执行matchobject条件匹配规则array_remove必需格式为{path:function.name,eq:web_search}indexnumber条件插入位置array_insert必需支持负数表示从末尾倒数越界会被夹紧到[0, len]splatbool否当渲染后的值是 JSON 数组时是否将其元素展开插入到目标数组。默认true。设为false则把整个数组作为单个嵌套元素插入。仅对数组操作生效。字段结构对应OverrideOperation结构体internal/objects/channel.go。服务端在保存渠道设置时会对操作做严格校验ValidateBodyOverrideOperations见 internal/server/biz/channel_override.goset/set_if_absent/delete/ 数组操作必须有非空pathset_if_absent必须有非空valuerename/copy必须有非空from与toarray_insert必须提供indexarray_remove必须提供match.path与match.eq所有set/set_if_absent/ 数组操作都禁止指向stream字段。基础示例[ { op: set, path: temperature, value: 0.7 }, { op: set, path: max_tokens, value: 2000 }, { op: delete, path: frequency_penalty } ]提供可由客户端覆盖的默认值当渠道需要提供默认值同时保留下游客户端的覆盖权时使用set_if_absent[ { op: set_if_absent, path: max_output_tokens, value: 32000 } ]当请求未携带max_output_tokens时AxonHub 会追加max_output_tokens: 32000客户端已提供该字段时则保留原值。是否存在按 JSON path 判断因此0、false、空字符串和显式null都视为已存在。源码层面对此有双重保障见 internal/server/orchestrator/override.go 的applyBodySetIfAbsent出站请求体由格式转换format transformation产生客户端发送的字段可能在转换中被丢弃或改名因此除检查出站请求体外还会回查原始入站请求体originalBody只要入站请求里存在该路径就视为已存在。这一行为由 TestOverrideBodySetIfAbsent 中的多组用例逐一验证包括保留0、false、空字符串、显式null以及嵌套路径等情况。使用模板你可以使用模板使参数根据输入请求动态变化[ { op: set, path: custom_field, value: model-{{.Model}} }, { op: set, path: effort_level, value: effort-{{.ReasoningEffort}} }, { op: set, path: user_context, value: user-{{index .Metadata \user_id\}} }, { op: set, path: trace_id, value: {{index .RequestHeader \x-trace-id\}} } ]模板渲染由renderTemplate/renderOverrideValue完成internal/server/orchestrator/override.go。其中renderOverrideValue的自动 JSON 解析逻辑值得注意渲染结果若以{、[、数字、-开头或恰好为true/false/null会尝试json.Unmarshal成功则作为结构化 JSON 值而非字符串写入请求体——这就是下文动态 JSON 对象一节的实现基础。测试 TestOverrideParametersWithTemplate 同时覆盖了请求体与请求头两条重写链路。条件执行使用condition字段实现条件逻辑[ { op: set, path: top_k, value: 40, condition: {{eq .Model \claude-3-opus-20240229\}} }, { op: set, path: logic_field, value: premium-mode, condition: {{eq .Model \gpt-4o\}} }, { op: set, path: logic_field, value: standard-mode, condition: {{ne .Model \gpt-4o\}} } ]条件求值由evaluateCondition实现internal/server/orchestrator/override.go先渲染condition模板再对结果做TrimSpace后与字符串true精确比较condition为空字符串表示无条件、始终执行。{{eq ...}}、{{ne ...}}、{{or ...}}等均为 Go 模板内建函数可直接使用如{{or (eq .RequestModel gpt-4-fast) (eq .RequestModel gpt-4-priority)}}。字段重命名与复制[ { op: rename, from: old_field_name, to: new_field_name }, { op: copy, from: model, to: custom_model_header } ]实现细节rename先读取from路径的值from不存在则整个操作静默跳过no-op存在则先删除from再写入tocopy同样在from不存在时跳过见 internal/server/orchestrator/override.go测试用例见 TestOverrideOperationsNewFormat。数组操作数组操作允许你向已有数组如system、messages、tools注入或移除元素不会替换整个数组。当你想保留客户端原有的内容、同时在前后插入网关侧的内容或过滤某些数组项时使用这些操作。行为说明对array_append、array_prepend、array_insert如果path不存在会以提供的值创建一个新数组。如果path存在但不是数组操作会被跳过并记录警告日志。对插入类数组操作如果渲染后的value是一个 JSON 数组并且splat为true默认值其中的元素会被展开插入到目标数组将splat设为false可把整个数组作为单个嵌套元素插入。对array_insertindex支持负数从末尾倒数。index -1表示插入到最后一个元素之前。越界值会被夹紧到[0, len]。对array_removematch.path相对于数组中的每个元素解析当该路径的字符串值等于match.eq时该元素会被移除。如果目标数组不存在操作不会修改请求体如果目标路径不是数组操作会被跳过并记录警告日志。这些规则全部由 internal/server/orchestrator/override.go 中的applyBodyArrayInsert与applyBodyArrayRemove实现并有 TestOverrideOperationsArrayOps 中 20 余个子用例逐一覆盖负数索引、越界夹紧、缺失路径建数组、非数组路径告警、嵌套cache_control对象、splat 展开与关闭、按嵌套字段移除等。追加单个对象到末尾[ { op: array_append, path: messages, value: {\role\:\system\,\content\:\appended note\} } ]在数组开头注入多个 system 项保留用户原有内容[ { op: array_prepend, path: system, value: [{\type\:\text\,\text\:\x-anthropic-billing-header: ...\},{\type\:\text\,\text\:\You are Claude Code...\,\cache_control\:{\type\:\ephemeral\}}] } ]假设原始请求是system: [{type:text,text:user}]最终结果{ system: [ {type: text, text: x-anthropic-billing-header: ...}, {type: text, text: You are Claude Code..., cache_control: {type: ephemeral}}, {type: text, text: user} ] }插入到指定位置[ { op: array_insert, path: messages, index: 1, value: {\role\:\system\,\content\:\inserted between message 0 and 1\} } ]把数组作为单个嵌套元素插入关闭 splat[ { op: array_prepend, path: tags, value: [\a\,\b\], splat: false } ]对{tags: [x]}执行后结果为{tags: [[a,b], x]}。按工具名称移除某个 tool[ { op: array_remove, path: tools, match: { path: function.name, eq: web_search } } ]对{tools:[{function:{name:get_weather}},{function:{name:web_search}}]}执行后web_search这个工具会从tools数组中移除。动态 JSON 对象如果渲染后的模板字符串是一个有效的 JSON 对象或数组AxonHub 会自动解析它并将其作为结构化的 JSON 对象插入而不是作为字符串[ { op: set, path: settings, value: {\id\: \{{.Model}}\, \enabled\: true} } ]结果 Body:{settings: {id: gpt-4o, enabled: true}}测试用例 TestOverrideParametersWithTemplate 断言了该行为json_field被解析为对象IsObject()为真其中的id与数字val均按结构化值落地。删除字段使用delete操作删除指定字段[ { op: delete, path: frequency_penalty } ]重写请求头Override Headers重写请求头使用与重写参数相同的操作格式[ { op: set, path: X-Custom-Model, value: {{.Model}} }, { op: set, path: X-User-ID, value: {{index .Metadata \user_id\}} }, { op: set, path: X-Trace-Id, value: {{index .RequestHeader \x-trace-id\}} }, { op: set, path: Extra, value: {\session_id\:{{toJSON .PromptCacheKey}}}, condition: {{if .PromptCacheKey}}true{{end}} }, { op: delete, path: X-Internal-Header }, { op: rename, from: Old-Header, to: New-Header } ]请求头重写的关键实现在applyOverrideOperationToHeadersinternal/server/orchestrator/override.go。与请求体重写相比有几点差异值得留意请求头的value只做模板字符串渲染renderTemplate不做JSON 结构化解析——测试 TestOverrideHeadersKeepJSONLikeString 验证了这一点因此请求头中如{session_id:...}这样的 JSON 字符串会原样保留常用于配合toJSON转义嵌入值。rename会将from的所有取值逐条Add到tocopy同理只是不删除源头。特殊哨兵值__AXONHUB_CLEAR__兼容旧格式渲染结果若等于该值则等价于delete操作删除该请求头headers.Del。这一哨兵同样作用于请求体见applyBodySet中的sjson.DeleteBytes分支internal/server/orchestrator/override.go。set使用headers.Set会覆盖同名单值多值追加由rename/copy的headers.Add完成。常见用例1. 映射推理强度Reasoning Effort如果提供商使用不同的字段名或值来表示推理强度[ { op: set, path: provider_specific_effort, value: max, condition: {{eq .ReasoningEffort \high\}} }, { op: set, path: provider_specific_effort, value: normal, condition: {{ne .ReasoningEffort \high\}} } ]AxonHub 内部将各类推理强度统一为none / minimal / low / medium / high / xhigh / max七个等级写入RenderContext.ReasoningEffort见 internal/server/orchestrator/override.go因此这里以统一等级为条件、输出提供商私有字段值即可完成跨提供商的能力映射。注意条件中的模型名必须写为经映射后的{{.Model}}因为.RequestModel是客户端原始值。2. 特定模型参数某些模型可能需要 OpenAI/Anthropic 标准 API 之外的特定参数[ { op: set, path: top_k, value: 40, condition: {{eq .Model \claude-3-opus-20240229\}} } ]3. 在请求头中注入元数据将内部追踪 ID 传递给提供商以便调试[ { op: set, path: X-Request-Source, value: axonhub-gateway }, { op: set, path: X-Internal-User, value: {{index .Metadata \internal_id\}} } ]向后兼容旧版格式自动转换AxonHub 仍然支持旧版的重写参数格式JSON 对象系统会自动将其转换为新的操作格式旧版格式仍支持{ temperature: 0.7, max_tokens: 2000 }这会等效转换为[ {op: set, path: temperature, value: 0.7}, {op: set, path: max_tokens, value: 2000} ]转换逻辑集中在 internal/objects/channel.go 的ParseOverrideOperations空字符串、{}、[]视为无操作以[开头的输入按新格式数组解析否则按旧版 map 解析每个键值对转换为一个set操作非字符串值通过fmt.Sprintf(%v, ...)转为字符串值为__AXONHUB_CLEAR__时转换为delete操作。渠道侧通过GetBodyOverrideOperations惰性解析并缓存结果internal/server/biz/channel_override.go避免每次请求重复解析。相关兼容性测试见 TestParseOverrideOperations 与 TestOverrideLegacyFormatCompatibility。注意事项与限制Stream 参数请求体中的stream参数无法被重写因为它由 AxonHub 的流水线统一管理。实现层有两道防线运行时遇到path为stream不区分大小写的操作会打印警告并跳过internal/server/orchestrator/override.go配置保存阶段的ValidateBodyOverrideOperations也会直接拒绝包含stream的重写参数。非 JSON 请求体保护请求体重写基于tidwall/sjson实现而sjson对非 JSON 文档会静默丢弃并重建为全新 JSON 对象。为防止 multipart图像编辑/变换、语音转录等或二进制载荷被破坏bodyOverrideSupportedinternal/server/orchestrator/override.go会检查 Content-Type 与gjson.ValidBytes非 JSON 载荷直接跳过全部请求体重写操作并记录警告。对应测试为 TestOverrideBodySkipsNonJSONBody。请求头安全在重写Authorization等安全敏感的请求头时请务必小心。默认入站请求头会被过滤掉敏感头llm/httpclient/utils.go但出站请求头的重写操作本身并不阻止你设置Authorization误配置可能覆盖上游凭据。无效模板如果模板解析或执行失败将使用原始值并记录警告日志renderTemplate中的 parse/execute 错误分支见 internal/server/orchestrator/override.go。执行顺序操作按数组顺序执行后续操作可以覆盖前面的操作结果。多个array_prepend依次执行时也会按声明顺序在索引 0 处堆叠测试用例 multiple array_prepend ops apply in declared order 对此有明确断言。小结请求重写是 AxonHub 渠道配置中最灵活的能力之一以 Go 模板为表达式语言以九种操作类型为原语既能完成简单的参数增删改也能实现reasoning_effort跨提供商映射、system 前缀注入、工具按名过滤等复杂编排。结合本文给出的源码路径internal/server/orchestrator/override.go、internal/objects/channel.go、internal/server/biz/channel_override.go与测试用例internal/server/orchestrator/override_test.go你可以在此基础上按需设计自己的重写规则并在上线前充分验证其行为边界。赞分享AI 应用API网关LLM 网关后端前端【免费下载链接】axonhub⚡️ Open-source AI Gateway — Use any SDK to call 100 LLMs. Built-in failover, load balancing, cost control end-to-end tracing.项目地址https://gitcode.com/gh_mirrors/ax/axonhub点击查看免费下载相关推荐GoReplay 请求改写指南URL、查询参数与请求头的在线重写实战GoReplay 请求改写指南URL、查询参数与请求头的在线重写实战 GoReplay 是用于捕获线上 HTTP 流量并重放到测试环境的高保真流量回放工具。在性能测试可观测性网络Puppeteer ContinueRequestOverrides拦截请求时改写 URL、方法、请求体与请求头的完整指南Puppeteer ContinueRequestOverrides拦截请求时改写 URL、方法、请求体与请求头的完整指南 本文围绕 Puppeteer 官方浏览器控制测试网页爬虫开发工具Requests请求重写修改请求URL与方法Requests请求重写修改请求URL与方法 在日常的API开发和数据爬取工作中我们经常会遇到需要动态修改请求URL或HTTP方法的场景。例如API版本升级后端网络通信上一篇chezmoi 模板函数 gopassCat 实战从 gopass 读取二进制密钥的完整指南下一篇WeChatMsg 使用教程3 步完成微信聊天记录导出还能生成年度聊天报告创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考