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 获取所有请求头部
|
// GetAllReqHeader 获取所有请求头部
|
||||||
func (c *Context) GetAllReqHeader() http.Header {
|
func (c *Context) GetAllReqHeader() http.Header {
|
||||||
return c.Request.Header
|
return c.Request.Header
|
||||||
|
|
|
||||||
|
|
@ -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
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