如何为Gin框架中使用SSE的/stream接口移除全局WriteTimeout
解决Gin框架SSE接口取消全局WriteTimeout限制的方案
针对你的场景,有几种可靠的方法可以单独为/stream接口取消全局设置的WriteTimeout,以下是具体实现:
方法一:在SSE Stream回调中动态取消超时
直接在你的/stream接口的c.Stream回调函数里,添加取消写超时的逻辑,无需修改全局配置:
v2.GET("/stream", HeadersMiddleware(), r.serveHTTP(), func(c *gin.Context) { v, ok := c.Get("clientChan") if !ok { return } clientChan, ok := v.(ClientChan) if !ok { return } c.Stream(func(w io.Writer) bool { // 取消写超时:将WriteDeadline设置为零值(无截止时间) if deadlineSetter, ok := w.(interface{ SetWriteDeadline(time.Time) error }); ok { _ = deadlineSetter.SetWriteDeadline(time.Time{}) } person := Person{Name: "John", Age: 30} jsonBytes, err := json.Marshal(person) if err != nil { log.Printf("marshal error: %v", err) return false } if msg, ok := <-clientChan; ok { c.SSEvent("message", jsonBytes) log.Printf(msg) return true } log.Printf("client channel closed") return false }) })
说明
- 通过断言
w为包含SetWriteDeadline方法的接口,直接操作底层连接的写超时设置 time.Time{}零值代表取消所有写超时限制,适配SSE长连接的需求- 忽略设置错误是因为如果失败,大概率是连接已关闭,此时返回
false终止Stream即可
方法二:用中间件针对特定路径统一处理
如果有多个SSE接口需要取消超时,可以创建一个中间件,统一匹配路径并取消超时:
// 定义取消写超时的中间件 func DisableWriteTimeoutForSSE() gin.HandlerFunc { return func(c *gin.Context) { // 匹配需要取消超时的路径,这里是/stream if c.Request.URL.Path == "/stream" { if deadlineSetter, ok := c.Writer.(interface{ SetWriteDeadline(time.Time) error }); ok { _ = deadlineSetter.SetWriteDeadline(time.Time{}) } } c.Next() } }
然后将中间件注册到目标路由组或接口:
// 注册到v2路由组,对组内所有接口生效 v2.Use(DisableWriteTimeoutForSSE()) // 或者仅在/stream接口上使用 v2.GET("/stream", DisableWriteTimeoutForSSE(), HeadersMiddleware(), r.serveHTTP(), func(c *gin.Context) { // 原有业务代码 })
方法三:全局不设置WriteTimeout,为非SSE接口添加超时
如果担心依赖内部实现的稳定性,可以反过来操作:全局Server不设置WriteTimeout,然后为非SSE接口单独添加写超时中间件:
1. 修改全局Server配置
a.httpServer = &http.Server{ Handler: c.Handler(a.router.Router), ReadTimeout: cfg.HTTP.ReadTimeout, IdleTimeout: cfg.HTTP.IdleTimeout, ReadHeaderTimeout: cfg.HTTP.ReadHeaderTimeout, // 移除WriteTimeout配置 }
2. 写超时中间件
func SetWriteTimeout(timeout time.Duration) gin.HandlerFunc { return func(c *gin.Context) { // 仅为非/stream接口设置写超时 if c.Request.URL.Path != "/stream" { if deadlineSetter, ok := c.Writer.(interface{ SetWriteDeadline(time.Time) error }); ok { _ = deadlineSetter.SetWriteDeadline(time.Now().Add(timeout)) } } c.Next() } }
3. 全局注册中间件
a.router.Use(SetWriteTimeout(cfg.HTTP.WriteTimeout))
这样既保留了普通接口的超时保护,又满足了SSE长连接的无超时需求。
内容的提问来源于stack exchange,提问作者Grandeas
相关产品推荐
相关产品推荐

