diff --git a/server/http.go b/server/http.go new file mode 100644 index 0000000..38ba4ee --- /dev/null +++ b/server/http.go @@ -0,0 +1,19 @@ +package server + +// NewHttpHandleWrapper 创建一个新的 http 处理程序包装器 +// - 默认使用 server.HttpContext 作为上下文,如果需要依赖其作为新的上下文,可以通过 NewHttpContext 创建 +func NewHttpHandleWrapper[Context any](srv *Server, packer ContextPacker[Context]) *Http[Context] { + return &Http[Context]{ + HttpRouter: &HttpRouter[Context]{ + srv: srv, + group: srv.ginServer, + packer: packer, + }, + } +} + +// Http 基于 gin.Engine 包装的 http 服务器 +type Http[Context any] struct { + srv *Server + *HttpRouter[Context] +} diff --git a/server/http_context.go b/server/http_context.go new file mode 100644 index 0000000..0af46cd --- /dev/null +++ b/server/http_context.go @@ -0,0 +1,40 @@ +package server + +import ( + "github.com/gin-gonic/gin" +) + +// NewHttpContext 基于 gin.Context 创建一个新的 HttpContext +func NewHttpContext(ctx *gin.Context) *HttpContext { + hc := &HttpContext{ + ctx: ctx, + } + return hc +} + +// HttpContext 基于 gin.Context 的 http 请求上下文 +type HttpContext struct { + ctx *gin.Context +} + +// Gin 获取 gin.Context +func (slf *HttpContext) Gin() *gin.Context { + return slf.ctx +} + +// ReadTo 读取请求数据到指定结构体,如果失败则返回错误 +func (slf *HttpContext) ReadTo(dest any) error { + var ctx = slf.Gin() + if ctx == nil { + return nil + } + if err := ctx.ShouldBind(dest); err != nil { + if uri := ctx.ShouldBindUri(dest); uri == nil { + return uri + } else if query := ctx.ShouldBindQuery(dest); query == nil { + return query + } + return err + } + return nil +} diff --git a/server/http_router.go b/server/http_router.go new file mode 100644 index 0000000..2b49df9 --- /dev/null +++ b/server/http_router.go @@ -0,0 +1,145 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +type HandlerFunc[Context any] func(ctx Context) +type ContextPacker[Context any] func(ctx *gin.Context) Context + +type HttpRouter[Context any] struct { + srv *Server + group gin.IRouter + packer ContextPacker[Context] +} + +func (slf *HttpRouter[Context]) handlesConvert(handlers []HandlerFunc[Context]) []gin.HandlerFunc { + var handles []gin.HandlerFunc + for i := 0; i < len(handlers); i++ { + handler := handlers[i] + handles = append(handles, func(ctx *gin.Context) { + slf.srv.messageCounter.Add(1) + defer func() { + slf.srv.messageCounter.Add(-1) + }() + hc := slf.packer(ctx) + handler(hc) + }) + } + return handles +} + +// Handle 使用给定的路径和方法注册新的请求句柄和中间件 +// - 最后一个处理程序应该是真正的处理程序,其他处理程序应该是可以而且应该在不同路由之间共享的中间件。 +func (slf *HttpRouter[Context]) Handle(httpMethod, relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + handles := slf.handlesConvert(handlers) + slf.group.Handle(httpMethod, relativePath, handles...) + return slf +} + +// POST 是 Handle("POST", path, handlers) 的快捷方式 +func (slf *HttpRouter[Context]) POST(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + return slf.Handle(http.MethodPost, relativePath, handlers...) +} + +// GET 是 Handle("GET", path, handlers) 的快捷方式 +func (slf *HttpRouter[Context]) GET(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + return slf.Handle(http.MethodGet, relativePath, handlers...) +} + +// DELETE 是 Handle("DELETE", path, handlers) 的快捷方式 +func (slf *HttpRouter[Context]) DELETE(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + return slf.Handle(http.MethodDelete, relativePath, handlers...) +} + +// PATCH 是 Handle("PATCH", path, handlers) 的快捷方式 +func (slf *HttpRouter[Context]) PATCH(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + return slf.Handle(http.MethodPatch, relativePath, handlers...) +} + +// PUT 是 Handle("PUT", path, handlers) 的快捷方式 +func (slf *HttpRouter[Context]) PUT(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + return slf.Handle(http.MethodPut, relativePath, handlers...) +} + +// OPTIONS 是 Handle("OPTIONS", path, handlers) 的快捷方式 +func (slf *HttpRouter[Context]) OPTIONS(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + return slf.Handle(http.MethodOptions, relativePath, handlers...) +} + +// HEAD 是 Handle("HEAD", path, handlers) 的快捷方式 +func (slf *HttpRouter[Context]) HEAD(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + return slf.Handle(http.MethodHead, relativePath, handlers...) +} + +// CONNECT 是 Handle("CONNECT", path, handlers) 的快捷方式 +func (slf *HttpRouter[Context]) CONNECT(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + return slf.Handle(http.MethodConnect, relativePath, handlers...) +} + +// TRACE 是 Handle("TRACE", path, handlers) 的快捷方式 +func (slf *HttpRouter[Context]) TRACE(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + return slf.Handle(http.MethodTrace, relativePath, handlers...) +} + +// Any 注册一个匹配所有 HTTP 方法的路由 +// - GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. +func (slf *HttpRouter[Context]) Any(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + for _, m := range []string{ + http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodHead, + http.MethodOptions, http.MethodDelete, http.MethodConnect, http.MethodTrace} { + slf.Handle(m, relativePath, handlers...) + } + return slf +} + +// Match 注册一个匹配指定 HTTP 方法的路由 +// - GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. +func (slf *HttpRouter[Context]) Match(methods []string, relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + for _, m := range methods { + slf.Handle(m, relativePath, handlers...) + } + return slf +} + +// StaticFile 注册单个路由以便为本地文件系统的单个文件提供服务。 +// - 例如: StaticFile("favicon.ico", "./resources/favicon.ico") +func (slf *HttpRouter[Context]) StaticFile(relativePath, filepath string) *HttpRouter[Context] { + slf.group.StaticFile(relativePath, filepath) + return slf +} + +// StaticFileFS 与 `StaticFile` 类似,但可以使用自定义的 `http.FileSystem` 代替。 +// - 例如: StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false}) +// - 由于依赖于 gin.Engine 默认情况下使用:gin.Dir +func (slf *HttpRouter[Context]) StaticFileFS(relativePath, filepath string, fs http.FileSystem) *HttpRouter[Context] { + slf.group.StaticFileFS(relativePath, filepath, fs) + return slf +} + +// Static 提供来自给定文件系统根目录的文件。 +// - 例如: Static("/static", "/var/www") +func (slf *HttpRouter[Context]) Static(relativePath, root string) *HttpRouter[Context] { + slf.group.StaticFS(relativePath, gin.Dir(root, false)) + return slf +} + +// StaticFS 与 `Static` 类似,但可以使用自定义的 `http.FileSystem` 代替。 +// - 例如: StaticFS("/static", Dir{"/var/www", false}) +// - 由于依赖于 gin.Engine 默认情况下使用:gin.Dir +func (slf *HttpRouter[Context]) StaticFS(relativePath string, fs http.FileSystem) *HttpRouter[Context] { + slf.group.StaticFS(relativePath, fs) + return slf +} + +// Group 创建一个新的路由组。您应该添加所有具有共同中间件的路由。 +// - 例如: v1 := slf.Group("/v1") +func (slf *HttpRouter[Context]) Group(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] { + group := slf.group.Group(relativePath, slf.handlesConvert(handlers)...) + return &HttpRouter[Context]{ + srv: slf.srv, + group: group, + packer: slf.packer, + } +} diff --git a/server/http_wrapper.go b/server/http_wrapper.go index 0ed91fb..5a9cce9 100644 --- a/server/http_wrapper.go +++ b/server/http_wrapper.go @@ -7,9 +7,22 @@ import ( type HttpWrapperHandleFunc[CTX any] func(ctx CTX) +// NewHttpWrapper 创建 http 包装器 +// +// Deprecated: 从 Minotaur 0.0.29 开始,由于该函数基于 *Server.HttpRouter 函数设计,已弃用。 +// 如果需要单纯的对 *gin.Engine 进行包装,可以使用 NewGinWrapper 函数进行包装。该函数已不在建议对 server.Server 使用。 +// 如果需要对 Server.HttpServer 进行包装,可以使用 NewHttpHandleWrapper 函数进行包装。 func NewHttpWrapper[CTX any](server *Server, pack func(ctx *gin.Context) CTX) *HttpWrapper[CTX] { return &HttpWrapper[CTX]{ - server: server.HttpRouter().(*gin.Engine), + server: server.ginServer, + packHandle: pack, + } +} + +// NewGinWrapper 创建 gin 包装器,用于对 NewHttpWrapper 函数的替代 +func NewGinWrapper[CTX any](server *gin.Engine, pack func(ctx *gin.Context) CTX) *HttpWrapper[CTX] { + return &HttpWrapper[CTX]{ + server: server, packHandle: pack, } } diff --git a/server/server.go b/server/server.go index 850cf4a..31fbe01 100644 --- a/server/server.go +++ b/server/server.go @@ -497,6 +497,9 @@ func (slf *Server) GRPCServer() *grpc.Server { } // HttpRouter 当网络类型为 NetworkHttp 时将被允许获取路由器进行路由注册,否则将会发生 panic +// - 通过该函数注册的路由将无法在服务器关闭时正常等待请求结束 +// +// Deprecated: 从 Minotaur 0.0.29 开始,由于设计原因已弃用,该函数将直接返回 *gin.Server 对象,导致无法正常的对请求结束时进行处理 func (slf *Server) HttpRouter() gin.IRouter { if slf.ginServer == nil { panic(ErrNetworkOnlySupportHttp) @@ -504,6 +507,23 @@ func (slf *Server) HttpRouter() gin.IRouter { return slf.ginServer } +// HttpServer 替代 HttpRouter 的函数,返回一个 *Http[*HttpContext] 对象 +// - 通过该函数注册的路由将在服务器关闭时正常等待请求结束 +// - 如果需要自行包装 Context 对象,可以使用 NewHttpHandleWrapper 方法 +func (slf *Server) HttpServer() *Http[*HttpContext] { + if slf.ginServer == nil { + panic(ErrNetworkOnlySupportHttp) + } + return NewHttpHandleWrapper(slf, func(ctx *gin.Context) *HttpContext { + return NewHttpContext(ctx) + }) +} + +// GetMessageCount 获取当前服务器中消息的数量 +func (slf *Server) GetMessageCount() int64 { + return slf.messageCounter.Load() +} + // ShuntChannelFreed 释放分流通道 func (slf *Server) ShuntChannelFreed(channelGuid int64) { if slf.shuntChannels == nil { @@ -648,7 +668,7 @@ func (slf *Server) dispatchMessage(msg *Message) { PushSystemMessage(slf, func() { callback(err) }, "AsyncCallback") - } else { + } else if err != nil { log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("error", err), log.String("stack", string(debug.Stack()))) } }); err != nil {