touka/docs/routing.md
wjqserver e4d3eed379 feat: redesign server startup around Run options
Replace the old RunShutdown and RunTLS style entry points with a single Run(opts...) API for v1. Add focused startup semantics tests, keep TLS and graceful shutdown independent, ensure sibling servers are cleaned up on startup failure, and update docs to match the new option-based startup model.
2026-04-07 17:44:55 +08:00

4 KiB
Raw Blame History

路由系统

Touka 拥有一个强大且灵活的路由系统底层基于高性能的基数树Radix Tree实现。

基础路由

您可以为所有标准的 HTTP 方法注册处理器:

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)

// 同时注册多个方法
r.HandleFunc([]string{"GET", "POST"}, "/multi", handle)

服务器级 OPTIONS * 请求不需要单独注册路由。Touka 会直接返回一个空的 200 OK 响应,而不会把它当成 / 路由来匹配。

路径参数 (Named Parameters)

使用冒号 : 定义路径参数。参数值可以通过 c.Param(key) 获取。

// 匹配 /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)

使用星号 * 定义通配符路由,它会捕获路径中该位置之后的所有内容。

// 匹配 /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)

路由组允许您共享公共路径前缀或中间件,使代码结构更清晰。

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。
r := touka.New()
r.SetRedirectTrailingSlash(true)
r.SetHandleMethodNotAllowed(true)

获取已注册路由信息

您可以使用 GetRouterInfo 获取当前引擎中所有已注册路由的列表。

routes := r.GetRouterInfo()
for _, route := range routes {
    fmt.Printf("Method: %s, Path: %s\n", route.Method, route.Path)
}

自定义 404 处理

当请求没有匹配到任何路由时Touka 会返回 404。您可以自定义 404 的处理逻辑:

// 使用单个处理器
r.NoRoute(func(c *touka.Context) {
    c.JSON(http.StatusNotFound, touka.H{
        "error": "资源未找到",
        "path":  c.Request.URL.Path,
    })
})

// 使用处理器链
r.NoRoutes(
    LogNotFoundMiddleware(),
    func(c *touka.Context) {
        c.JSON(http.StatusNotFound, touka.H{"error": "Not found"})
    },
)

注意NoRouteNoRoutes 不是处理链的终点,您仍然可以在其中调用 c.Next() 来继续执行默认的 404 处理。

静态文件路由

Touka 提供了便捷的方法来注册静态文件路由:

// 服务整个目录
r.StaticDir("/assets", "./static")
// 访问 /assets/js/main.js 将返回 ./static/js/main.js

// 服务单个文件
r.StaticFile("/favicon.ico", "./resources/favicon.ico")

// 服务嵌入式文件系统
//go:embed dist/*
var content embed.FS

func main() {
    r := touka.Default()
    fsroot, _ := fs.Sub(content, "dist")
    r.StaticFS("/", http.FS(fsroot))
    r.Run(touka.WithAddr(":8080"))
}

这些方法同样可以在路由组中使用:

api := r.Group("/api")
api.StaticDir("/files", "./uploads")
api.StaticFile("/logo", "./assets/logo.png")