Gin 中间件
pkg/opentrans/ginx 提供了适合 Gin 项目直接接入的轻量中间件封装,用来在 HTTP 请求和响应链路中完成协议转换。
提供的中间件
ConvertRequestBody
在进入业务 handler 之前读取并转换请求体:
go
router.POST(
"/v1/chat/completions",
ginx.ConvertRequestBody(opentrans.ProtocolOpenAI, opentrans.ProtocolClaude),
func(c *gin.Context) {
c.Status(200)
},
)适合:
- 客户端协议兼容
- 上游 OpenAI 风格请求转内部 Claude/Gemini 风格
ConvertResponseBody
捕获 handler 写出的非流式 JSON 响应,再按目标协议输出:
go
router.GET(
"/v1/messages",
ginx.ConvertResponseBody(opentrans.ProtocolClaude, opentrans.ProtocolOpenAI),
handler,
)适合:
- 内部服务返回 provider A,外部对外暴露 provider B
ConvertStreamEventBody
按 SSE 帧转换流式输出:
go
router.GET(
"/v1/messages/stream",
ginx.ConvertStreamEventBody(opentrans.ProtocolClaude, opentrans.ProtocolOpenAI),
handler,
)中间件执行模型
请求中间件
执行顺序:
- 读取
c.Request.Body - 调用
opentrans.ConvertRequestBody - 用转换后的 body 回填
c.Request.Body - 更新
Content-Length - 继续执行后续 handler
响应中间件
执行顺序:
- 捕获 handler 输出
- 在请求结束后转换响应体
- 写回真实响应
注意:
- 该中间件适合非流式响应
- 如果 handler 已经直接 flush 原始内容,则不会再做二次捕获转换
流式中间件
执行顺序:
- 捕获 SSE 字节流
- 按帧解析
data:事件 - 转换事件 payload
- 保留其余 SSE 元信息并输出
当前已覆盖:
\n\n和\r\n\r\n帧分隔- 多行
data: event:id:- comment 行
[DONE]终止标记透传
错误处理
三个中间件都支持 MiddlewareOptions:
go
router.Use(ginx.ConvertRequestBody(
opentrans.ProtocolOpenAI,
opentrans.ProtocolClaude,
ginx.MiddlewareOptions{
OnError: func(c *gin.Context, err error) {
c.AbortWithStatusJSON(502, gin.H{
"error": "convert failed",
"detail": err.Error(),
})
},
},
))默认行为是返回:
- HTTP
502 Bad Gateway - JSON body:
{"error":"..."}
接入建议
适合放在什么位置
- 面向外部 API 的最前层
- 上游 provider 适配层
- 对外兼容 OpenAI/Claude 的代理层
不建议直接这样用的场景
- 需要极细粒度控制 SSE 帧结构
- 需要完整代理所有 provider 原始流语义
- 非 Gin 框架
如果你需要更深度的流式控制,建议直接在业务里使用 NormalizeStreamEvent / MarshalStreamEvent。
常见问题
为什么请求体只能读取一次?
因为中间件会消费 c.Request.Body,所以它必须在读取后把转换后的 body 回填回去。OpenTrans 已经在中间件内部处理了这一步。
为什么流式响应里还会保留 event: 或 id:?
这是为了避免破坏 SSE 协议语义。OpenTrans 只转换 data: payload,其余元信息尽量透传。
可以把一个协议的请求和另一个协议的响应混搭吗?
可以。请求、响应和流式中间件是独立的,你可以按自己的代理拓扑分别组合。