diff --git a/docs/sse.md b/docs/sse.md index 5a75731..bafc553 100644 --- a/docs/sse.md +++ b/docs/sse.md @@ -118,3 +118,14 @@ r.GET("/events-graceful", func(c *touka.Context) { ``` 在该示例中,我们显式地在回调函数中使用 `select` 监听 `ctx.Done()`。虽然 Touka 的 `EventStream` 内部也会检查此信号,但在回调内部自行处理可以执行更复杂的清理逻辑(如关闭数据库连接、停止特定的 Goroutine 等)。 + +### 为什么会出现 "context deadline exceeded"? + +如果您在优雅停机时遇到 `context deadline exceeded` 错误,通常是因为 SSE 连接仍然活跃,而 `http.Server.Shutdown` 正在等待它们结束。 + +在 Touka 的新版本中,我们通过 `BaseContext` 将 `Engine` 的关闭信号注入到了每个请求的 `Context` 中。这意味着: +1. 当服务器收到关闭信号时,`engine.shutdownCtx` 会被取消。 +2. 随后,所有活跃请求的 `c.Request.Context()` 也会收到取消信号。 +3. 您的 SSE 处理器中的 `case <-c.Request.Context().Done():` 会立即触发,从而优雅地结束连接。 + +**注意:** 请务必使用 `RunShutdown`、`RunTLS` 或 `RunTLSRedir` 来启动服务器,以便框架能自动管理这些信号。 diff --git a/engine.go b/engine.go index 0a95765..1e7bb18 100644 --- a/engine.go +++ b/engine.go @@ -67,6 +67,9 @@ type Engine struct { Protocols ProtocolsConfig //协议版本配置 useDefaultProtocols bool //是否使用默认协议 + shutdownCtx context.Context + shutdownCancel context.CancelFunc + // ServerConfigurator 允许在服务器启动前对其进行自定义配置 // 例如,设置 ReadTimeout, WriteTimeout 等 ServerConfigurator func(*http.Server) @@ -207,6 +210,7 @@ func New() *Engine { TLSServerConfigurator: nil, GlobalMaxRequestBodySize: -1, } + engine.shutdownCtx, engine.shutdownCancel = context.WithCancel(context.Background()) //engine.SetProtocols(GetDefaultProtocolsConfig()) engine.SetDefaultProtocols() engine.SetLoggerCfg(defaultLogRecoConfig) @@ -766,3 +770,9 @@ func (engine *Engine) handleRequest(c *Context) { c.Next() // 执行处理函数链 //c.Writer.Flush() // 确保所有缓冲的响应数据被发送 } + +// Context 返回 Engine 的根上下文, 该上下文在服务器优雅关闭时会被取消. +// 它可以用于在长连接 (如 SSE) 中监听关闭信号. +func (engine *Engine) Context() context.Context { + return engine.shutdownCtx +} diff --git a/serve.go b/serve.go index 7e05b8c..6a4cf2a 100644 --- a/serve.go +++ b/serve.go @@ -224,7 +224,11 @@ func (engine *Engine) RunShutdown(addr string, timeouts ...time.Duration) error srv := &http.Server{ Addr: addr, Handler: engine, + BaseContext: func(l net.Listener) context.Context { + return engine.shutdownCtx + }, } + srv.RegisterOnShutdown(engine.shutdownCancel) // 应用框架的默认配置和用户提供的自定义配置 //engine.applyDefaultServerConfig(srv) @@ -241,7 +245,11 @@ func (engine *Engine) RunShutdownWithContext(addr string, ctx context.Context, t srv := &http.Server{ Addr: addr, Handler: engine, + BaseContext: func(l net.Listener) context.Context { + return engine.shutdownCtx + }, } + srv.RegisterOnShutdown(engine.shutdownCancel) // 应用框架的默认配置和用户提供的自定义配置 //engine.applyDefaultServerConfig(srv) @@ -270,7 +278,11 @@ func (engine *Engine) RunTLS(addr string, tlsConfig *tls.Config, timeouts ...tim Addr: addr, Handler: engine, TLSConfig: tlsConfig, + BaseContext: func(l net.Listener) context.Context { + return engine.shutdownCtx + }, } + srv.RegisterOnShutdown(engine.shutdownCancel) // 应用框架的默认配置和用户提供的自定义配置 // 优先使用 TLSServerConfigurator,如果未设置,则回退到通用的 ServerConfigurator @@ -304,7 +316,11 @@ func (engine *Engine) RunTLSRedir(httpAddr, httpsAddr string, tlsConfig *tls.Con Addr: httpsAddr, Handler: engine, TLSConfig: tlsConfig, + BaseContext: func(l net.Listener) context.Context { + return engine.shutdownCtx + }, } + httpsSrv.RegisterOnShutdown(engine.shutdownCancel) //engine.applyDefaultServerConfig(httpsSrv) if engine.TLSServerConfigurator != nil { engine.TLSServerConfigurator(httpsSrv)