67 lines
2.2 KiB
Go
67 lines
2.2 KiB
Go
package middleware
|
|
|
|
import (
|
|
"bytes"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// responseRecorder 拦截下游 Writer,捕获 body 用于重写
|
|
// WriteHeader 走原始 writer(状态码由 gin's underlying writer 持有)
|
|
// Write/WriteString 走 buffer(body 暂存,post 阶段根据需要重写)
|
|
type responseRecorder struct {
|
|
gin.ResponseWriter
|
|
body *bytes.Buffer
|
|
}
|
|
|
|
func (r *responseRecorder) Write(b []byte) (int, error) {
|
|
return r.body.Write(b)
|
|
}
|
|
|
|
func (r *responseRecorder) WriteString(s string) (int, error) {
|
|
return r.body.WriteString(s)
|
|
}
|
|
|
|
// GRPCStatusInterceptor Dubbo 透传响应拦截器
|
|
//
|
|
// 用途: Dubbo-Triples 框架会自动把 gRPC code 转成 HTTP 状态码
|
|
// (例如 codes.Unauthenticated → HTTP 401)写到响应里,这是问题根源
|
|
// (把业务码泄漏到传输层)。本拦截器在 gateway 这一层剥掉这个自动转换:
|
|
//
|
|
// - 业务响应(原本要 4xx/5xx): 强制改写为 HTTP 200,业务码由 body 的 code 字段携带
|
|
// - transport 错误(AuthMiddleware 真鉴权失败 401、404 路由不存在、5xx 网关内部错):
|
|
// 保持原 HTTP 状态码,body 原样透传
|
|
//
|
|
// 实现关键: 用 c.IsAborted() 区分"中间件主动 abort"和"控制器正常返回"
|
|
// - AuthMiddleware 走 c.AbortWithStatusJSON(401, ...) → c.IsAborted() == true → 保留 401
|
|
// - 控制器调 c.JSON(403, ...) (Dubbo 自动转 gRPC code=7) → c.IsAborted() == false → 改 200
|
|
//
|
|
// 仅作用于 /api/* 路径,其他路径(health、swagger、segment/json 等)直接跳过
|
|
func GRPCStatusInterceptor() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// 跳过非 Dubbo 透传的请求
|
|
if !strings.HasPrefix(c.Request.URL.Path, "/api/") {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// 拦截响应体到 recorder(状态码走原 writer,body 走 buffer)
|
|
original := c.Writer
|
|
recorder := &responseRecorder{ResponseWriter: original, body: &bytes.Buffer{}}
|
|
c.Writer = recorder
|
|
c.Next()
|
|
|
|
// 已经被前置中间件 abort(如 AuthMiddleware 写 401),保持原状态码
|
|
if c.IsAborted() {
|
|
_, _ = recorder.body.WriteTo(original)
|
|
return
|
|
}
|
|
|
|
// 业务响应: 强制 HTTP 200
|
|
original.WriteHeader(http.StatusOK)
|
|
_, _ = recorder.body.WriteTo(original)
|
|
}
|
|
}
|