touka/engine.go
2025-05-28 18:25:28 +08:00

679 lines
24 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package touka
import (
"context"
"reflect"
"runtime"
"strings"
"net/http"
"path"
"sync"
"github.com/WJQSERVER-STUDIO/httpc"
)
// Last 返回链中的最后一个处理函数。
// 如果链为空,则返回 nil。
func (c HandlersChain) Last() HandlerFunc {
if len(c) > 0 {
return c[len(c)-1]
}
return nil
}
// Engine 是 Touka 框架的核心,负责路由注册、中间件管理和请求分发。
// 它实现了 http.Handler 接口,可以直接用于 http.ListenAndServe。
type Engine struct {
methodTrees methodTrees // 存储所有HTTP方法的路由树
pool sync.Pool // Context Pool 用于复用 Context 对象,提高性能。
globalHandlers HandlersChain // 全局中间件,应用于所有路由。
maxParams uint16 // 记录所有路由中最大的参数数量,用于优化 Params 切片的分配。
// 可配置项,用于控制框架行为,参考 Gin
RedirectTrailingSlash bool // 是否自动重定向带尾部斜杠的路径到不带尾部斜杠的路径 (e.g. /foo/ -> /foo)
RedirectFixedPath bool // 是否自动修复路径中的大小写错误 (e.g. /Foo -> /foo)
HandleMethodNotAllowed bool // 是否启用 MethodNotAllowed 处理器
ForwardByClientIP bool // 是否信任 X-Forwarded-For 等头部获取客户端 IP
RemoteIPHeaders []string // 用于获取客户端 IP 的头部列表,例如 {"X-Forwarded-For", "X-Real-IP"}
// TrustedProxies []string // 可信代理 IP 列表,用于判断是否使用 X-Forwarded-For 等头部 (预留接口)
HTTPClient *httpc.Client // 用于在此上下文中执行出站 HTTP 请求。
HTMLRender interface{} // 用于 HTML 模板渲染,可以设置为 *template.Template 或自定义渲染器接口
routesInfo []RouteInfo // 存储所有注册的路由信息
errorHandle ErrorHandle // 错误处理
noRoute HandlerFunc
unMatchFS UnMatchFS // 未匹配下的处理
serverProtocols *http.Protocols //服务协议
Protocols ProtocolsConfig //协议版本配置
useDefaultProtocols bool //是否使用默认协议
}
type ErrorHandle struct {
useDefault bool
handler ErrorHandler
}
type ErrorHandler func(c *Context, code int)
// defaultErrorHandle 默认错误处理
func defaultErrorHandle(c *Context, code int) { // 检查客户端是否已断开连接
select {
case <-c.Request.Context().Done():
return
default:
// 输出json 状态码与状态码对应描述
c.JSON(code, H{
"code": code,
"message": http.StatusText(code),
})
c.Writer.Flush()
c.Abort()
return
}
}
type UnMatchFS struct {
FSForUnmatched http.FileSystem
ServeUnmatchedAsFS bool
}
// ProtocolsConfig 协议版本配置结构体
type ProtocolsConfig struct {
Http1 bool // 是否启用 HTTP/1.1
Http2 bool // 是否启用 HTTP/2
Http2_Cleartext bool // 是否启用 H2C
}
// New 创建并返回一个 Engine 实例。
func New() *Engine {
engine := &Engine{
methodTrees: make(methodTrees, 0, 9), // 常见的HTTP方法有9个 (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT, TRACE)
RedirectTrailingSlash: true,
RedirectFixedPath: true,
HandleMethodNotAllowed: true,
ForwardByClientIP: true,
HTTPClient: httpc.New(), // 提供一个默认的 HTTPClient
routesInfo: make([]RouteInfo, 0), // 初始化路由信息切片
globalHandlers: make(HandlersChain, 0),
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
errorHandle: ErrorHandle{
useDefault: true,
handler: defaultErrorHandle,
},
unMatchFS: UnMatchFS{
ServeUnmatchedAsFS: false,
},
}
//engine.SetProtocols(GetDefaultProtocolsConfig())
engine.SetDefaultProtocols()
// 初始化 Context Pool为每个新 Context 实例提供一个构造函数
engine.pool.New = func() interface{} {
return &Context{
Writer: newResponseWriter(nil), // 初始时可以传入nil在ServeHTTP中会重新设置实际的 http.ResponseWriter
Params: make(Params, 0, engine.maxParams), // 预分配 Params 切片以减少内存分配
Keys: make(map[string]interface{}),
Errors: make([]error, 0),
ctx: context.Background(), // 初始上下文,后续会被请求的 Context 覆盖
HTTPClient: engine.HTTPClient,
engine: engine, // Context 持有 Engine 引用,方便访问 Engine 的配置
}
}
return engine
}
// 生成一个携带默认中间件的Engine
func Default() *Engine {
engine := New()
engine.Use(Recovery())
return engine
}
// === 外部操作方法 ===
// 设置自定义错误处理
func (engine *Engine) SetErrorHandler(handler ErrorHandler) {
engine.errorHandle.useDefault = false
engine.errorHandle.handler = handler
}
// 获取一个默认错误处理handle
func (engine *Engine) GetDefaultErrHandler() ErrorHandler {
return defaultErrorHandle
}
// 传入并配置unMatchFS
func (engine *Engine) SetUnMatchFS(fs http.FileSystem) {
if fs != nil {
engine.unMatchFS.FSForUnmatched = fs
engine.unMatchFS.ServeUnmatchedAsFS = true
} else {
engine.unMatchFS.ServeUnmatchedAsFS = false
}
}
// 获取默认Protocol配置
func GetDefaultProtocolsConfig() *ProtocolsConfig {
return &ProtocolsConfig{
Http1: true,
Http2: false,
Http2_Cleartext: false,
}
}
// 设置默认Protocols
func (engine *Engine) SetDefaultProtocols() {
engine.useDefaultProtocols = true
engine.SetProtocols(GetDefaultProtocolsConfig())
}
// 设置Protocol
func (engine *Engine) SetProtocols(config *ProtocolsConfig) {
engine.Protocols = *config
engine.serverProtocols = &http.Protocols{} // 初始化指针
func() {
var p http.Protocols
p.SetHTTP1(config.Http1)
p.SetHTTP2(config.Http2)
p.SetUnencryptedHTTP2(config.Http2_Cleartext)
*engine.serverProtocols = p // 将值赋给指针指向的结构体
}()
engine.useDefaultProtocols = false
}
// 配置Req IP来源 Headers
func (engine *Engine) SetRemoteIPHeaders(headers []string) {
engine.RemoteIPHeaders = headers
}
// SetForwardByClientIP 设置是否信任 X-Forwarded-For 等头部获取客户端 IP。
func (engine *Engine) SetForwardByClientIP(enable bool) {
engine.ForwardByClientIP = enable
}
// SetHTTPClient 设置 Engine 使用的 httpc.Client。
func (engine *Engine) SetHTTPClient(client *httpc.Client) {
if client != nil {
engine.HTTPClient = client
}
}
// registerMethodTree 内部方法,用于获取或注册对应 HTTP 方法的路由树根节点。
// 如果该方法没有对应的树,则创建一个新的树。
func (engine *Engine) registerMethodTree(method string) *node {
for _, tree := range engine.methodTrees {
if tree.method == method {
return tree.root
}
}
// 如果没有找到,则创建一个新的方法树并添加到列表中
root := &node{
nType: root, // 根节点类型
fullPath: "/", // 根路径
}
engine.methodTrees = append(engine.methodTrees, methodTree{method: method, root: root})
return root
}
// addRoute 将一个路由及处理函数链添加到路由树中。
// 这是框架内部路由注册的核心逻辑。
// groupPath 用于记录路由所属的分组路径。
func (engine *Engine) addRoute(method, absolutePath, groupPath string, handlers HandlersChain) { // relativePath 更名为 absolutePath
if absolutePath == "" {
panic("absolute path must not be empty")
}
if len(handlers) == 0 {
panic("handlers must not be empty")
}
// 检查并更新 maxParams使用 absolutePath
if n := countParams(absolutePath); n > engine.maxParams {
engine.maxParams = n
}
root := engine.registerMethodTree(method)
root.addRoute(absolutePath, handlers) // 调用 node 的 addRoute 方法将路由添加到树中
handlerName := "unknown"
if len(handlers) > 0 {
handlerName = getHandlerName(handlers.Last())
}
engine.routesInfo = append(engine.routesInfo, RouteInfo{
Method: method,
Path: absolutePath, // 使用完整的绝对路径
Handler: handlerName,
Group: groupPath,
})
}
// getHandlerName 辅助函数,用于获取 HandlerFunc 的名称。
// 注意:这只是一个简单的反射实现,对于匿名函数或闭包,可能返回不可读的名称。
func getHandlerName(h HandlerFunc) string {
//return reflect.TypeOf(h).Name() // 对于具名函数,返回函数名。对于匿名函数,可能返回空字符串或类似 func123 这样的名称。
// 更精确的获取函数名需要 import "runtime"
// pc := reflect.ValueOf(h).Pointer()
// f := runtime.FuncForPC(pc)
// return f.Name()
if h == nil {
return "nil_handler"
}
pc := reflect.ValueOf(h).Pointer()
f := runtime.FuncForPC(pc)
return f.Name() // 返回例如 "main.HomeHandler" 或 "touka.Logger"
}
// ServeHTTP 实现了 http.Handler 接口,是 Engine 处理所有 HTTP 请求的入口。
// 每个传入的 HTTP 请求都会调用此方法。
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 从 Context Pool 中获取一个 Context 对象进行复用
c := engine.pool.Get().(*Context)
c.reset(w, req) // 重置 Context 对象的状态以适应当前请求
// 执行请求处理
engine.handleRequest(c)
// 将 Context 对象放回 Context Pool以供下次复用
engine.pool.Put(c)
}
// handleRequest 负责根据请求查找路由并执行相应的处理函数链。
// 这是路由查找和执行的核心逻辑。
func (engine *Engine) handleRequest(c *Context) {
httpMethod := c.Request.Method
requestPath := c.Request.URL.Path
// 查找对应的路由树的根节点
rootNode := engine.methodTrees.get(httpMethod) // 这里获取到的 rootNode 已经是 *node 类型
if rootNode != nil {
// 查找匹配的节点和处理函数
// 这里传递 &c.Params 而不是重新创建,以利用 Context 中预分配的容量
// skippedNodes 内部使用,因此无需从外部传入已分配的 slice
var skippedNodes []skippedNode // 用于回溯的跳过节点
// 直接在 rootNode 上调用 getValue 方法
value := rootNode.getValue(requestPath, &c.Params, &skippedNodes, true) // unescape=true 对路径参数进行 URL 解码
if value.handlers != nil {
//c.handlers = engine.combineHandlers(engine.globalHandlers, value.handlers) // 组合全局中间件和路由处理函数
c.handlers = value.handlers
c.Next() // 执行处理函数链
c.Writer.Flush() // 确保所有缓冲的响应数据被发送
return
}
// 如果没有找到处理函数,检查是否需要重定向(尾部斜杠或大小写修复)
if httpMethod != http.MethodConnect && requestPath != "/" { // CONNECT 方法和根路径不进行重定向
if value.tsr && engine.RedirectTrailingSlash {
// 尾部斜杠重定向:/foo/ -> /foo 或 /foo -> /foo/
redirectPath := requestPath
if len(requestPath) > 0 && requestPath[len(requestPath)-1] == '/' {
redirectPath = requestPath[:len(requestPath)-1]
} else {
redirectPath = requestPath + "/"
}
c.Redirect(http.StatusMovedPermanently, redirectPath) // 301 永久重定向
return
}
// 尝试不区分大小写的查找
// 直接在 rootNode 上调用 findCaseInsensitivePath 方法
ciPath, found := rootNode.findCaseInsensitivePath(requestPath, engine.RedirectTrailingSlash)
if found && engine.RedirectFixedPath {
c.Redirect(http.StatusMovedPermanently, BytesToString(ciPath)) // 301 永久重定向到修正后的路径
return
}
}
}
/*
// 如果没有找到路由,且启用了 MethodNotAllowed 处理
if engine.HandleMethodNotAllowed {
// 是否是OPTIONS方式
if httpMethod == http.MethodOptions {
// 如果是 OPTIONS 请求,尝试查找所有允许的方法
allowedMethods := []string{}
for _, treeIter := range engine.methodTrees {
var tempSkippedNodes []skippedNode
// 注意这里 treeIter.root 才是正确的,因为 treeIter 是 methodTree 类型
value := treeIter.root.getValue(requestPath, nil, &tempSkippedNodes, false)
if value.handlers != nil {
allowedMethods = append(allowedMethods, treeIter.method)
}
}
if len(allowedMethods) > 0 {
// 如果找到了允许的方法,返回 200 OK 并设置 Allow 头部
c.Writer.Header().Set("Allow", strings.Join(allowedMethods, ", "))
c.Status(http.StatusOK)
return
}
}
// 尝试遍历所有方法树,看是否有其他方法可以匹配当前路径
for _, treeIter := range engine.methodTrees {
if treeIter.method == httpMethod { // 已经处理过当前方法,跳过
continue
}
var tempSkippedNodes []skippedNode // 用于临时查找,不影响主 Context
// 注意这里 treeIter.root 才是正确的,因为 treeIter 是 methodTree 类型
value := treeIter.root.getValue(requestPath, nil, &tempSkippedNodes, false) // 只查找是否存在,不需要参数
if value.handlers != nil {
// 使用定义的ErrorHandle处理
engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
return
}
}
}
// 是否开启了UnMatchFS
if engine.unMatchFS.ServeUnmatchedAsFS {
// 若不是GET HEAD OPTIONS则返回405
if c.Request.Method == http.MethodGet || c.Request.Method == http.MethodHead {
// 使用 http.FileServer 处理未匹配的请求
fileServer := http.FileServer(engine.unMatchFS.FSForUnmatched)
//ecw := newErrorCapturingResponseWriter(c, c.engine.errorHandle.handler)
ecw := AcquireErrorCapturingResponseWriter(c, c.engine.errorHandle.handler)
defer ReleaseErrorCapturingResponseWriter(ecw)
fileServer.ServeHTTP(ecw, c.Request)
ecw.processAfterFileServer()
return
} else {
log.Printf("Not Allowed Method: %s", c.Request.Method)
// 若为OPTIONS
if c.Request.Method == http.MethodOptions {
//返回allow get
c.Writer.Header().Set("Allow", "GET")
c.Status(http.StatusOK)
c.Abort()
return
} else {
engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
return
}
}
} else {
engine.errorHandle.handler(c, http.StatusNotFound)
return
}
*/
// 构建处理链
// 组合全局中间件和路由处理函数
handlers := engine.globalHandlers
// 如果启用了 MethodNotAllowed 处理,并且没有找到精确匹配的路由
// 则在全局中间件之后添加 MethodNotAllowed 处理器
if engine.HandleMethodNotAllowed {
handlers = append(handlers, MethodNotAllowed())
}
// 如果启用了 UnMatchFS 处理,并且没有找到精确匹配的路由和 MethodNotAllowed
// 则在处理链的最后添加 UnMatchFS 处理器
if engine.unMatchFS.ServeUnmatchedAsFS {
handlers = append(handlers, unMatchFSHandle())
}
// 如果用户设置了 NoRoute 处理器且没有匹配到任何路由、MethodNotAllowed 或 UnMatchFS
// 则在处理链的最后添加 NoRoute 处理器
if engine.noRoute != nil {
handlers = append(handlers, engine.noRoute)
}
handlers = append(handlers, NotFound())
c.handlers = handlers
c.Next() // 执行处理函数链
c.Writer.Flush() // 确保所有缓冲的响应数据被发送
}
// UnMatchFS HandleFunc
func unMatchFSHandle() HandlerFunc {
return func(c *Context) {
engine := c.engine
if c.Request.Method == http.MethodGet || c.Request.Method == http.MethodHead {
// 使用 http.FileServer 处理未匹配的请求
fileServer := http.FileServer(engine.unMatchFS.FSForUnmatched)
//ecw := newErrorCapturingResponseWriter(c, c.engine.errorHandle.handler)
ecw := AcquireErrorCapturingResponseWriter(c, c.engine.errorHandle.handler)
defer ReleaseErrorCapturingResponseWriter(ecw)
fileServer.ServeHTTP(ecw, c.Request)
ecw.processAfterFileServer()
return
} else {
if engine.noRoute == nil {
// 若为OPTIONS
if c.Request.Method == http.MethodOptions {
//返回allow get
c.Writer.Header().Set("Allow", "GET")
c.Status(http.StatusOK)
c.Abort()
return
} else {
engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
return
}
} else {
c.Next()
}
}
}
}
// 405中间件
func MethodNotAllowed() HandlerFunc {
return func(c *Context) {
httpMethod := c.Request.Method
requestPath := c.Request.URL.Path
engine := c.engine
// 是否是OPTIONS方式
if httpMethod == http.MethodOptions {
// 如果是 OPTIONS 请求,尝试查找所有允许的方法
allowedMethods := []string{}
for _, treeIter := range engine.methodTrees {
var tempSkippedNodes []skippedNode
// 注意这里 treeIter.root 才是正确的,因为 treeIter 是 methodTree 类型
value := treeIter.root.getValue(requestPath, nil, &tempSkippedNodes, false)
if value.handlers != nil {
allowedMethods = append(allowedMethods, treeIter.method)
}
}
if len(allowedMethods) > 0 {
// 如果找到了允许的方法,返回 200 OK 并设置 Allow 头部
c.Writer.Header().Set("Allow", strings.Join(allowedMethods, ", "))
c.Status(http.StatusOK)
return
}
}
// 尝试遍历所有方法树,看是否有其他方法可以匹配当前路径
for _, treeIter := range engine.methodTrees {
if treeIter.method == httpMethod { // 已经处理过当前方法,跳过
continue
}
var tempSkippedNodes []skippedNode // 用于临时查找,不影响主 Context
// 注意这里 treeIter.root 才是正确的,因为 treeIter 是 methodTree 类型
value := treeIter.root.getValue(requestPath, nil, &tempSkippedNodes, false) // 只查找是否存在,不需要参数
if value.handlers != nil {
// 使用定义的ErrorHandle处理
engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
return
}
}
}
}
// 404最后处理
func NotFound() HandlerFunc {
return func(c *Context) {
engine := c.engine
engine.errorHandle.handler(c, http.StatusNotFound)
return
}
}
// 传入并设置NoRoute (这不是最后一个处理, 你仍可以next到默认的404处理)
func (Engine *Engine) NoRoute(handler HandlerFunc) {
Engine.noRoute = handler
}
// combineHandlers 组合多个处理函数链为一个。
// 这是构建完整处理链(全局中间件 + 组中间件 + 路由处理函数)的关键。
func (engine *Engine) combineHandlers(h1 HandlersChain, h2 HandlersChain) HandlersChain {
finalSize := len(h1) + len(h2)
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, h1)
copy(mergedHandlers[len(h1):], h2)
return mergedHandlers
}
// Use 将全局中间件添加到 Engine。
// 这些中间件将应用于所有注册的路由。
func (engine *Engine) Use(middleware ...HandlerFunc) IRouter {
engine.globalHandlers = append(engine.globalHandlers, middleware...)
return engine
}
// Handle 注册通用 HTTP 方法的路由。
// 这是所有具体 HTTP 方法注册的基础方法。
func (engine *Engine) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) {
absolutePath := path.Join("/", relativePath) // 修正:统一使用 path.Join 进行路径拼接
// 修正:将全局中间件与此路由的处理函数合并
fullHandlers := engine.combineHandlers(engine.globalHandlers, handlers)
engine.addRoute(httpMethod, absolutePath, "/", fullHandlers)
}
// GET 注册 GET 方法的路由。
func (engine *Engine) GET(relativePath string, handlers ...HandlerFunc) {
engine.Handle(http.MethodGet, relativePath, handlers...)
}
// POST 注册 POST 方法的路由。
func (engine *Engine) POST(relativePath string, handlers ...HandlerFunc) {
engine.Handle(http.MethodPost, relativePath, handlers...)
}
// PUT 注册 PUT 方法的路由。
func (engine *Engine) PUT(relativePath string, handlers ...HandlerFunc) {
engine.Handle(http.MethodPut, relativePath, handlers...)
}
// DELETE 注册 DELETE 方法的路由。
func (engine *Engine) DELETE(relativePath string, handlers ...HandlerFunc) {
engine.Handle(http.MethodDelete, relativePath, handlers...)
}
// PATCH 注册 PATCH 方法的路由。
func (engine *Engine) PATCH(relativePath string, handlers ...HandlerFunc) {
engine.Handle(http.MethodPatch, relativePath, handlers...)
}
// HEAD 注册 HEAD 方法的路由。
func (engine *Engine) HEAD(relativePath string, handlers ...HandlerFunc) {
engine.Handle(http.MethodHead, relativePath, handlers...)
}
// OPTIONS 注册 OPTIONS 方法的路由。
func (engine *Engine) OPTIONS(relativePath string, handlers ...HandlerFunc) {
engine.Handle(http.MethodOptions, relativePath, handlers...)
}
// ANY 注册所有常见 HTTP 方法的路由。
func (engine *Engine) ANY(relativePath string, handlers ...HandlerFunc) {
engine.Handle(http.MethodGet, relativePath, handlers...)
engine.Handle(http.MethodPost, relativePath, handlers...)
engine.Handle(http.MethodPut, relativePath, handlers...)
engine.Handle(http.MethodDelete, relativePath, handlers...)
engine.Handle(http.MethodPatch, relativePath, handlers...)
engine.Handle(http.MethodHead, relativePath, handlers...)
engine.Handle(http.MethodOptions, relativePath, handlers...)
}
// GetRouterInfo 返回所有已注册的路由信息。
func (engine *Engine) GetRouterInfo() []RouteInfo {
return engine.routesInfo
}
// Group 创建一个新的路由组。
// 路由组允许将具有相同前缀路径和/或共享中间件的路由组织在一起。
func (engine *Engine) Group(relativePath string, handlers ...HandlerFunc) IRouter {
return &RouterGroup{
Handlers: engine.combineHandlers(engine.globalHandlers, handlers), // 继承全局中间件
basePath: path.Join("/", relativePath),
engine: engine, // 指向 Engine 实例
}
}
// RouterGroup 表示一个路由分组,可以添加组特定的中间件和路由。
// 它也实现了 IRouter 接口,允许嵌套分组。
type RouterGroup struct {
Handlers HandlersChain // 组中间件,仅应用于当前组及其子组的路由
basePath string // 组路径前缀
engine *Engine // 指向 Engine 实例,用于注册路由到全局路由树
}
// Use 将中间件应用于当前路由组。
// 这些中间件将应用于当前组及其子组的所有路由。
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRouter {
group.Handlers = append(group.Handlers, middleware...)
return group
}
// Handle 注册通用 HTTP 方法的路由到当前组。
// 路径是相对于当前组的 basePath。
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) {
absolutePath := path.Join(group.basePath, relativePath)
fullHandlers := group.engine.combineHandlers(group.Handlers, handlers)
group.engine.addRoute(httpMethod, absolutePath, group.basePath, fullHandlers)
}
// GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, ANY 方法与 Engine 类似,只是通过 Group 的 Handle 方法注册。
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) {
group.Handle(http.MethodGet, relativePath, handlers...)
}
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) {
group.Handle(http.MethodPost, relativePath, handlers...)
}
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) {
group.Handle(http.MethodPut, relativePath, handlers...)
}
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) {
group.Handle(http.MethodDelete, relativePath, handlers...)
}
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) {
group.Handle(http.MethodPatch, relativePath, handlers...)
}
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) {
group.Handle(http.MethodHead, relativePath, handlers...)
}
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) {
group.Handle(http.MethodOptions, relativePath, handlers...)
}
func (group *RouterGroup) ANY(relativePath string, handlers ...HandlerFunc) {
group.Handle(http.MethodGet, relativePath, handlers...)
group.Handle(http.MethodPost, relativePath, handlers...)
group.Handle(http.MethodPut, relativePath, handlers...)
group.Handle(http.MethodDelete, relativePath, handlers...)
group.Handle(http.MethodPatch, relativePath, handlers...)
group.Handle(http.MethodHead, relativePath, handlers...)
group.Handle(http.MethodOptions, relativePath, handlers...)
}
// Group 为当前组创建一个新的子组。
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) IRouter {
return &RouterGroup{
Handlers: group.engine.combineHandlers(group.Handlers, handlers),
basePath: path.Join(group.basePath, relativePath),
engine: group.engine, // 指向 Engine 实例
}
}