mirror of
https://github.com/infinite-iroha/touka.git
synced 2026-06-13 15:47:38 +08:00
docs: add comprehensive documentation in Chinese
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
parent
0f7f68fb89
commit
8a5acb0b04
10 changed files with 714 additions and 6 deletions
77
docs/advanced.md
Normal file
77
docs/advanced.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# 高级特性与优化
|
||||
|
||||
本章节涵盖了 Touka 的一些深层特性以及在生产环境中的最佳实践。
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. Context 池化
|
||||
|
||||
Touka 使用 `sync.Pool` 来重用 `touka.Context` 对象。这极大减少了每个请求产生的内存分配和 GC 压力。
|
||||
- **代价**: 您必须在处理器返回后立即停止对该 `Context` 指针的任何引用。
|
||||
- **解决方案**: 如果需要在后台 Goroutine 中使用请求数据,请预先提取所需数据(如 `c.Query` 的值),或者深拷贝该对象(不推荐)。
|
||||
|
||||
### 2. 预分配参数切片
|
||||
|
||||
在路由匹配过程中,Touka 会预分配路径参数切片,并根据路由深度进行缓存,从而在路由查找时实现几乎零分配。
|
||||
|
||||
## 优雅停机 (Graceful Shutdown)
|
||||
|
||||
在部署新版本时,我们希望服务器停止接收新请求,但能处理完当前正在进行的请求。
|
||||
|
||||
```go
|
||||
r := touka.Default()
|
||||
// ... 注册路由 ...
|
||||
|
||||
// 监听 SIGINT 和 SIGTERM 信号
|
||||
// 如果在 10 秒内未处理完,则强制关闭
|
||||
if err := r.RunShutdown(":8080", 10*time.Second); err != nil {
|
||||
log.Fatal("服务器退出异常:", err)
|
||||
}
|
||||
```
|
||||
|
||||
## 与标准库集成
|
||||
|
||||
Touka 遵循 `net/http` 哲学。您可以方便地使用现有的标准库组件。
|
||||
|
||||
### 适配 `http.HandlerFunc`
|
||||
|
||||
```go
|
||||
r.GET("/pprof/*any", touka.AdapterStdFunc(pprof.Index))
|
||||
```
|
||||
|
||||
### 手动注入
|
||||
|
||||
由于 `Engine` 实现了 `http.Handler` 接口,您可以将其挂载到任何地方。
|
||||
|
||||
```go
|
||||
s := &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: r, // Engine 实例
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
s.ListenAndServe()
|
||||
```
|
||||
|
||||
## 自定义日志集成
|
||||
|
||||
Touka 默认集成了 `reco` 日志库。您可以自定义其输出行为。
|
||||
|
||||
```go
|
||||
logConfig := reco.Config{
|
||||
Level: reco.LevelInfo,
|
||||
Output: os.Stdout,
|
||||
Async: true, // 异步写入提高性能
|
||||
}
|
||||
r.SetLoggerCfg(logConfig)
|
||||
```
|
||||
|
||||
## 内存读取限制 (MaxReader)
|
||||
|
||||
为了防止恶意的大数据包攻击(如慢速 HTTP 攻击或内存溢出),Touka 内置了 `MaxReader` 机制。
|
||||
|
||||
```go
|
||||
// 设置全局最大读取限制(例如 2MB)
|
||||
r.SetMaxReader(2 << 20)
|
||||
```
|
||||
120
docs/context.md
Normal file
120
docs/context.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# 上下文 (Context)
|
||||
|
||||
`touka.Context` 是 Touka 框架中最重要的结构。它携带了关于当前 HTTP 请求的所有必要信息,并提供了一系列方法来解析请求和构建响应。
|
||||
|
||||
## 请求数据解析
|
||||
|
||||
### 查询参数 (Query Parameters)
|
||||
|
||||
```go
|
||||
// /welcome?firstname=Jane&lastname=Doe
|
||||
r.GET("/welcome", func(c *touka.Context) {
|
||||
firstname := c.DefaultQuery("firstname", "Guest")
|
||||
lastname := c.Query("lastname") // 快捷方式,不存在则为空
|
||||
|
||||
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
|
||||
})
|
||||
```
|
||||
|
||||
### 表单数据 (Form Data)
|
||||
|
||||
```go
|
||||
r.POST("/form_post", func(c *touka.Context) {
|
||||
message := c.PostForm("message")
|
||||
nick := c.DefaultPostForm("nick", "anonymous")
|
||||
|
||||
c.JSON(http.StatusOK, touka.H{
|
||||
"status": "posted",
|
||||
"message": message,
|
||||
"nick": nick,
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### JSON 绑定
|
||||
|
||||
Touka 提供了非常便捷的 JSON 绑定功能,它会自动解析请求体并填充到结构体中,同时进行基本的验证。
|
||||
|
||||
```go
|
||||
type LoginRequest struct {
|
||||
User string `json:"user" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
r.POST("/login", func(c *touka.Context) {
|
||||
var json LoginRequest
|
||||
if err := c.ShouldBindJSON(&json); err != nil {
|
||||
c.JSON(http.StatusBadRequest, touka.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if json.User != "admin" || json.Password != "123" {
|
||||
c.JSON(http.StatusUnauthorized, touka.H{"status": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, touka.H{"status": "you are logged in"})
|
||||
})
|
||||
```
|
||||
|
||||
## 响应构建
|
||||
|
||||
### 基础格式
|
||||
|
||||
Touka 支持多种响应格式:
|
||||
|
||||
```go
|
||||
// JSON
|
||||
c.JSON(http.StatusOK, touka.H{"message": "hey"})
|
||||
|
||||
// 字符串 (支持格式化)
|
||||
c.String(http.StatusOK, "welcome %s", name)
|
||||
|
||||
// 纯文本
|
||||
c.Text(http.StatusOK, "just text")
|
||||
|
||||
// HTML 模板
|
||||
c.HTML(http.StatusOK, "index.tmpl", touka.H{"title": "Main website"})
|
||||
```
|
||||
|
||||
### 文件与流
|
||||
|
||||
```go
|
||||
// 服务本地文件
|
||||
c.File("/local/file.go")
|
||||
|
||||
// 将文件内容作为响应体(不触发下载)
|
||||
c.SetRespBodyFile(http.StatusOK, "config.json")
|
||||
|
||||
// 写入数据流
|
||||
c.WriteStream(reader)
|
||||
```
|
||||
|
||||
### 重定向
|
||||
|
||||
```go
|
||||
c.Redirect(http.StatusMovedPermanently, "http://google.com/")
|
||||
```
|
||||
|
||||
## 数据传递 (Keys/Values)
|
||||
|
||||
您可以在中间件和处理器之间共享数据。
|
||||
|
||||
```go
|
||||
// 在中间件中设置
|
||||
c.Set("user_id", 12345)
|
||||
|
||||
// 在处理器中获取
|
||||
id, exists := c.Get("user_id")
|
||||
val := c.MustGet("user_id").(int)
|
||||
```
|
||||
|
||||
## 状态管理
|
||||
|
||||
- `c.Abort()`: 停止执行后续的处理器/中间件。
|
||||
- `c.Next()`: 执行后续的处理链。这常用于中间件中,在执行完某些前置逻辑后,显式调用 `Next`,并在其返回后执行后置逻辑。
|
||||
|
||||
## 对象池化
|
||||
|
||||
为了提高性能,Touka 的 Context 对象是复用的。
|
||||
**重要提示:不要在 Goroutine 中持久化持有 `touka.Context` 指针。如果您需要在 Goroutine 中使用请求数据,请务必在派生 Goroutine 前提取所需的值。**
|
||||
66
docs/error-handling.md
Normal file
66
docs/error-handling.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# 统一错误处理
|
||||
|
||||
Touka 的核心优势之一是其**高度统一且自动化**的错误处理机制。
|
||||
|
||||
## 全局错误处理器
|
||||
|
||||
您可以为整个引擎设置一个统一的错误处理器。无论错误来自您的业务代码,还是来自框架内部(如 404/405),甚至是来自标准库的 `http.FileServer`,最终都会流向这个处理器。
|
||||
|
||||
```go
|
||||
r.SetErrorHandler(func(c *touka.Context, code int, err error) {
|
||||
// 您可以在这里定义统一的错误响应格式
|
||||
c.JSON(code, touka.H{
|
||||
"code": code,
|
||||
"message": http.StatusText(code),
|
||||
"detail": err.Error(),
|
||||
})
|
||||
|
||||
// 也可以记录日志
|
||||
c.Errorf("HTTP Error %d: %v", code, err)
|
||||
})
|
||||
```
|
||||
|
||||
## `errorCapturingResponseWriter` (ecw) 的工作原理
|
||||
|
||||
很多时候,我们希望拦截标准库组件(如 `http.FileServer`)产生的错误,以便能够应用我们自定义的 404 页面或 JSON 响应。
|
||||
|
||||
Touka 通过包装标准的 `http.ResponseWriter` 实现了这一点:
|
||||
|
||||
1. **拦截写入**: 当 `http.FileServer` 等组件尝试调用 `WriteHeader(statusCode)` 且 `statusCode >= 400` 时,Touka 的包装器会捕获这个状态码。
|
||||
2. **阻止输出**: 它会阻止组件继续向响应体写入默认的错误消息(如 `404 page not found`)。
|
||||
3. **回调处理**: 包装器随后会调用全局配置的 `ErrorHandler`。
|
||||
|
||||
这意味着您可以像这样轻松地为静态文件服务设置自定义错误处理:
|
||||
|
||||
```go
|
||||
r := touka.New()
|
||||
|
||||
// 设置全局错误处理
|
||||
r.SetErrorHandler(func(c *touka.Context, code int, err error) {
|
||||
if code == http.StatusNotFound {
|
||||
c.String(http.StatusNotFound, "找不到此资源")
|
||||
return
|
||||
}
|
||||
c.String(code, "发生错误: %v", err)
|
||||
})
|
||||
|
||||
// 服务静态目录
|
||||
r.StaticDir("/static", "./public")
|
||||
// 如果用户访问 /static/missing-file.jpg,他将看到 "找不到此资源"
|
||||
```
|
||||
|
||||
## 手动触发错误处理
|
||||
|
||||
您也可以在处理器中通过 `c.ErrorUseHandle` 手动触发此流程:
|
||||
|
||||
```go
|
||||
r.GET("/item/:id", func(c *touka.Context) {
|
||||
item, err := db.GetItem(c.Param("id"))
|
||||
if err != nil {
|
||||
// 调用全局错误处理器
|
||||
c.ErrorUseHandle(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, item)
|
||||
})
|
||||
```
|
||||
26
docs/introduction.md
Normal file
26
docs/introduction.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Touka (灯花) 简介
|
||||
|
||||
Touka 是一个基于 Go 语言构建的高性能、多层次 Web 框架。其设计目标是为开发者提供**更直接的控制、有效的扩展能力,以及针对特定场景的行为优化**。
|
||||
|
||||
## 为什么选择 Touka?
|
||||
|
||||
在众多的 Go Web 框架中,Touka 致力于在保持类似 Gin 的易用性的同时,提供更深度的底层控制和更强大的统一错误处理机制。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **高性能路由**: 基于基数树(Radix Tree)实现的路由系统,支持高效的路径匹配、参数捕获和通配符路由。
|
||||
- **极致性能优化**:
|
||||
- **Context 复用**: 通过对象池(sync.Pool)管理 `touka.Context`,显著减少 GC 压力。
|
||||
- **最小化内存分配**: 在热点路径上尽可能减少临时对象的产生。
|
||||
- **统一错误处理**: 独创的 `errorCapturingResponseWriter` 机制,能够捕获包括标准库 `http.FileServer` 在内的所有组件产生的错误状态码,并交由全局处理器统一处理。
|
||||
- **无缝集成 SSE**: 内置对 Server-Sent Events 的支持,提供简单易用的回调式 API 和高度灵活的通道式 API。
|
||||
- **静态资源增强**: 针对本地文件、目录以及 Go 嵌入式文件系统(embed.FS)提供了开箱即用的支持。
|
||||
- **标准库兼容**: 提供了适配器,可以轻松将现有的 `http.Handler` 或 `http.HandlerFunc` 集成到 Touka 中。
|
||||
|
||||
## 设计哲学
|
||||
|
||||
1. **直接性**: 框架 API 设计直观,尽可能减少开发者需要记忆的概念。
|
||||
2. **可扩展性**: 每一个核心组件(如日志、错误处理器、渲染器)都是可插拔或可定制的。
|
||||
3. **健壮性**: 内置优雅停机支持,确保在服务器更新或关闭时请求能得到正确处理。
|
||||
|
||||
Touka 不仅仅是一个处理 HTTP 请求的工具,它还是构建现代化、可维护、高可用 Web 应用的坚实基础。
|
||||
99
docs/middleware.md
Normal file
99
docs/middleware.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# 中间件 (Middleware)
|
||||
|
||||
中间件是运行在 HTTP 请求处理链中的函数。它们可以拦截请求、修改数据、控制流向(通过 `c.Next()` 或 `c.Abort()`),并执行通用的前置/后置逻辑。
|
||||
|
||||
## 如何使用中间件
|
||||
|
||||
### 全局中间件
|
||||
|
||||
全局中间件应用于所有注册的路由。
|
||||
|
||||
```go
|
||||
r := touka.New()
|
||||
r.Use(touka.Recovery()) // 崩溃恢复
|
||||
r.Use(MyCustomLogger()) // 自定义日志
|
||||
```
|
||||
|
||||
### 路由组中间件
|
||||
|
||||
仅应用于特定组下的路由。
|
||||
|
||||
```go
|
||||
api := r.Group("/api")
|
||||
api.Use(AuthMiddleware())
|
||||
{
|
||||
api.GET("/user", handleUser)
|
||||
}
|
||||
```
|
||||
|
||||
## 编写自定义中间件
|
||||
|
||||
中间件的函数签名是 `touka.HandlerFunc`。
|
||||
|
||||
### 示例:请求计时器
|
||||
|
||||
```go
|
||||
func TimerMiddleware() touka.HandlerFunc {
|
||||
return func(c *touka.Context) {
|
||||
// --- 前置逻辑 ---
|
||||
start := time.Now()
|
||||
|
||||
// 执行处理链中的下一个函数
|
||||
c.Next()
|
||||
|
||||
// --- 后置逻辑 ---
|
||||
duration := time.Since(start)
|
||||
log.Printf("Request %s %s took %v", c.Request.Method, c.Request.URL.Path, duration)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例:简单的 API 密钥验证
|
||||
|
||||
```go
|
||||
func APIKeyAuth() touka.HandlerFunc {
|
||||
return func(c *touka.Context) {
|
||||
apiKey := c.GetHeader("X-API-KEY")
|
||||
if apiKey != "secret-token" {
|
||||
// 验证失败,返回错误并中止后续逻辑
|
||||
c.JSON(http.StatusUnauthorized, touka.H{"error": "Invalid API Key"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 验证通过,继续执行
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 内置中间件
|
||||
|
||||
- **Recovery**: 捕获任何发生的 panic,恢复运行并返回 500 错误。它还负责调用全局错误处理器。
|
||||
|
||||
Touka 的设计非常精简,许多扩展功能(如 Gzip, JWT, Sessions)由外部或第三方库提供,您可以轻松通过 `r.Use()` 集成它们。
|
||||
|
||||
## 条件中间件 (Conditional Middleware)
|
||||
|
||||
Touka 支持根据布尔条件动态启用或禁用中间件。这在根据环境配置启用插件时非常有用。
|
||||
|
||||
### `UseIf`
|
||||
|
||||
```go
|
||||
// 仅在 Debug 模式下启用日志
|
||||
r.Use(r.UseIf(config.IsDebug, MyDebugLogger))
|
||||
```
|
||||
|
||||
### `UseChainIf` (条件中间件链)
|
||||
|
||||
如果您有一组相关的中间件需要根据同一条件启用,可以使用 `UseChainIf`。
|
||||
|
||||
```go
|
||||
r.Use(r.UseChainIf(config.EnableMetrics,
|
||||
MetricsMiddleware,
|
||||
PrometheusMiddleware,
|
||||
MonitoringMiddleware,
|
||||
))
|
||||
```
|
||||
|
||||
这些方法利用了 `MiddlewareXFunc`(即返回 `HandlerFunc` 的工厂函数),确保中间件实例按需创建或高效复用。
|
||||
76
docs/quickstart.md
Normal file
76
docs/quickstart.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# 快速开始
|
||||
|
||||
本指南将帮助您在几分钟内启动并运行一个 Touka 应用。
|
||||
|
||||
## 安装
|
||||
|
||||
确保您的环境中已经安装了 Go 1.25 或更高版本。
|
||||
|
||||
在您的项目目录中运行:
|
||||
|
||||
```bash
|
||||
go get github.com/infinite-iroha/touka
|
||||
```
|
||||
|
||||
## 基础示例
|
||||
|
||||
创建一个 `main.go` 文件,并粘贴以下代码:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
"log"
|
||||
"github.com/infinite-iroha/touka"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 创建默认引擎(包含 Recovery 中间件)
|
||||
r := touka.Default()
|
||||
|
||||
// 2. 注册一个简单的 GET 路由
|
||||
r.GET("/ping", func(c *touka.Context) {
|
||||
c.JSON(http.StatusOK, touka.H{
|
||||
"message": "pong",
|
||||
"time": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// 3. 注册带参数的路由
|
||||
r.GET("/hello/:name", func(c *touka.Context) {
|
||||
name := c.Param("name")
|
||||
c.String(http.StatusOK, "Hello, %s!", name)
|
||||
})
|
||||
|
||||
// 4. 启动服务器并监听 8080 端口
|
||||
log.Println("Touka server is running on :8080")
|
||||
if err := r.Run(":8080"); err != nil {
|
||||
log.Fatalf("Server failed: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 运行应用
|
||||
|
||||
执行以下命令启动服务器:
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
现在,您可以访问:
|
||||
- `http://localhost:8080/ping`
|
||||
- `http://localhost:8080/hello/World`
|
||||
|
||||
## 优雅停机
|
||||
|
||||
在生产环境中,我们推荐使用 `RunShutdown` 方法来启动服务器,它会监听系统信号并在关闭前等待正在处理的请求完成。
|
||||
|
||||
```go
|
||||
// 等待 10 秒以处理剩余请求
|
||||
if err := r.RunShutdown(":8080", 10*time.Second); err != nil {
|
||||
log.Fatalf("Server forced to shutdown: %v", err)
|
||||
}
|
||||
```
|
||||
94
docs/routing.md
Normal file
94
docs/routing.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# 路由系统
|
||||
|
||||
Touka 拥有一个强大且灵活的路由系统,底层基于高性能的基数树(Radix Tree)实现。
|
||||
|
||||
## 基础路由
|
||||
|
||||
您可以为所有标准的 HTTP 方法注册处理器:
|
||||
|
||||
```go
|
||||
r.GET("/someGet", handle)
|
||||
r.POST("/somePost", handle)
|
||||
r.PUT("/somePut", handle)
|
||||
r.DELETE("/someDelete", handle)
|
||||
r.PATCH("/somePatch", handle)
|
||||
r.HEAD("/someHead", handle)
|
||||
r.OPTIONS("/someOptions", handle)
|
||||
|
||||
// 注册所有上述方法的路由
|
||||
r.ANY("/any", handle)
|
||||
```
|
||||
|
||||
## 路径参数 (Named Parameters)
|
||||
|
||||
使用冒号 `:` 定义路径参数。参数值可以通过 `c.Param(key)` 获取。
|
||||
|
||||
```go
|
||||
// 匹配 /user/john, 不匹配 /user/ 或 /user/john/send
|
||||
r.GET("/user/:name", func(c *touka.Context) {
|
||||
name := c.Param("name")
|
||||
c.String(http.StatusOK, "Hello %s", name)
|
||||
})
|
||||
|
||||
// 匹配 /user/john/send
|
||||
r.GET("/user/:name/:action", func(c *touka.Context) {
|
||||
name := c.Param("name")
|
||||
action := c.Param("action")
|
||||
c.String(http.StatusOK, "%s is doing %s", name, action)
|
||||
})
|
||||
```
|
||||
|
||||
## 通配符路由 (Catch-all Parameters)
|
||||
|
||||
使用星号 `*` 定义通配符路由,它会捕获路径中该位置之后的所有内容。
|
||||
|
||||
```go
|
||||
// 匹配 /src/main.go, /src/scripts/app.js 等
|
||||
r.GET("/src/*filepath", func(c *touka.Context) {
|
||||
path := c.Param("filepath")
|
||||
c.String(http.StatusOK, "Viewing file: %s", path)
|
||||
})
|
||||
```
|
||||
|
||||
## 路由组 (RouterGroup)
|
||||
|
||||
路由组允许您共享公共路径前缀或中间件,使代码结构更清晰。
|
||||
|
||||
```go
|
||||
v1 := r.Group("/api/v1")
|
||||
{
|
||||
v1.GET("/login", loginEndpoint)
|
||||
v1.GET("/submit", submitEndpoint)
|
||||
}
|
||||
|
||||
v2 := r.Group("/api/v2")
|
||||
v2.Use(AuthMiddleware()) // 仅应用于 v2 组
|
||||
{
|
||||
v2.POST("/data", dataEndpoint)
|
||||
}
|
||||
```
|
||||
|
||||
## 路由行为配置
|
||||
|
||||
Touka 允许您自定义路由匹配的行为:
|
||||
|
||||
- **RedirectTrailingSlash**: 如果启用(默认),请求 `/foo/` 会被重定向到 `/foo`(如果只有后者注册了),反之亦然。
|
||||
- **RedirectFixedPath**: 如果启用(默认),引擎会尝试修复路径大小写或移除多余的斜杠并重定向。
|
||||
- **HandleMethodNotAllowed**: 如果启用,当请求路径匹配但方法不匹配时,返回 405 而非 404。
|
||||
|
||||
```go
|
||||
r := touka.New()
|
||||
r.RedirectTrailingSlash = true
|
||||
r.HandleMethodNotAllowed = true
|
||||
```
|
||||
|
||||
## 获取已注册路由信息
|
||||
|
||||
您可以使用 `GetRouterInfo` 获取当前引擎中所有已注册路由的列表。
|
||||
|
||||
```go
|
||||
routes := r.GetRouterInfo()
|
||||
for _, route := range routes {
|
||||
fmt.Printf("Method: %s, Path: %s\n", route.Method, route.Path)
|
||||
}
|
||||
```
|
||||
79
docs/sse.md
Normal file
79
docs/sse.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Server-Sent Events (SSE)
|
||||
|
||||
Server-Sent Events 允许服务器向客户端实时推送数据。Touka 对此提供了原生且易用的支持。
|
||||
|
||||
## 核心结构:`Event`
|
||||
|
||||
`Event` 结构体代表一个 SSE 消息:
|
||||
|
||||
```go
|
||||
type Event struct {
|
||||
Event string // 事件名称
|
||||
Data string // 数据内容 (支持多行)
|
||||
Id string // 事件 ID
|
||||
Retry string // 重连时间 (毫秒)
|
||||
}
|
||||
```
|
||||
|
||||
## 模式一:回调模式 (EventStream)
|
||||
|
||||
这是最推荐的使用方式,它更简单且能自动管理连接生命周期。
|
||||
|
||||
```go
|
||||
r.GET("/events", func(c *touka.Context) {
|
||||
c.EventStream(func(w io.Writer) bool {
|
||||
// 构建事件
|
||||
event := touka.Event{
|
||||
Data: "现在的时间是: " + time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
// 渲染并写入
|
||||
if err := event.Render(w); err != nil {
|
||||
return false // 发生写入错误(如客户端断开),返回 false 停止流
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
return true // 返回 true 继续下一次循环
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 模式二:通道模式 (EventStreamChan)
|
||||
|
||||
如果您需要更高级的并发控制(例如从多个异步源接收数据),可以使用通道模式。
|
||||
|
||||
```go
|
||||
r.GET("/events-chan", func(c *touka.Context) {
|
||||
eventChan, errChan := c.EventStreamChan()
|
||||
|
||||
// 监听错误/断开连接
|
||||
go func() {
|
||||
if err := <-errChan; err != nil {
|
||||
log.Printf("SSE 错误: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 发送数据
|
||||
go func() {
|
||||
defer close(eventChan) // 务必在结束时关闭
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
select {
|
||||
case <-c.Request.Context().Done():
|
||||
return
|
||||
default:
|
||||
eventChan <- touka.Event{
|
||||
Data: fmt.Sprintf("消息 #%d", i),
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **资源回收**: 确保在 `EventStreamChan` 模式下正确监听 `c.Request.Context().Done()` 以避免 Goroutine 泄漏。
|
||||
2. **数据格式**: SSE 协议要求数据为 UTF-8。Touka 的 `Render` 方法会自动处理多行数据并加上必要的 `data:` 前缀。
|
||||
3. **超时管理**: SSE 连接通常是长连接,请确保您的反向代理(如 Nginx)配置了足够大的写超时时间。
|
||||
63
docs/static-files.md
Normal file
63
docs/static-files.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# 静态文件与资源
|
||||
|
||||
Touka 提供了多种方式来服务静态文件,这些方法都集成了 Touka 的统一错误处理机制。
|
||||
|
||||
## 服务本地目录
|
||||
|
||||
`StaticDir` 方法将 URL 路径映射到本地文件系统目录。
|
||||
|
||||
```go
|
||||
// 访问 /assets/js/main.js 将读取 ./static/js/main.js
|
||||
r.StaticDir("/assets", "./static")
|
||||
```
|
||||
|
||||
## 服务单个文件
|
||||
|
||||
`StaticFile` 用于将特定的 URL 映射到单个本地文件。
|
||||
|
||||
```go
|
||||
r.StaticFile("/favicon.ico", "./resources/favicon.ico")
|
||||
```
|
||||
|
||||
## 集成 Go 嵌入式资源 (embed.FS)
|
||||
|
||||
使用 Go 1.16+ 的 `embed` 特性,您可以将整个静态前端项目编译进二进制文件中。
|
||||
|
||||
```go
|
||||
//go:embed dist/*
|
||||
var content embed.FS
|
||||
|
||||
func main() {
|
||||
r := touka.Default()
|
||||
|
||||
// 剥离 "dist" 前缀并包装为 http.FS
|
||||
fsroot, _ := fs.Sub(content, "dist")
|
||||
|
||||
// 使用 StaticFS 提供服务
|
||||
r.StaticFS("/static", http.FS(fsroot))
|
||||
|
||||
// 您也可以使用 StaticFS 服务根路径
|
||||
// r.StaticFS("/", http.FS(fsroot))
|
||||
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
## 未匹配路径作为文件服务 (UnMatchFS)
|
||||
|
||||
这是一个独特的功能:当没有任何 API 路由匹配时,尝试从指定的文件系统中查找并返回文件。这非常适合用于单页应用(SPA)的部署。
|
||||
|
||||
```go
|
||||
r := touka.New()
|
||||
r.SetUnMatchFS(http.Dir("./frontend/dist"), true)
|
||||
|
||||
// API 路由
|
||||
r.GET("/api/status", handleStatus)
|
||||
|
||||
// 如果请求 /index.html 且没有 /index.html 的路由,
|
||||
// 则会从 ./frontend/dist/index.html 读取。
|
||||
```
|
||||
|
||||
## 性能提示
|
||||
|
||||
对于高负载的静态资源分发,虽然 Touka 表现出色,但我们仍建议在生产环境中使用 Nginx 或 CDN 站在 Touka 前面来处理静态文件,让 Touka 专注于处理动态逻辑。
|
||||
Loading…
Add table
Add a link
Reference in a new issue