mirror of
https://github.com/infinite-iroha/touka.git
synced 2026-02-03 17:01:11 +08:00
This commit introduces several significant enhancements and completes key features:
1. **Enhanced HTML Rendering:**
- Defined a new `touka.HTMLRender` interface in `engine.go` for pluggable HTML rendering.
- `Engine.HTMLRender` now uses this interface type.
- `Context.HTML` is updated to use the configured `HTMLRender` instance, with improved error handling if no renderer is set or if rendering fails. The previous `fmt.Sprintf` fallback has been removed.
- Added `DefaultHTMLRenderer` (using `html/template`) and helper methods `Engine.LoadHTMLGlob()` and `Engine.SetHTMLTemplate()` for easy setup.
2. **Comprehensive Data Binding:**
- **`Context.ShouldBindForm`**: Implemented using `gorilla/schema` to bind `x-www-form-urlencoded` and `multipart/form-data` (fields) from the request body.
- **`Context.ShouldBindQuery`**: Implemented using `gorilla/schema` to bind URL query parameters.
- **`Context.ShouldBindXML`**: Implemented using `encoding/xml` to bind XML data from the request body. This method also respects the `Engine.MaxRequestBodySize` limit.
- **`Context.ShouldBind`**: Implemented as a generic binder that inspects the `Content-Type` header and dispatches to the appropriate specific binder (JSON, XML, Form). It handles missing or unsupported content types.
3. **Comprehensive Unit Tests for `context.go`:**
- Massively expanded `context_test.go` to provide extensive test coverage for nearly all methods in `context.go`.
- This includes detailed tests for all new data binding methods, the updated HTML rendering logic, state management (`Keys`), request/response utilities, error handling, header and cookie manipulation, streaming, Go context integration, and logging.
- Mocks for `HTMLRender`, `ErrorHandler`, and `reco.Logger` were used to facilitate thorough testing.
These changes significantly improve the framework's feature set, robustness, and maintainability due to increased test coverage.
1079 lines
37 KiB
Go
1079 lines
37 KiB
Go
package touka
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"log"
|
||
"reflect"
|
||
"runtime"
|
||
"strings"
|
||
|
||
"html/template"
|
||
"io"
|
||
"net/http"
|
||
"path"
|
||
"sync"
|
||
|
||
"github.com/WJQSERVER-STUDIO/httpc"
|
||
"github.com/fenthope/reco"
|
||
)
|
||
|
||
// HTMLRender defines the interface for HTML rendering.
|
||
type HTMLRender interface {
|
||
Render(writer io.Writer, name string, data interface{}, c *Context) error
|
||
}
|
||
|
||
// DefaultHTMLRenderer is a basic implementation of HTMLRender using html/template.
|
||
type DefaultHTMLRenderer struct {
|
||
Templates *template.Template
|
||
}
|
||
|
||
// Render executes the template and writes to the writer.
|
||
func (r *DefaultHTMLRenderer) Render(writer io.Writer, name string, data interface{}, c *Context) error {
|
||
return r.Templates.ExecuteTemplate(writer, name, data)
|
||
}
|
||
|
||
// 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 请求
|
||
|
||
LogReco *reco.Logger
|
||
|
||
HTMLRender HTMLRender // 用于 HTML 模板渲染
|
||
|
||
routesInfo []RouteInfo // 存储所有注册的路由信息
|
||
|
||
errorHandle ErrorHandle // 错误处理
|
||
|
||
noRoute HandlerFunc // NoRoute 处理器
|
||
noRoutes HandlersChain // NoRoutes 处理器链 (如果 noRoute 未设置,则使用此链)
|
||
|
||
unMatchFS UnMatchFS // 未匹配下的处理
|
||
unMatchFileServer http.Handler // 处理handle
|
||
|
||
serverProtocols *http.Protocols //服务协议
|
||
Protocols ProtocolsConfig //协议版本配置
|
||
useDefaultProtocols bool //是否使用默认协议
|
||
|
||
// ServerConfigurator 允许在服务器启动前对其进行自定义配置
|
||
// 例如,设置 ReadTimeout, WriteTimeout 等
|
||
ServerConfigurator func(*http.Server)
|
||
|
||
// TLSServerConfigurator 允许在 HTTPS 服务器启动前进行自定义配置
|
||
// 如果设置了此回调,它将优先于 ServerConfigurator 被用于 HTTPS 服务器
|
||
// 如果未设置,HTTPS 服务器将回退使用 ServerConfigurator (如果已设置)
|
||
TLSServerConfigurator func(*http.Server)
|
||
|
||
MaxRequestBodySize int64 // 限制读取Body的最大字节数
|
||
}
|
||
|
||
type ErrorHandle struct {
|
||
useDefault bool
|
||
handler ErrorHandler
|
||
}
|
||
|
||
type ErrorHandler func(c *Context, code int, err error)
|
||
|
||
// defaultErrorHandle 默认错误处理
|
||
func defaultErrorHandle(c *Context, code int, err error) { // 检查客户端是否已断开连接
|
||
select {
|
||
case <-c.Request.Context().Done():
|
||
// 客户端断开连接,无需进一步处理
|
||
return
|
||
default:
|
||
// 检查响应是否已经写入
|
||
if c.Writer.Written() {
|
||
return
|
||
}
|
||
|
||
// 收集错误信息用于日志记录
|
||
primaryErrStr := "none"
|
||
if err != nil {
|
||
primaryErrStr = err.Error()
|
||
}
|
||
|
||
var collectedErrors []string
|
||
for _, e := range c.GetErrors() {
|
||
collectedErrors = append(collectedErrors, e.Error())
|
||
}
|
||
collectedErrorsStr := strings.Join(collectedErrors, "; ")
|
||
if collectedErrorsStr == "" {
|
||
collectedErrorsStr = "none"
|
||
}
|
||
|
||
// 记录错误日志
|
||
logMessage := fmt.Sprintf("[Touka ErrorHandler] Request: [%s] %s | Primary Error: %s | Collected Errors: %s",
|
||
c.Request.Method, c.Request.URL.Path, primaryErrStr, collectedErrorsStr)
|
||
|
||
if c.engine != nil && c.engine.LogReco != nil {
|
||
c.engine.LogReco.Error(logMessage)
|
||
} else {
|
||
log.Println(logMessage) // Fallback to standard logger
|
||
}
|
||
|
||
// 输出json 状态码与状态码对应描述
|
||
var errMsg string
|
||
if err != nil {
|
||
errMsg = err.Error()
|
||
}
|
||
c.JSON(code, H{
|
||
"code": code,
|
||
"message": http.StatusText(code),
|
||
"error": errMsg,
|
||
})
|
||
c.Writer.Flush()
|
||
c.Abort()
|
||
return
|
||
}
|
||
}
|
||
|
||
// 默认errorhandle包装 避免竞争意外问题, 保证稳定性
|
||
func defaultErrorWarp(handler ErrorHandler) ErrorHandler {
|
||
return func(c *Context, code int, err error) {
|
||
select {
|
||
case <-c.Request.Context().Done():
|
||
return
|
||
default:
|
||
if c.Writer.Written() {
|
||
log.Printf("errpage: response already started for status %d, skipping error page rendering, err: %v", code, err)
|
||
return
|
||
}
|
||
}
|
||
handler(c, code, err)
|
||
}
|
||
}
|
||
|
||
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,
|
||
},
|
||
noRoute: nil,
|
||
noRoutes: make(HandlersChain, 0),
|
||
ServerConfigurator: nil,
|
||
TLSServerConfigurator: nil,
|
||
MaxRequestBodySize: 10 * 1024 * 1024, // 默认 10MB
|
||
}
|
||
//engine.SetProtocols(GetDefaultProtocolsConfig())
|
||
engine.SetDefaultProtocols()
|
||
engine.SetLoggerCfg(defaultLogRecoConfig)
|
||
// 初始化 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
|
||
}
|
||
|
||
// === 外部操作方法 ===
|
||
|
||
// LoadHTMLGlob loads HTML templates from a glob pattern and sets them as the HTML renderer.
|
||
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||
tpl := template.Must(template.ParseGlob(pattern))
|
||
engine.HTMLRender = &DefaultHTMLRenderer{Templates: tpl}
|
||
}
|
||
|
||
// SetHTMLTemplate sets a custom *template.Template as the HTML renderer.
|
||
// This will wrap the *template.Template with the DefaultHTMLRenderer.
|
||
func (engine *Engine) SetHTMLTemplate(tpl *template.Template) {
|
||
engine.HTMLRender = &DefaultHTMLRenderer{Templates: tpl}
|
||
}
|
||
|
||
// SetMaxRequestBodySize 设置读取Body的最大字节数
|
||
func (engine *Engine) SetMaxRequestBodySize(size int64) {
|
||
engine.MaxRequestBodySize = size
|
||
}
|
||
|
||
// SetServerConfigurator 设置一个函数,该函数将在任何 HTTP 或 HTTPS 服务器
|
||
// (通过 RunShutdown, RunTLS, RunTLSRedir) 启动前被调用,
|
||
// 允许用户对底层的 *http.Server 实例进行自定义配置
|
||
func (engine *Engine) SetServerConfigurator(fn func(*http.Server)) {
|
||
engine.ServerConfigurator = fn
|
||
}
|
||
|
||
// SetTLSServerConfigurator 设置一个函数,该函数将专门用于配置 HTTPS 服务器
|
||
// 如果设置了此函数,它将覆盖通用的 ServerConfigurator
|
||
func (engine *Engine) SetTLSServerConfigurator(fn func(*http.Server)) {
|
||
engine.TLSServerConfigurator = fn
|
||
}
|
||
|
||
// SetLogger传入实例
|
||
func (engine *Engine) SetLogger(logger *reco.Logger) {
|
||
engine.LogReco = logger
|
||
}
|
||
|
||
// 配置日志LoggerCfg
|
||
func (engine *Engine) SetLoggerCfg(logcfg reco.Config) {
|
||
engine.LogReco = NewLogger(logcfg)
|
||
}
|
||
|
||
// 设置自定义错误处理
|
||
func (engine *Engine) SetErrorHandler(handler ErrorHandler) {
|
||
engine.errorHandle.useDefault = false
|
||
engine.errorHandle.handler = defaultErrorWarp(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
|
||
engine.unMatchFileServer = http.FileServer(fs)
|
||
} else {
|
||
engine.unMatchFS.ServeUnmatchedAsFS = false
|
||
engine.unMatchFileServer = nil
|
||
}
|
||
}
|
||
|
||
// 获取默认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
|
||
}
|
||
}
|
||
}
|
||
|
||
// 构建处理链
|
||
// 组合全局中间件和路由处理函数
|
||
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)
|
||
} else if len(engine.noRoutes) > 0 {
|
||
handlers = append(handlers, engine.noRoutes...)
|
||
}
|
||
|
||
handlers = append(handlers, NotFound())
|
||
|
||
c.handlers = handlers
|
||
c.Next() // 执行处理函数链
|
||
//c.Writer.Flush() // 确保所有缓冲的响应数据被发送
|
||
}
|
||
|
||
// UnMatchFS HandleFunc
|
||
func unMatchFSHandle() HandlerFunc {
|
||
return func(c *Context) {
|
||
engine := c.engine
|
||
// 确保 engine.unMatchFileServer 存在
|
||
if !engine.unMatchFS.ServeUnmatchedAsFS || engine.unMatchFileServer == nil {
|
||
c.Next() // 如果未配置或 FileSystem 为 nil,则继续处理链
|
||
return
|
||
}
|
||
if c.Request.Method == http.MethodGet || c.Request.Method == http.MethodHead {
|
||
// 使用 http.FileServer 处理未匹配的请求
|
||
ecw := AcquireErrorCapturingResponseWriter(c)
|
||
defer ReleaseErrorCapturingResponseWriter(ecw)
|
||
c.engine.unMatchFileServer.ServeHTTP(ecw, c.Request)
|
||
ecw.processAfterFileServer()
|
||
c.Abort()
|
||
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, errors.New("method not allowed"))
|
||
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, errors.New("method not allowed"))
|
||
return
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 404最后处理
|
||
func NotFound() HandlerFunc {
|
||
return func(c *Context) {
|
||
engine := c.engine
|
||
engine.errorHandle.handler(c, http.StatusNotFound, errors.New("not found"))
|
||
return
|
||
}
|
||
}
|
||
|
||
// 传入并设置NoRoute (这不是最后一个处理, 你仍可以next到默认的404处理)
|
||
func (Engine *Engine) NoRoute(handler HandlerFunc) {
|
||
Engine.noRoute = handler
|
||
Engine.noRoutes = nil
|
||
}
|
||
|
||
// 传入并设置NoRoutes (这不是最后一个处理, 你仍可以next到默认的404处理)
|
||
func (Engine *Engine) NoRoutes(handlerFuncs ...HandlerFunc) {
|
||
Engine.noRoute = nil
|
||
Engine.noRoutes = handlerFuncs
|
||
}
|
||
|
||
// 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 进行路径拼接
|
||
absolutePath := resolveRoutePath("/", relativePath)
|
||
// 修正:将全局中间件与此路由的处理函数合并
|
||
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: resolveRoutePath("/", 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 := resolveRoutePath(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: resolveRoutePath(group.basePath, relativePath),
|
||
engine: group.engine, // 指向 Engine 实例
|
||
}
|
||
}
|
||
|
||
// == 其他操作方式 ===
|
||
|
||
// StaticDir 传入一个文件夹路径, 使用FileServer进行处理
|
||
// r.StaticDir("/test/*filepath", "/var/www/test")
|
||
func (engine *Engine) StaticDir(relativePath, rootPath string) {
|
||
// 清理路径
|
||
relativePath = path.Clean(relativePath)
|
||
rootPath = path.Clean(rootPath)
|
||
|
||
// 确保相对路径以 '/' 结尾,以便 FileServer 正确处理子路径
|
||
if !strings.HasSuffix(relativePath, "/") {
|
||
relativePath += "/"
|
||
}
|
||
|
||
// 创建一个文件系统处理器
|
||
fileServer := http.FileServer(http.Dir(rootPath))
|
||
|
||
// 注册一个捕获所有路径的路由,使用自定义处理器
|
||
// 注意:这里使用 ANY 方法,但 FileServer 通常只处理 GET 和 HEAD
|
||
// 我们可以通过在处理函数内部检查方法来限制
|
||
engine.ANY(relativePath+"*filepath", func(c *Context) {
|
||
// 检查是否是 GET 或 HEAD 方法
|
||
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodHead {
|
||
// 如果不是,且启用了 MethodNotAllowed 处理,则继续到 MethodNotAllowed 中间件
|
||
if engine.HandleMethodNotAllowed {
|
||
c.Next()
|
||
} else {
|
||
// 否则,返回 405 Method Not Allowed
|
||
engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
|
||
}
|
||
return
|
||
}
|
||
|
||
requestPath := c.Request.URL.Path
|
||
|
||
// 获取捕获到的文件路径参数
|
||
filepath := c.Param("filepath")
|
||
|
||
// 构造文件服务器需要处理的请求路径
|
||
// FileServer 会将请求路径与 http.Dir 的根路径结合
|
||
// 我们需要移除相对路径前缀,只保留文件路径部分
|
||
// 例如,如果 relativePath 是 "/static/",请求是 "/static/js/app.js"
|
||
// FileServer 需要的路径是 "/js/app.js"
|
||
// 这里的 filepath 参数已经包含了 "/" 前缀,例如 "/js/app.js"
|
||
// 所以直接使用 filepath 即可
|
||
c.Request.URL.Path = filepath
|
||
|
||
// 使用自定义的 ResponseWriter 包装器来捕获 FileServer 可能返回的错误状态码
|
||
// 这样我们可以在 FileServer 返回 404 或 403 时,使用 Engine 的 ErrorHandler 进行统一处理
|
||
ecw := AcquireErrorCapturingResponseWriter(c)
|
||
defer ReleaseErrorCapturingResponseWriter(ecw)
|
||
|
||
//
|
||
// 调用 FileServer 处理请求
|
||
fileServer.ServeHTTP(ecw, c.Request)
|
||
|
||
// 在 FileServer 处理完成后,检查是否捕获到错误状态码,并调用 ErrorHandler
|
||
ecw.processAfterFileServer()
|
||
|
||
// 恢复原始请求路径,以便后续中间件或日志记录使用
|
||
c.Request.URL.Path = requestPath
|
||
|
||
// 中止处理链,因为 FileServer 已经处理了响应
|
||
c.Abort()
|
||
})
|
||
}
|
||
|
||
// Group的StaticDir方式
|
||
func (group *RouterGroup) StaticDir(relativePath, rootPath string) {
|
||
// 清理路径
|
||
relativePath = path.Clean(relativePath)
|
||
rootPath = path.Clean(rootPath)
|
||
|
||
// 确保相对路径以 '/' 结尾,以便 FileServer 正确处理子路径
|
||
if !strings.HasSuffix(relativePath, "/") {
|
||
relativePath += "/"
|
||
}
|
||
|
||
// 创建一个文件系统处理器
|
||
fileServer := http.FileServer(http.Dir(rootPath))
|
||
|
||
// 注册一个捕获所有路径的路由,使用自定义处理器
|
||
// 注意:这里使用 ANY 方法,但 FileServer 通常只处理 GET 和 HEAD
|
||
// 我们可以通过在处理函数内部检查方法来限制
|
||
group.ANY(relativePath+"*filepath", func(c *Context) {
|
||
// 检查是否是 GET 或 HEAD 方法
|
||
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodHead {
|
||
// 如果不是,且启用了 MethodNotAllowed 处理,则继续到 MethodNotAllowed 中间件
|
||
if group.engine.HandleMethodNotAllowed {
|
||
c.Next()
|
||
} else {
|
||
// 否则,返回 405 Method Not Allowed
|
||
group.engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
|
||
}
|
||
return
|
||
}
|
||
|
||
requestPath := c.Request.URL.Path
|
||
|
||
// 获取捕获到的文件路径参数
|
||
filepath := c.Param("filepath")
|
||
|
||
// 构造文件服务器需要处理的请求路径
|
||
// FileServer 会将请求路径与 http.Dir 的根路径结合
|
||
// 我们需要移除相对路径前缀,只保留文件路径部分
|
||
// 例如,如果 relativePath 是 "/static/",请求是 "/static/js/app.js"
|
||
// FileServer 需要的路径是 "/js/app.js"
|
||
// 这里的 filepath 参数已经包含了 "/" 前缀,例如 "/js/app.js"
|
||
// 所以直接使用 filepath 即可
|
||
c.Request.URL.Path = filepath
|
||
|
||
// 使用自定义的 ResponseWriter 包装器来捕获 FileServer 可能返回的错误状态码
|
||
// 这样我们可以在 FileServer 返回 404 或 403 时,使用 Engine 的 ErrorHandler 进行统一处理
|
||
ecw := AcquireErrorCapturingResponseWriter(c)
|
||
defer ReleaseErrorCapturingResponseWriter(ecw)
|
||
|
||
//
|
||
// 调用 FileServer 处理请求
|
||
fileServer.ServeHTTP(ecw, c.Request)
|
||
|
||
// 在 FileServer 处理完成后,检查是否捕获到错误状态码,并调用 ErrorHandler
|
||
ecw.processAfterFileServer()
|
||
|
||
// 恢复原始请求路径,以便后续中间件或日志记录使用
|
||
c.Request.URL.Path = requestPath
|
||
|
||
// 中止处理链,因为 FileServer 已经处理了响应
|
||
c.Abort()
|
||
})
|
||
}
|
||
|
||
// Static File 传入一个文件路径, 使用FileServer进行处理
|
||
func (engine *Engine) StaticFile(relativePath, filePath string) {
|
||
// 清理路径
|
||
relativePath = path.Clean(relativePath)
|
||
filePath = path.Clean(filePath)
|
||
|
||
// 创建一个文件系统处理器,指向包含目标文件的目录
|
||
// http.Dir 需要一个目录路径
|
||
dir := path.Dir(filePath)
|
||
fileName := path.Base(filePath)
|
||
fileServer := http.FileServer(http.Dir(dir))
|
||
|
||
FileHandle := func(c *Context) {
|
||
// 检查是否是 GET 或 HEAD 方法
|
||
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodHead {
|
||
// 如果不是,且启用了 MethodNotAllowed 处理,则继续到 MethodNotAllowed 中间件
|
||
if engine.HandleMethodNotAllowed {
|
||
c.Next()
|
||
} else {
|
||
// 否则,返回 405 Method Not Allowed
|
||
engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
|
||
}
|
||
return
|
||
}
|
||
|
||
requestPath := c.Request.URL.Path
|
||
|
||
// 构造文件服务器需要处理的请求路径
|
||
// FileServer 会将请求路径与 http.Dir 的根路径结合
|
||
// 我们需要将请求路径设置为文件名,以便 FileServer 找到正确的文件
|
||
c.Request.URL.Path = "/" + fileName // FileServer 期望路径以 / 开头
|
||
|
||
// 使用自定义的 ResponseWriter 包装器来捕获 FileServer 可能返回的错误状态码
|
||
ecw := AcquireErrorCapturingResponseWriter(c)
|
||
defer ReleaseErrorCapturingResponseWriter(ecw)
|
||
|
||
// 调用 FileServer 处理请求
|
||
fileServer.ServeHTTP(ecw, c.Request)
|
||
|
||
// 在 FileServer 处理完成后,检查是否捕获到错误状态码,并调用 ErrorHandler
|
||
ecw.processAfterFileServer()
|
||
|
||
// 恢复原始请求路径
|
||
c.Request.URL.Path = requestPath
|
||
|
||
// 中止处理链,因为 FileServer 已经处理了响应
|
||
c.Abort()
|
||
}
|
||
|
||
// 注册一个精确匹配的路由
|
||
engine.GET(relativePath, FileHandle)
|
||
engine.HEAD(relativePath, FileHandle)
|
||
engine.OPTIONS(relativePath, FileHandle)
|
||
|
||
}
|
||
|
||
// Group的StaticFile
|
||
func (group *RouterGroup) StaticFile(relativePath, filePath string) {
|
||
// 清理路径
|
||
relativePath = path.Clean(relativePath)
|
||
filePath = path.Clean(filePath)
|
||
|
||
// 创建一个文件系统处理器,指向包含目标文件的目录
|
||
// http.Dir 需要一个目录路径
|
||
dir := path.Dir(filePath)
|
||
fileName := path.Base(filePath)
|
||
fileServer := http.FileServer(http.Dir(dir))
|
||
|
||
FileHandle := func(c *Context) {
|
||
// 检查是否是 GET 或 HEAD 方法
|
||
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodHead {
|
||
// 如果不是,且启用了 MethodNotAllowed 处理,则继续到 MethodNotAllowed 中间件
|
||
if group.engine.HandleMethodNotAllowed {
|
||
c.Next()
|
||
} else {
|
||
// 否则,返回 405 Method Not Allowed
|
||
group.engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
|
||
}
|
||
return
|
||
}
|
||
|
||
requestPath := c.Request.URL.Path
|
||
|
||
// 构造文件服务器需要处理的请求路径
|
||
// FileServer 会将请求路径与 http.Dir 的根路径结合
|
||
// 我们需要将请求路径设置为文件名,以便 FileServer 找到正确的文件
|
||
c.Request.URL.Path = "/" + fileName // FileServer 期望路径以 / 开头
|
||
|
||
// 使用自定义的 ResponseWriter 包装器来捕获 FileServer 可能返回的错误状态码
|
||
ecw := AcquireErrorCapturingResponseWriter(c)
|
||
defer ReleaseErrorCapturingResponseWriter(ecw)
|
||
|
||
// 调用 FileServer 处理请求
|
||
fileServer.ServeHTTP(ecw, c.Request)
|
||
|
||
// 在 FileServer 处理完成后,检查是否捕获到错误状态码,并调用 ErrorHandler
|
||
ecw.processAfterFileServer()
|
||
|
||
// 恢复原始请求路径
|
||
c.Request.URL.Path = requestPath
|
||
|
||
// 中止处理链,因为 FileServer 已经处理了响应
|
||
c.Abort()
|
||
}
|
||
|
||
// 注册一个精确匹配的路由
|
||
group.GET(relativePath, FileHandle)
|
||
group.HEAD(relativePath, FileHandle)
|
||
group.OPTIONS(relativePath, FileHandle)
|
||
}
|
||
|
||
// 维护一个Methods列表
|
||
var (
|
||
MethodGet = "GET"
|
||
MethodHead = "HEAD"
|
||
MethodPost = "POST"
|
||
MethodPut = "PUT"
|
||
MethodPatch = "PATCH"
|
||
MethodDelete = "DELETE"
|
||
MethodConnect = "CONNECT"
|
||
MethodOptions = "OPTIONS"
|
||
MethodTrace = "TRACE"
|
||
)
|
||
|
||
var MethodsSet = map[string]struct{}{
|
||
MethodGet: {},
|
||
MethodHead: {},
|
||
MethodPost: {},
|
||
MethodPut: {},
|
||
MethodPatch: {},
|
||
MethodDelete: {},
|
||
MethodConnect: {},
|
||
MethodOptions: {},
|
||
MethodTrace: {},
|
||
}
|
||
|
||
// HandleFunc 注册一个或多个 HTTP 方法的路由
|
||
// methods 参数是一个字符串切片,包含要注册的 HTTP 方法(例如 []string{"GET", "POST"})
|
||
// relativePath 是相对于当前组或 Engine 的路径
|
||
// handlers 是处理函数链
|
||
func (engine *Engine) HandleFunc(methods []string, relativePath string, handlers ...HandlerFunc) {
|
||
for _, method := range methods {
|
||
if _, ok := MethodsSet[method]; !ok {
|
||
panic("invalid method: " + method)
|
||
}
|
||
engine.Handle(method, relativePath, handlers...)
|
||
}
|
||
}
|
||
|
||
// HandleFunc 注册一个或多个 HTTP 方法的路由
|
||
// methods 参数是一个字符串切片,包含要注册的 HTTP 方法(例如 []string{"GET", "POST"})
|
||
// relativePath 是相对于当前组或 Engine 的路径
|
||
// handlers 是处理函数链
|
||
func (group *RouterGroup) HandleFunc(methods []string, relativePath string, handlers ...HandlerFunc) {
|
||
for _, method := range methods {
|
||
if _, ok := MethodsSet[method]; !ok {
|
||
panic("invalid method: " + method)
|
||
}
|
||
group.Handle(method, relativePath, handlers...)
|
||
}
|
||
}
|
||
|
||
// FileServer方式, 返回一个HandleFunc, 统一化处理
|
||
func FileServer(fs http.FileSystem) HandlerFunc {
|
||
return func(c *Context) {
|
||
// 检查是否是 GET 或 HEAD 方法
|
||
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodHead {
|
||
// 如果不是,且启用了 MethodNotAllowed 处理,则继续到 MethodNotAllowed 中间件
|
||
if c.engine.HandleMethodNotAllowed {
|
||
c.Next()
|
||
} else {
|
||
// 否则,返回 405 Method Not Allowed
|
||
c.engine.errorHandle.handler(c, http.StatusMethodNotAllowed, fmt.Errorf("Method %s is Not Allowed on FileServer", c.Request.Method))
|
||
}
|
||
return
|
||
}
|
||
|
||
// 使用自定义的 ResponseWriter 包装器来捕获 FileServer 可能返回的错误状态码
|
||
ecw := AcquireErrorCapturingResponseWriter(c)
|
||
defer ReleaseErrorCapturingResponseWriter(ecw)
|
||
|
||
// 调用 http.FileServer 处理请求
|
||
http.FileServer(fs).ServeHTTP(ecw, c.Request)
|
||
|
||
// 在 FileServer 处理完成后,检查是否捕获到错误状态码,并调用 ErrorHandler
|
||
ecw.processAfterFileServer()
|
||
|
||
// 中止处理链,因为 FileServer 已经处理了响应
|
||
c.Abort()
|
||
}
|
||
}
|