mirror of
https://github.com/infinite-iroha/touka.git
synced 2026-02-02 16:31:11 +08:00
Compare commits
5 commits
ee0ebc986c
...
7b536ac137
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b536ac137 | ||
|
|
b348d7d41f | ||
|
|
60b2936eff | ||
|
|
9cfc82a347 | ||
|
|
904aea5df8 |
5 changed files with 76 additions and 28 deletions
18
context.go
18
context.go
|
|
@ -65,6 +65,10 @@ type Context struct {
|
||||||
|
|
||||||
// 请求体Body大小限制
|
// 请求体Body大小限制
|
||||||
MaxRequestBodySize int64
|
MaxRequestBodySize int64
|
||||||
|
|
||||||
|
// skippedNodes 用于记录跳过的节点信息,以便回溯
|
||||||
|
// 通常在处理嵌套路由时使用
|
||||||
|
SkippedNodes []skippedNode
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Context 相关方法实现 ---
|
// --- Context 相关方法实现 ---
|
||||||
|
|
@ -80,7 +84,13 @@ func (c *Context) reset(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Request = req
|
c.Request = req
|
||||||
c.Params = c.Params[:0] // 清空 Params 切片,而不是重新分配,以复用底层数组
|
//c.Params = c.Params[:0] // 清空 Params 切片,而不是重新分配,以复用底层数组
|
||||||
|
//避免params长度为0
|
||||||
|
if cap(c.Params) > 0 {
|
||||||
|
c.Params = c.Params[:0]
|
||||||
|
} else {
|
||||||
|
c.Params = make(Params, 0, c.engine.maxParams)
|
||||||
|
}
|
||||||
c.handlers = nil
|
c.handlers = nil
|
||||||
c.index = -1 // 初始为 -1,`Next()` 将其设置为 0
|
c.index = -1 // 初始为 -1,`Next()` 将其设置为 0
|
||||||
c.Keys = make(map[string]any) // 每次请求重新创建 map,避免数据污染
|
c.Keys = make(map[string]any) // 每次请求重新创建 map,避免数据污染
|
||||||
|
|
@ -90,6 +100,12 @@ func (c *Context) reset(w http.ResponseWriter, req *http.Request) {
|
||||||
c.ctx = req.Context() // 使用请求的上下文,继承其取消信号和值
|
c.ctx = req.Context() // 使用请求的上下文,继承其取消信号和值
|
||||||
c.sameSite = http.SameSiteDefaultMode // 默认 SameSite 模式
|
c.sameSite = http.SameSiteDefaultMode // 默认 SameSite 模式
|
||||||
c.MaxRequestBodySize = c.engine.GlobalMaxRequestBodySize
|
c.MaxRequestBodySize = c.engine.GlobalMaxRequestBodySize
|
||||||
|
|
||||||
|
if cap(c.SkippedNodes) > 0 {
|
||||||
|
c.SkippedNodes = c.SkippedNodes[:0]
|
||||||
|
} else {
|
||||||
|
c.SkippedNodes = make([]skippedNode, 0, 256)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next 在处理链中执行下一个处理函数
|
// Next 在处理链中执行下一个处理函数
|
||||||
|
|
|
||||||
48
engine.go
48
engine.go
|
|
@ -421,6 +421,41 @@ func getHandlerName(h HandlerFunc) string {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MaxSkippedNodesCap = 256
|
||||||
|
|
||||||
|
// TempSkippedNodesPool 存储 *[]skippedNode 以复用内存
|
||||||
|
var TempSkippedNodesPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
// 返回一个指向容量为 256 的新切片的指针
|
||||||
|
s := make([]skippedNode, 0, MaxSkippedNodesCap)
|
||||||
|
return &s
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTempSkippedNodes 从 Pool 中获取一个 *[]skippedNode 指针
|
||||||
|
func GetTempSkippedNodes() *[]skippedNode {
|
||||||
|
// 直接返回 Pool 中存储的指针
|
||||||
|
return TempSkippedNodesPool.Get().(*[]skippedNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutTempSkippedNodes 将用完的 *[]skippedNode 指针放回 Pool
|
||||||
|
func PutTempSkippedNodes(skippedNodes *[]skippedNode) {
|
||||||
|
if skippedNodes == nil || *skippedNodes == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查容量是否符合预期。如果容量不足,则丢弃,不放回 Pool。
|
||||||
|
if cap(*skippedNodes) < MaxSkippedNodesCap {
|
||||||
|
return // 丢弃该对象,让 Pool 在下次 Get 时通过 New 重新分配
|
||||||
|
}
|
||||||
|
|
||||||
|
// 长度重置为 0,保留容量,实现复用
|
||||||
|
*skippedNodes = (*skippedNodes)[:0]
|
||||||
|
|
||||||
|
// 将指针存回 Pool
|
||||||
|
TempSkippedNodesPool.Put(skippedNodes)
|
||||||
|
}
|
||||||
|
|
||||||
// 405中间件
|
// 405中间件
|
||||||
func MethodNotAllowed() HandlerFunc {
|
func MethodNotAllowed() HandlerFunc {
|
||||||
return func(c *Context) {
|
return func(c *Context) {
|
||||||
|
|
@ -432,9 +467,10 @@ func MethodNotAllowed() HandlerFunc {
|
||||||
// 如果是 OPTIONS 请求,尝试查找所有允许的方法
|
// 如果是 OPTIONS 请求,尝试查找所有允许的方法
|
||||||
allowedMethods := []string{}
|
allowedMethods := []string{}
|
||||||
for _, treeIter := range engine.methodTrees {
|
for _, treeIter := range engine.methodTrees {
|
||||||
var tempSkippedNodes []skippedNode
|
|
||||||
// 注意这里 treeIter.root 才是正确的,因为 treeIter 是 methodTree 类型
|
// 注意这里 treeIter.root 才是正确的,因为 treeIter 是 methodTree 类型
|
||||||
value := treeIter.root.getValue(requestPath, nil, &tempSkippedNodes, false)
|
tempSkippedNodes := GetTempSkippedNodes()
|
||||||
|
value := treeIter.root.getValue(requestPath, nil, tempSkippedNodes, false)
|
||||||
|
PutTempSkippedNodes(tempSkippedNodes)
|
||||||
if value.handlers != nil {
|
if value.handlers != nil {
|
||||||
allowedMethods = append(allowedMethods, treeIter.method)
|
allowedMethods = append(allowedMethods, treeIter.method)
|
||||||
}
|
}
|
||||||
|
|
@ -451,9 +487,10 @@ func MethodNotAllowed() HandlerFunc {
|
||||||
if treeIter.method == httpMethod { // 已经处理过当前方法,跳过
|
if treeIter.method == httpMethod { // 已经处理过当前方法,跳过
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var tempSkippedNodes []skippedNode // 用于临时查找,不影响主 Context
|
|
||||||
// 注意这里 treeIter.root 才是正确的,因为 treeIter 是 methodTree 类型
|
// 注意这里 treeIter.root 才是正确的,因为 treeIter 是 methodTree 类型
|
||||||
value := treeIter.root.getValue(requestPath, nil, &tempSkippedNodes, false) // 只查找是否存在,不需要参数
|
tempSkippedNodes := GetTempSkippedNodes()
|
||||||
|
value := treeIter.root.getValue(requestPath, nil, tempSkippedNodes, false) // 只查找是否存在,不需要参数
|
||||||
|
PutTempSkippedNodes(tempSkippedNodes)
|
||||||
if value.handlers != nil {
|
if value.handlers != nil {
|
||||||
// 使用定义的ErrorHandle处理
|
// 使用定义的ErrorHandle处理
|
||||||
engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
|
engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
|
||||||
|
|
@ -661,9 +698,8 @@ func (engine *Engine) handleRequest(c *Context) {
|
||||||
// 查找匹配的节点和处理函数
|
// 查找匹配的节点和处理函数
|
||||||
// 这里传递 &c.Params 而不是重新创建,以利用 Context 中预分配的容量
|
// 这里传递 &c.Params 而不是重新创建,以利用 Context 中预分配的容量
|
||||||
// skippedNodes 内部使用,因此无需从外部传入已分配的 slice
|
// skippedNodes 内部使用,因此无需从外部传入已分配的 slice
|
||||||
var skippedNodes []skippedNode // 用于回溯的跳过节点
|
|
||||||
// 直接在 rootNode 上调用 getValue 方法
|
// 直接在 rootNode 上调用 getValue 方法
|
||||||
value := rootNode.getValue(requestPath, &c.Params, &skippedNodes, true) // unescape=true 对路径参数进行 URL 解码
|
value := rootNode.getValue(requestPath, &c.Params, &c.SkippedNodes, true) // unescape=true 对路径参数进行 URL 解码
|
||||||
|
|
||||||
if value.handlers != nil {
|
if value.handlers != nil {
|
||||||
//c.handlers = engine.combineHandlers(engine.globalHandlers, value.handlers) // 组合全局中间件和路由处理函数
|
//c.handlers = engine.combineHandlers(engine.globalHandlers, value.handlers) // 组合全局中间件和路由处理函数
|
||||||
|
|
|
||||||
6
go.mod
6
go.mod
|
|
@ -5,12 +5,12 @@ go 1.25.1
|
||||||
require (
|
require (
|
||||||
github.com/WJQSERVER-STUDIO/go-utils/iox v0.0.2
|
github.com/WJQSERVER-STUDIO/go-utils/iox v0.0.2
|
||||||
github.com/WJQSERVER-STUDIO/httpc v0.8.2
|
github.com/WJQSERVER-STUDIO/httpc v0.8.2
|
||||||
github.com/WJQSERVER/wanf v0.0.0-20250810023226-e51d9d0737ee
|
github.com/WJQSERVER/wanf v0.0.2
|
||||||
github.com/fenthope/reco v0.0.4
|
github.com/fenthope/reco v0.0.4
|
||||||
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3
|
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
golang.org/x/net v0.46.0 // indirect
|
golang.org/x/net v0.48.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
6
go.sum
6
go.sum
|
|
@ -4,15 +4,21 @@ github.com/WJQSERVER-STUDIO/httpc v0.8.2 h1:PFPLodV0QAfGEP6915J57vIqoKu9cGuuiXG/
|
||||||
github.com/WJQSERVER-STUDIO/httpc v0.8.2/go.mod h1:8WhHVRO+olDFBSvL5PC/bdMkb6U3vRdPJ4p4pnguV5Y=
|
github.com/WJQSERVER-STUDIO/httpc v0.8.2/go.mod h1:8WhHVRO+olDFBSvL5PC/bdMkb6U3vRdPJ4p4pnguV5Y=
|
||||||
github.com/WJQSERVER/wanf v0.0.0-20250810023226-e51d9d0737ee h1:tJ31DNBn6UhWkk8fiikAQWqULODM+yBcGAEar1tzdZc=
|
github.com/WJQSERVER/wanf v0.0.0-20250810023226-e51d9d0737ee h1:tJ31DNBn6UhWkk8fiikAQWqULODM+yBcGAEar1tzdZc=
|
||||||
github.com/WJQSERVER/wanf v0.0.0-20250810023226-e51d9d0737ee/go.mod h1:q2Pyg+G+s1acMWxrbI4CwS/Yk76/BzLREEdZ8iFwUNE=
|
github.com/WJQSERVER/wanf v0.0.0-20250810023226-e51d9d0737ee/go.mod h1:q2Pyg+G+s1acMWxrbI4CwS/Yk76/BzLREEdZ8iFwUNE=
|
||||||
|
github.com/WJQSERVER/wanf v0.0.2 h1:E3dfHP6AACYamKn5BVUpi7pkO3L26WJycKF4AhGusXY=
|
||||||
|
github.com/WJQSERVER/wanf v0.0.2/go.mod h1:q2Pyg+G+s1acMWxrbI4CwS/Yk76/BzLREEdZ8iFwUNE=
|
||||||
github.com/fenthope/reco v0.0.4 h1:yo2g3aWwdoMpaZWZX4SdZOW7mCK82RQIU/YI8ZUQThM=
|
github.com/fenthope/reco v0.0.4 h1:yo2g3aWwdoMpaZWZX4SdZOW7mCK82RQIU/YI8ZUQThM=
|
||||||
github.com/fenthope/reco v0.0.4/go.mod h1:eMyS8HpdMVdJ/2WJt6Cvt8P1EH9Igzj5lSJrgc+0jeg=
|
github.com/fenthope/reco v0.0.4/go.mod h1:eMyS8HpdMVdJ/2WJt6Cvt8P1EH9Igzj5lSJrgc+0jeg=
|
||||||
github.com/go-json-experiment/json v0.0.0-20250813233538-9b1f9ea2e11b h1:6Q4zRHXS/YLOl9Ng1b1OOOBWMidAQZR3Gel0UKPC/KU=
|
github.com/go-json-experiment/json v0.0.0-20250813233538-9b1f9ea2e11b h1:6Q4zRHXS/YLOl9Ng1b1OOOBWMidAQZR3Gel0UKPC/KU=
|
||||||
github.com/go-json-experiment/json v0.0.0-20250813233538-9b1f9ea2e11b/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
github.com/go-json-experiment/json v0.0.0-20250813233538-9b1f9ea2e11b/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||||
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 h1:02WINGfSX5w0Mn+F28UyRoSt9uvMhKguwWMlOAh6U/0=
|
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 h1:02WINGfSX5w0Mn+F28UyRoSt9uvMhKguwWMlOAh6U/0=
|
||||||
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
|
|
|
||||||
20
tree.go
20
tree.go
|
|
@ -5,7 +5,6 @@
|
||||||
package touka
|
package touka
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
@ -27,12 +26,6 @@ func BytesToString(b []byte) string {
|
||||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
strColon = []byte(":") // 定义字节切片常量, 表示冒号, 用于路径参数识别
|
|
||||||
strStar = []byte("*") // 定义字节切片常量, 表示星号, 用于捕获所有路径识别
|
|
||||||
strSlash = []byte("/") // 定义字节切片常量, 表示斜杠, 用于路径分隔符识别
|
|
||||||
)
|
|
||||||
|
|
||||||
// Param 是单个 URL 参数, 由键和值组成.
|
// Param 是单个 URL 参数, 由键和值组成.
|
||||||
type Param struct {
|
type Param struct {
|
||||||
Key string // 参数的键名
|
Key string // 参数的键名
|
||||||
|
|
@ -106,17 +99,14 @@ func (n *node) addChild(child *node) {
|
||||||
|
|
||||||
// countParams 计算路径中参数(冒号)和捕获所有(星号)的数量.
|
// countParams 计算路径中参数(冒号)和捕获所有(星号)的数量.
|
||||||
func countParams(path string) uint16 {
|
func countParams(path string) uint16 {
|
||||||
var n uint16
|
colons := strings.Count(path, ":")
|
||||||
s := StringToBytes(path) // 将路径字符串转换为字节切片
|
stars := strings.Count(path, "*")
|
||||||
n += uint16(bytes.Count(s, strColon)) // 统计冒号的数量
|
return uint16(colons + stars)
|
||||||
n += uint16(bytes.Count(s, strStar)) // 统计星号的数量
|
|
||||||
return n
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// countSections 计算路径中斜杠('/')的数量, 即路径段的数量.
|
// countSections 计算路径中斜杠('/')的数量, 即路径段的数量.
|
||||||
func countSections(path string) uint16 {
|
func countSections(path string) uint16 {
|
||||||
s := StringToBytes(path) // 将路径字符串转换为字节切片
|
return uint16(strings.Count(path, "/"))
|
||||||
return uint16(bytes.Count(s, strSlash)) // 统计斜杠的数量
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeType 定义了节点的类型.
|
// nodeType 定义了节点的类型.
|
||||||
|
|
@ -419,7 +409,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
n.addChild(child) // 添加子节点
|
n.addChild(child) // 添加子节点
|
||||||
n.indices = string('/') // 索引设置为 '/'
|
n.indices = "/" // 索引设置为 '/'
|
||||||
n = child // 移动到新创建的 catchAll 节点
|
n = child // 移动到新创建的 catchAll 节点
|
||||||
n.priority++ // 增加优先级
|
n.priority++ // 增加优先级
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue