mirror of
https://github.com/infinite-iroha/touka.git
synced 2026-02-03 08:51:11 +08:00
Compare commits
No commits in common. "b94152318610179a7592c64bd4e3304226833767" and "229d15d405474d9e491cb69934278d0651ae249e" have entirely different histories.
b941523186
...
229d15d405
5 changed files with 4 additions and 247 deletions
|
|
@ -570,11 +570,6 @@ 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,8 +560,7 @@ 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)
|
||||||
|
|
@ -623,7 +622,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: resolveRoutePath("/", relativePath),
|
basePath: path.Join("/", relativePath),
|
||||||
engine: engine, // 指向 Engine 实例
|
engine: engine, // 指向 Engine 实例
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -646,7 +645,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 := resolveRoutePath(group.basePath, relativePath)
|
absolutePath := path.Join(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)
|
||||||
}
|
}
|
||||||
|
|
@ -687,7 +686,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: resolveRoutePath(group.basePath, relativePath),
|
basePath: path.Join(group.basePath, relativePath),
|
||||||
engine: group.engine, // 指向 Engine 实例
|
engine: group.engine, // 指向 Engine 实例
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
89
midware_x.go
89
midware_x.go
|
|
@ -1,89 +0,0 @@
|
||||||
package touka
|
|
||||||
|
|
||||||
// UseChainIf 是一个条件中间件包装器。
|
|
||||||
// 如果 `condition` 为 true,它将执行提供的 `middlewares` 链。
|
|
||||||
// 如果 `condition` 为 false,它会直接跳过这些中间件,调用下一个处理器。
|
|
||||||
func (engine *Engine) UseChainIf(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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UseIf 是一个条件中间件包装器
|
|
||||||
func (engine *Engine) UseIf(condition bool, middlewares HandlerFunc) HandlerFunc {
|
|
||||||
if !condition {
|
|
||||||
return func(c *Context) {
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return middlewares
|
|
||||||
}
|
|
||||||
|
|
||||||
func (engine *Engine) UseNewChainIf(condition bool, middlewares ...HandlerFunc) HandlerFunc {
|
|
||||||
// 如果条件不满足或没有提供中间件,返回一个“穿透”中间件。
|
|
||||||
if !condition || len(middlewares) == 0 {
|
|
||||||
return func(c *Context) {
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果条件满足,返回一个处理器,该处理器负责按顺序执行这个子链。
|
|
||||||
return func(c *Context) {
|
|
||||||
// 保存原始的处理链和当前执行索引,以便在子链结束后恢复。
|
|
||||||
originalHandlers := c.handlers
|
|
||||||
originalIndex := c.index
|
|
||||||
|
|
||||||
// 创建一个新的临时处理链。
|
|
||||||
// 这个链由传入的 `middlewares` 和一个特殊的“恢复”处理器组成。
|
|
||||||
subChain := make(HandlersChain, len(middlewares)+1)
|
|
||||||
copy(subChain, middlewares)
|
|
||||||
|
|
||||||
// 在子链的末尾添加“恢复”处理器。
|
|
||||||
// 当所有 `middlewares` 都执行完毕并调用了 Next() 后,这个函数会被执行。
|
|
||||||
subChain[len(middlewares)] = func(ctx *Context) {
|
|
||||||
// 恢复原始的处理链状态。
|
|
||||||
ctx.handlers = originalHandlers
|
|
||||||
ctx.index = originalIndex
|
|
||||||
// 继续执行原始处理链中 `ChainIf` 之后的下一个处理器。
|
|
||||||
ctx.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将 Context 的处理器链临时替换为我们的子链,并重置索引以从头开始。
|
|
||||||
c.handlers = subChain
|
|
||||||
c.index = -1
|
|
||||||
|
|
||||||
// 调用 c.Next() 来启动这个子链的执行。
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
49
path.go
49
path.go
|
|
@ -1,49 +0,0 @@
|
||||||
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
99
path_test.go
|
|
@ -1,99 +0,0 @@
|
||||||
// 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