mirror of
https://github.com/infinite-iroha/touka.git
synced 2026-06-13 15:47:38 +08:00
Merge pull request #63 from infinite-iroha/add-detailed-docs-13958049504187048410
Add detailed docs 13958049504187048410
This commit is contained in:
commit
8feb31a990
3 changed files with 78 additions and 0 deletions
52
docs/sse.md
52
docs/sse.md
|
|
@ -77,3 +77,55 @@ r.GET("/events-chan", func(c *touka.Context) {
|
||||||
1. **资源回收**: 确保在 `EventStreamChan` 模式下正确监听 `c.Request.Context().Done()` 以避免 Goroutine 泄漏。
|
1. **资源回收**: 确保在 `EventStreamChan` 模式下正确监听 `c.Request.Context().Done()` 以避免 Goroutine 泄漏。
|
||||||
2. **数据格式**: SSE 协议要求数据为 UTF-8。Touka 的 `Render` 方法会自动处理多行数据并加上必要的 `data:` 前缀。
|
2. **数据格式**: SSE 协议要求数据为 UTF-8。Touka 的 `Render` 方法会自动处理多行数据并加上必要的 `data:` 前缀。
|
||||||
3. **超时管理**: SSE 连接通常是长连接,请确保您的反向代理(如 Nginx)配置了足够大的写超时时间。
|
3. **超时管理**: SSE 连接通常是长连接,请确保您的反向代理(如 Nginx)配置了足够大的写超时时间。
|
||||||
|
|
||||||
|
## 优雅关闭与资源清理
|
||||||
|
|
||||||
|
在长连接场景下,正确处理客户端断开或服务器关闭信号至关重要,以防止资源泄漏。
|
||||||
|
|
||||||
|
### 示例:监听 Context 取消信号
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.GET("/events-graceful", func(c *touka.Context) {
|
||||||
|
// 设置响应头(如果手动处理,EventStream 会自动设置)
|
||||||
|
|
||||||
|
// 使用 Context 的 Done 通道来感知连接关闭
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
// 启动一个用于模拟数据生成的循环
|
||||||
|
ticker := time.NewTicker(2 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
c.EventStream(func(w io.Writer) bool {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// 收到优雅关闭信号(客户端离开或服务器正在关闭)
|
||||||
|
fmt.Println("SSE 连接正在关闭,开始清理资源...")
|
||||||
|
return false // 返回 false 告知框架停止流
|
||||||
|
|
||||||
|
case t := <-ticker.C:
|
||||||
|
event := touka.Event{
|
||||||
|
Data: "Tick at " + t.Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
if err := event.Render(w); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println("SSE 连接已彻底释放")
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
在该示例中,我们显式地在回调函数中使用 `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` 来启动服务器,以便框架能自动管理这些信号。
|
||||||
|
|
|
||||||
10
engine.go
10
engine.go
|
|
@ -67,6 +67,9 @@ type Engine struct {
|
||||||
Protocols ProtocolsConfig //协议版本配置
|
Protocols ProtocolsConfig //协议版本配置
|
||||||
useDefaultProtocols bool //是否使用默认协议
|
useDefaultProtocols bool //是否使用默认协议
|
||||||
|
|
||||||
|
shutdownCtx context.Context
|
||||||
|
shutdownCancel context.CancelFunc
|
||||||
|
|
||||||
// ServerConfigurator 允许在服务器启动前对其进行自定义配置
|
// ServerConfigurator 允许在服务器启动前对其进行自定义配置
|
||||||
// 例如,设置 ReadTimeout, WriteTimeout 等
|
// 例如,设置 ReadTimeout, WriteTimeout 等
|
||||||
ServerConfigurator func(*http.Server)
|
ServerConfigurator func(*http.Server)
|
||||||
|
|
@ -207,6 +210,7 @@ func New() *Engine {
|
||||||
TLSServerConfigurator: nil,
|
TLSServerConfigurator: nil,
|
||||||
GlobalMaxRequestBodySize: -1,
|
GlobalMaxRequestBodySize: -1,
|
||||||
}
|
}
|
||||||
|
engine.shutdownCtx, engine.shutdownCancel = context.WithCancel(context.Background())
|
||||||
//engine.SetProtocols(GetDefaultProtocolsConfig())
|
//engine.SetProtocols(GetDefaultProtocolsConfig())
|
||||||
engine.SetDefaultProtocols()
|
engine.SetDefaultProtocols()
|
||||||
engine.SetLoggerCfg(defaultLogRecoConfig)
|
engine.SetLoggerCfg(defaultLogRecoConfig)
|
||||||
|
|
@ -766,3 +770,9 @@ func (engine *Engine) handleRequest(c *Context) {
|
||||||
c.Next() // 执行处理函数链
|
c.Next() // 执行处理函数链
|
||||||
//c.Writer.Flush() // 确保所有缓冲的响应数据被发送
|
//c.Writer.Flush() // 确保所有缓冲的响应数据被发送
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context 返回 Engine 的根上下文, 该上下文在服务器优雅关闭时会被取消.
|
||||||
|
// 它可以用于在长连接 (如 SSE) 中监听关闭信号.
|
||||||
|
func (engine *Engine) Context() context.Context {
|
||||||
|
return engine.shutdownCtx
|
||||||
|
}
|
||||||
|
|
|
||||||
16
serve.go
16
serve.go
|
|
@ -224,7 +224,11 @@ func (engine *Engine) RunShutdown(addr string, timeouts ...time.Duration) error
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: engine,
|
Handler: engine,
|
||||||
|
BaseContext: func(l net.Listener) context.Context {
|
||||||
|
return engine.shutdownCtx
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
srv.RegisterOnShutdown(engine.shutdownCancel)
|
||||||
|
|
||||||
// 应用框架的默认配置和用户提供的自定义配置
|
// 应用框架的默认配置和用户提供的自定义配置
|
||||||
//engine.applyDefaultServerConfig(srv)
|
//engine.applyDefaultServerConfig(srv)
|
||||||
|
|
@ -241,7 +245,11 @@ func (engine *Engine) RunShutdownWithContext(addr string, ctx context.Context, t
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: engine,
|
Handler: engine,
|
||||||
|
BaseContext: func(l net.Listener) context.Context {
|
||||||
|
return engine.shutdownCtx
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
srv.RegisterOnShutdown(engine.shutdownCancel)
|
||||||
|
|
||||||
// 应用框架的默认配置和用户提供的自定义配置
|
// 应用框架的默认配置和用户提供的自定义配置
|
||||||
//engine.applyDefaultServerConfig(srv)
|
//engine.applyDefaultServerConfig(srv)
|
||||||
|
|
@ -270,7 +278,11 @@ func (engine *Engine) RunTLS(addr string, tlsConfig *tls.Config, timeouts ...tim
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: engine,
|
Handler: engine,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
|
BaseContext: func(l net.Listener) context.Context {
|
||||||
|
return engine.shutdownCtx
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
srv.RegisterOnShutdown(engine.shutdownCancel)
|
||||||
|
|
||||||
// 应用框架的默认配置和用户提供的自定义配置
|
// 应用框架的默认配置和用户提供的自定义配置
|
||||||
// 优先使用 TLSServerConfigurator,如果未设置,则回退到通用的 ServerConfigurator
|
// 优先使用 TLSServerConfigurator,如果未设置,则回退到通用的 ServerConfigurator
|
||||||
|
|
@ -304,7 +316,11 @@ func (engine *Engine) RunTLSRedir(httpAddr, httpsAddr string, tlsConfig *tls.Con
|
||||||
Addr: httpsAddr,
|
Addr: httpsAddr,
|
||||||
Handler: engine,
|
Handler: engine,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
|
BaseContext: func(l net.Listener) context.Context {
|
||||||
|
return engine.shutdownCtx
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
httpsSrv.RegisterOnShutdown(engine.shutdownCancel)
|
||||||
//engine.applyDefaultServerConfig(httpsSrv)
|
//engine.applyDefaultServerConfig(httpsSrv)
|
||||||
if engine.TLSServerConfigurator != nil {
|
if engine.TLSServerConfigurator != nil {
|
||||||
engine.TLSServerConfigurator(httpsSrv)
|
engine.TLSServerConfigurator(httpsSrv)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue