use new resolveRoutePath replace path.Join && add UseIf

This commit is contained in:
wjqserver 2025-06-17 14:20:14 +08:00
parent bfc6b439e4
commit 53544644af
5 changed files with 198 additions and 4 deletions

View file

@ -570,6 +570,11 @@ func (c *Context) SetHeaders(headers map[string][]string) {
} }
} }
// 获取所有resp Headers
func (c *Context) GetAllRespHeader() http.Header {
return c.Writer.Header()
}
// GetAllReqHeader 获取所有请求头部 // GetAllReqHeader 获取所有请求头部
func (c *Context) GetAllReqHeader() http.Header { func (c *Context) GetAllReqHeader() http.Header {
return c.Request.Header return c.Request.Header

View file

@ -560,7 +560,8 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRouter {
// Handle 注册通用 HTTP 方法的路由 // Handle 注册通用 HTTP 方法的路由
// 这是所有具体 HTTP 方法注册的基础方法 // 这是所有具体 HTTP 方法注册的基础方法
func (engine *Engine) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) { func (engine *Engine) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) {
absolutePath := path.Join("/", relativePath) // 修正:统一使用 path.Join 进行路径拼接 //absolutePath := path.Join("/", relativePath) // 修正:统一使用 path.Join 进行路径拼接
absolutePath := resolveRoutePath("/", relativePath)
// 修正:将全局中间件与此路由的处理函数合并 // 修正:将全局中间件与此路由的处理函数合并
fullHandlers := engine.combineHandlers(engine.globalHandlers, handlers) fullHandlers := engine.combineHandlers(engine.globalHandlers, handlers)
engine.addRoute(httpMethod, absolutePath, "/", fullHandlers) engine.addRoute(httpMethod, absolutePath, "/", fullHandlers)
@ -622,7 +623,7 @@ func (engine *Engine) GetRouterInfo() []RouteInfo {
func (engine *Engine) Group(relativePath string, handlers ...HandlerFunc) IRouter { func (engine *Engine) Group(relativePath string, handlers ...HandlerFunc) IRouter {
return &RouterGroup{ return &RouterGroup{
Handlers: engine.combineHandlers(engine.globalHandlers, handlers), // 继承全局中间件 Handlers: engine.combineHandlers(engine.globalHandlers, handlers), // 继承全局中间件
basePath: path.Join("/", relativePath), basePath: resolveRoutePath("/", relativePath),
engine: engine, // 指向 Engine 实例 engine: engine, // 指向 Engine 实例
} }
} }
@ -645,7 +646,7 @@ func (group *RouterGroup) Use(middleware ...HandlerFunc) IRouter {
// Handle 注册通用 HTTP 方法的路由到当前组 // Handle 注册通用 HTTP 方法的路由到当前组
// 路径是相对于当前组的 basePath // 路径是相对于当前组的 basePath
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) { func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) {
absolutePath := path.Join(group.basePath, relativePath) absolutePath := resolveRoutePath(group.basePath, relativePath)
fullHandlers := group.engine.combineHandlers(group.Handlers, handlers) fullHandlers := group.engine.combineHandlers(group.Handlers, handlers)
group.engine.addRoute(httpMethod, absolutePath, group.basePath, fullHandlers) group.engine.addRoute(httpMethod, absolutePath, group.basePath, fullHandlers)
} }
@ -686,7 +687,7 @@ func (group *RouterGroup) ANY(relativePath string, handlers ...HandlerFunc) {
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) IRouter { func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) IRouter {
return &RouterGroup{ return &RouterGroup{
Handlers: group.engine.combineHandlers(group.Handlers, handlers), Handlers: group.engine.combineHandlers(group.Handlers, handlers),
basePath: path.Join(group.basePath, relativePath), basePath: resolveRoutePath(group.basePath, relativePath),
engine: group.engine, // 指向 Engine 实例 engine: group.engine, // 指向 Engine 实例
} }
} }

40
midware_x.go Normal file
View file

@ -0,0 +1,40 @@
package touka
// UseIf 是一个条件中间件包装器。
// 如果 `condition` 为 true它将执行提供的 `middlewares` 链。
// 如果 `condition` 为 false它会直接跳过这些中间件调用下一个处理器。
func (engine *Engine) UseIf(condition bool, middlewares ...HandlerFunc) HandlerFunc {
if !condition {
return func(c *Context) {
c.Next()
}
}
// 如果条件为真,返回一个处理器,该处理器负责执行子链。
// 我们通过修改 Context 的状态来做到这一点。
// 这比递归闭包或替换 c.Next 要清晰。
return func(c *Context) {
// 1. 将当前的处理链和索引位置保存下来。
originalHandlers := c.handlers
originalIndex := c.index
// 2. 创建一个新的处理链,它由传入的中间件和最后的 "恢复并继续" 处理器组成。
subChain := make(HandlersChain, len(middlewares)+1)
copy(subChain, middlewares)
// 3. 在子链的末尾添加一个特殊的 handler。
// 它的作用是恢复原始的处理链状态,并继续执行原始链中 `If` 之后的处理器。
subChain[len(middlewares)] = func(ctx *Context) {
ctx.handlers = originalHandlers
ctx.index = originalIndex
ctx.Next()
}
// 4. 将 Context 的处理器链临时替换为我们的子链,并重置索引。
c.handlers = subChain
c.index = -1
// 5. 调用 c.Next() 来启动子链的执行。
c.Next()
}
}

49
path.go Normal file
View file

@ -0,0 +1,49 @@
package touka
import (
"path"
"strings"
)
// resolveRoutePath 安全地拼接基础路径和相对路径,并正确处理尾部斜杠。
// 这是一个为高性能路由注册优化的版本。
func resolveRoutePath(basePath, relativePath string) string {
// 如果相对路径为空,直接返回基础路径
if relativePath == "" {
return basePath
}
// 如果基础路径为空,直接返回相对路径(确保以/开头)
if basePath == "" {
return relativePath
}
// 使用 strings.Builder 来高效构建路径,避免多次字符串分配
var b strings.Builder
// 估算一个合理的容量以减少扩容
b.Grow(len(basePath) + len(relativePath) + 1)
b.WriteString(basePath)
// 检查 basePath 是否以斜杠结尾
if basePath[len(basePath)-1] != '/' {
b.WriteByte('/') // 如果没有,则添加
}
// 检查 relativePath 是否以斜杠开头,如果是,则移除
if relativePath[0] == '/' {
b.WriteString(relativePath[1:])
} else {
b.WriteString(relativePath)
}
// path.Clean 仍然是处理 '..' 和 '//' 等复杂情况最可靠的方式。
// 我们可以只在最终结果上调用一次,而不是在拼接过程中。
finalPath := path.Clean(b.String())
// 关键:如果原始 relativePath 有尾部斜杠,但 Clean 把它移除了,我们要加回来。
// 只有当最终路径不是根路径 "/" 时才需要加回。
if strings.HasSuffix(relativePath, "/") && finalPath != "/" {
return finalPath + "/"
}
return finalPath
}

99
path_test.go Normal file
View file

@ -0,0 +1,99 @@
// touka/path_test.go
package touka
import (
"fmt"
"path"
"strings"
"testing"
)
func TestResolveRoutePath(t *testing.T) {
// 定义一组测试用例
testCases := []struct {
basePath string
relativePath string
expected string
}{
// --- 基本情况 ---
{basePath: "/api", relativePath: "/v1", expected: "/api/v1"},
{basePath: "/api/", relativePath: "v1", expected: "/api/v1"},
{basePath: "/api", relativePath: "v1", expected: "/api/v1"},
{basePath: "/api/", relativePath: "/v1", expected: "/api/v1"},
// --- 尾部斜杠处理 ---
{basePath: "/api", relativePath: "/v1/", expected: "/api/v1/"},
{basePath: "/api/", relativePath: "v1/", expected: "/api/v1/"},
{basePath: "", relativePath: "/v1/", expected: "/v1/"},
{basePath: "/", relativePath: "/v1/", expected: "/v1/"},
// --- 根路径和空路径 ---
{basePath: "/", relativePath: "/", expected: "/"},
{basePath: "/api", relativePath: "/", expected: "/api/"},
{basePath: "/api/", relativePath: "/", expected: "/api/"},
{basePath: "/", relativePath: "/users", expected: "/users"},
{basePath: "/users", relativePath: "", expected: "/users"},
{basePath: "", relativePath: "/users", expected: "/users"},
// --- 路径清理测试 (由 path.Clean 处理) ---
{basePath: "/api/v1", relativePath: "../v2", expected: "/api/v2"},
{basePath: "/api/v1/", relativePath: "../v2/", expected: "/api/v2/"},
{basePath: "/api//v1", relativePath: "/users", expected: "/api/v1/users"},
{basePath: "/api/./v1", relativePath: "/users", expected: "/api/v1/users"},
}
for _, tc := range testCases {
// 使用 t.Run 为每个测试用例创建一个子测试,方便定位问题
testName := fmt.Sprintf("base:'%s', rel:'%s'", tc.basePath, tc.relativePath)
t.Run(testName, func(t *testing.T) {
result := resolveRoutePath(tc.basePath, tc.relativePath)
if result != tc.expected {
t.Errorf("resolveRoutePath('%s', '%s') = '%s'; want '%s'",
tc.basePath, tc.relativePath, result, tc.expected)
}
})
}
}
// 性能基准测试,用于观测优化效果
func BenchmarkResolveRoutePath(b *testing.B) {
basePath := "/api/v1/some/long/path"
relativePath := "/users/profile/details/"
// b.N 是由 testing 包提供的循环次数
for i := 0; i < b.N; i++ {
// 在循环内调用被测试的函数
resolveRoutePath(basePath, relativePath)
}
}
// (可选)可以保留旧的实现,进行性能对比
func resolveRoutePath_Old(basePath, relativePath string) string {
if relativePath == "/" {
if basePath != "" && basePath != "/" && !strings.HasSuffix(basePath, "/") {
return basePath + "/"
}
return basePath
}
finalPath := path.Clean(basePath + "/" + relativePath)
if strings.HasSuffix(relativePath, "/") && !strings.HasSuffix(finalPath, "/") {
return finalPath + "/"
}
return finalPath
}
func BenchmarkResolveRoutePath_Old(b *testing.B) {
basePath := "/api/v1/some/long/path"
relativePath := "/users/profile/details/"
for i := 0; i < b.N; i++ {
resolveRoutePath_Old(basePath, relativePath)
}
}
func BenchmarkJoinStd(b *testing.B) {
basePath := "/api/v1/some/long/path"
relativePath := "/users/profile/details/"
for i := 0; i < b.N; i++ {
path.Join(basePath, relativePath)
}
}