Revel 框架需在 app.Init 阶段注册高优先级灰度中间件,置于 BuiltinInterceptors 之前,统一提取白名单灰度 Header,安全透传至下游,禁用 Redirect 跳转,且 AppConfig 不支持热更新灰度规则。

Revel 框架本身不内置灰度发布能力,必须靠你在 app.Init 阶段注册自定义 HTTP 中间件来实现流量识别与路由分流——这是唯一安全、可控、不绕过鉴权/限流逻辑的路径。
灰度中间件必须在 Revel 的 Router 之前注册
Revel 的中间件执行顺序很关键:如果你把灰度判断写在某个 Controller 的 Begin 方法里,或者塞进 InterceptFunc 且注册位置靠后,就可能被前置的 AuthFilter、RateLimitFilter 或日志中间件先处理,导致灰度标识丢失、统计口径错乱、甚至跳过安全校验。
- 正确做法是在
app/init.go的init()函数中,调用revel.Interceptors.Add时传入一个高优先级函数,并确保它排在revel.BuiltinInterceptors之前 - 不要依赖
c.Request.Header.Get("X-Gray-Version")在业务层做 if 判断——这会让同一请求在链路追踪里分裂成两个 span(/api/user 和 /api/user?gray=v2),监控对不上 - 推荐统一提取
X-Gray-Version或X-Release-Stage,值只接受v1、v2、canary白名单,拒绝任意字符串,防止 header 注入触发未发布分支
如何安全透传灰度 Header 到下游服务
Revel 默认使用 net/http,所以你用 http.Client 调下游时,c.Request.Header 是可读写的。但直接 req.Header.Set("X-Gray-Version", value) 会污染原始请求头,影响后续中间件(比如鉴权中间件依赖原始 Authorization)。
- 必须用
c.Request.Clone(c.Request.Context())创建新请求副本,再修改副本的 Header - 如果调的是 gRPC 服务,Header 键名要转小写(如
x-gray-version),否则grpc-go会静默丢弃 - 敏感 Header 如
Cookie、Authorization默认不透传,除非明确需要;可用strings.HasPrefix(k, "X-Gray-") || strings.HasPrefix(k, "X-Release-")做白名单过滤
避免用 Revel 的 Redirect 做灰度跳转
有人试图在中间件里写 c.Redirect("/api/v2/user") 来把灰度用户导向新版路径,这会导致 URL 暴露、Referer 断裂、前端 history 不一致,而且无法复用旧版的缓存策略和 CDN 配置。
- 正确方式是改写
c.Request.URL.Path或c.Request.Host,让后续所有 Handler 和 Filter 都自然走新逻辑,就像没发生过跳转一样 - 若需代理到独立灰度集群(如 http://gray-api.internal),应使用
httputil.NewSingleHostReverseProxy+ 自定义Director,而不是靠Redirect - 注意
ReverseProxy不会自动继承 Revel 的 context.Value,灰度版本号得从c.Params.Get("gray_version")或c.Request.Context().Value("gray_version")显式取出再设进下游请求
最易被忽略的一点:Revel 的 AppConfig 是启动时加载的,不支持运行时热更新灰度规则。如果要用 YAML 文件动态配置灰度用户 ID 列表或比例,得自己加一层 Watcher 监听文件变更,并用 sync.RWMutex 保护内存中的规则 map——别指望靠 revel.Config.StringDefault 实现热生效。


















