mirror of
https://github.com/infinite-iroha/touka.git
synced 2026-02-02 16:31:11 +08:00
use new resolveRoutePath replace path.Join && add UseIf
This commit is contained in:
parent
bfc6b439e4
commit
53544644af
5 changed files with 198 additions and 4 deletions
|
|
@ -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 获取所有请求头部
|
||||
func (c *Context) GetAllReqHeader() http.Header {
|
||||
return c.Request.Header
|
||||
|
|
|
|||
|
|
@ -560,7 +560,8 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRouter {
|
|||
// Handle 注册通用 HTTP 方法的路由
|
||||
// 这是所有具体 HTTP 方法注册的基础方法
|
||||
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)
|
||||
engine.addRoute(httpMethod, absolutePath, "/", fullHandlers)
|
||||
|
|
@ -622,7 +623,7 @@ func (engine *Engine) GetRouterInfo() []RouteInfo {
|
|||
func (engine *Engine) Group(relativePath string, handlers ...HandlerFunc) IRouter {
|
||||
return &RouterGroup{
|
||||
Handlers: engine.combineHandlers(engine.globalHandlers, handlers), // 继承全局中间件
|
||||
basePath: path.Join("/", relativePath),
|
||||
basePath: resolveRoutePath("/", relativePath),
|
||||
engine: engine, // 指向 Engine 实例
|
||||
}
|
||||
}
|
||||
|
|
@ -645,7 +646,7 @@ func (group *RouterGroup) Use(middleware ...HandlerFunc) IRouter {
|
|||
// Handle 注册通用 HTTP 方法的路由到当前组
|
||||
// 路径是相对于当前组的 basePath
|
||||
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)
|
||||
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 {
|
||||
return &RouterGroup{
|
||||
Handlers: group.engine.combineHandlers(group.Handlers, handlers),
|
||||
basePath: path.Join(group.basePath, relativePath),
|
||||
basePath: resolveRoutePath(group.basePath, relativePath),
|
||||
engine: group.engine, // 指向 Engine 实例
|
||||
}
|
||||
}
|
||||
|
|
|
|||
40
midware_x.go
Normal file
40
midware_x.go
Normal 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
49
path.go
Normal 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
99
path_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue