diff --git a/app.go b/app.go index d2fc569..723b1de 100644 --- a/app.go +++ b/app.go @@ -8,14 +8,14 @@ import ( "github.com/ebrickdev/ebrick/logger" "github.com/ebrickdev/ebrick/module" "github.com/ebrickdev/ebrick/transport/grpc" - "github.com/ebrickdev/ebrick/transport/httpserver" + "github.com/ebrickdev/ebrick/transport/http" ) // Application defines the interface for the application. type Application interface { RegisterModules(ctx context.Context, modules ...module.Module) error GrpcServer() grpc.GRPCServer - HTTPServer() httpserver.HTTPServer + HTTPServer() http.HTTPServer Start(ctx context.Context) error Options() *Options } @@ -23,7 +23,7 @@ type Application interface { // application is the implementation of the Application interface. type application struct { mm *module.ModuleManager - httpServer httpserver.HTTPServer + httpServer http.HTTPServer grpcServer grpc.GRPCServer options *Options } @@ -34,7 +34,7 @@ func (app *application) GrpcServer() grpc.GRPCServer { } // HTTPServer returns the web server instance. -func (app *application) HTTPServer() httpserver.HTTPServer { +func (app *application) HTTPServer() http.HTTPServer { return app.httpServer } @@ -89,7 +89,7 @@ func (app *application) Start(ctx context.Context) error { return combinedErr } -// registerRoutesAndServices registers all routes for modules implementing httpserver.Routable or grpc.ServiceRegistrar. +// registerRoutesAndServices registers all routes for modules implementing http.Routable or grpc.ServiceRegistrar. func (app *application) registerRoutesAndServices(log logger.Logger) error { for _, mod := range app.mm.GetModules() { if svcReg, ok := mod.(grpc.ServiceRegistrar); ok { diff --git a/ebrick.go b/ebrick.go index da5a4b4..a7e75ee 100644 --- a/ebrick.go +++ b/ebrick.go @@ -6,14 +6,14 @@ import ( "github.com/ebrickdev/ebrick/module" "github.com/ebrickdev/ebrick/security/auth" "github.com/ebrickdev/ebrick/transport/grpc" - "github.com/ebrickdev/ebrick/transport/httpserver" + "github.com/ebrickdev/ebrick/transport/http" "gorm.io/gorm" ) var ( EventBus messaging.EventBus Logger logger.Logger - HTTPServer httpserver.HTTPServer + HTTPServer http.HTTPServer GRPCServer grpc.GRPCServer AuthManager auth.AuthManager DB *gorm.DB @@ -25,7 +25,7 @@ func NewApplication(opts ...Option) Application { // Set the global variables here. EventBus = options.EventBus Logger = options.Logger - HTTPServer = options.HTTPServer + HTTPServer = options.http GRPCServer = options.GRPCServer AuthManager = options.AuthManager DB = options.DB @@ -39,7 +39,7 @@ func NewApplication(opts ...Option) Application { app := &application{ mm: moduleManager, - httpServer: options.HTTPServer, + httpServer: options.http, grpcServer: options.GRPCServer, options: options, } diff --git a/options.go b/options.go index 68f0246..a02645a 100644 --- a/options.go +++ b/options.go @@ -9,19 +9,19 @@ import ( "github.com/ebrickdev/ebrick/messaging" "github.com/ebrickdev/ebrick/security/auth" "github.com/ebrickdev/ebrick/transport/grpc" - "github.com/ebrickdev/ebrick/transport/httpserver" + "github.com/ebrickdev/ebrick/transport/http" "gorm.io/gorm" ) // Options holds both configuration values and runtime dependencies type Options struct { - Name string // Application name - Version string // Application version - Cache cache.Cache // Cache instance - Logger logger.Logger // Logger instance - EventBus messaging.EventBus // Event bus instance for inter-component communication - HTTPServer httpserver.HTTPServer // HTTP server instance - GRPCServer grpc.GRPCServer // gRPC server instance; optional + Name string // Application name + Version string // Application version + Cache cache.Cache // Cache instance + Logger logger.Logger // Logger instance + EventBus messaging.EventBus // Event bus instance for inter-component communication + http http.HTTPServer // HTTP server instance + GRPCServer grpc.GRPCServer // gRPC server instance; optional DB *gorm.DB AuthManager auth.AuthManager } @@ -61,9 +61,9 @@ func newOptions(opts ...Option) *Options { opt.EventBus = eventBus } - // Initialize HTTPServer if not provided. - // NewHTTPServer uses the application config to configure the server. - if opt.HTTPServer == nil { + // Initialize http if not provided. + // Newhttp uses the application config to configure the server. + if opt.http == nil { var webMode string if cfg.Env == "development" { webMode = "debug" @@ -71,9 +71,9 @@ func newOptions(opts ...Option) *Options { webMode = "release" } - opt.HTTPServer = httpserver.NewHTTPServer( - httpserver.WithAddress(fmt.Sprintf(":%s", cfg.Server.Port)), - httpserver.WithMode(webMode), + opt.http = http.NewHTTPServer( + http.WithAddress(fmt.Sprintf(":%s", cfg.Server.Port)), + http.WithMode(webMode), ) } @@ -116,9 +116,9 @@ func WithEventBus(eventBus messaging.EventBus) Option { return func(o *Options) { o.EventBus = eventBus } } -// WithHTTPServer sets the HTTPServer dependency. -func WithHTTPServer(httpServer httpserver.HTTPServer) Option { - return func(o *Options) { o.HTTPServer = httpServer } +// Withhttp sets the http dependency. +func Withhttp(http http.HTTPServer) Option { + return func(o *Options) { o.http = http } } // WithGRPCServer sets the GRPCServer dependency. diff --git a/security/auth/middleware.go b/security/auth/middleware.go index d1a6375..900845b 100644 --- a/security/auth/middleware.go +++ b/security/auth/middleware.go @@ -2,25 +2,21 @@ package auth import ( "errors" - "net/http" "strings" "github.com/ebrickdev/ebrick/logger" - "github.com/ebrickdev/ebrick/transport/httpserver" + "github.com/ebrickdev/ebrick/transport/http" ) const ( - // bearerPrefix is the expected prefix in the Authorization header. bearerPrefix = "Bearer " ) -// AuthMiddleware provides middleware for token authentication and role-based authorization. type AuthMiddleware struct { authManager AuthManager logger logger.Logger } -// NewAuthMiddleware creates a new AuthMiddleware instance. func NewAuthMiddleware(authManager AuthManager, logger logger.Logger) *AuthMiddleware { return &AuthMiddleware{ authManager: authManager, @@ -28,64 +24,63 @@ func NewAuthMiddleware(authManager AuthManager, logger logger.Logger) *AuthMiddl } } -// TokenAuth is middleware that validates a JWT token from the Authorization header. -func (am *AuthMiddleware) TokenAuth() httpserver.HandlerFunc { - return func(c httpserver.Context) { - authHeader := c.Request().Header.Get("Authorization") +func (am *AuthMiddleware) TokenAuth() http.HandlerFunc { + return func(c *http.Context) { + authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { am.logger.Warn("Authorization header missing") - abortWithError(c, http.StatusUnauthorized, "missing token") + c.AbortWithError(http.StatusUnauthorized, errors.New("missing token")) return } - if !strings.HasPrefix(authHeader, bearerPrefix) { + if len(authHeader) < len(bearerPrefix) || !strings.EqualFold(authHeader[:len(bearerPrefix)], bearerPrefix) { am.logger.Warn("Authorization header missing Bearer prefix") - abortWithError(c, http.StatusUnauthorized, "invalid token format") + c.AbortWithError(http.StatusUnauthorized, errors.New("invalid token format")) return } - tokenString := strings.TrimPrefix(authHeader, bearerPrefix) + tokenString := strings.TrimSpace(authHeader[len(bearerPrefix):]) if tokenString == "" { am.logger.Warn("Empty token after Bearer prefix removal") - abortWithError(c, http.StatusUnauthorized, "missing token") + c.AbortWithError(http.StatusUnauthorized, errors.New("missing token")) return } - principal, err := am.authManager.Authenticate(c.Request().Context(), tokenString) + principal, err := am.authManager.Authenticate(c.Request.Context(), tokenString) if err != nil { am.logger.Error("Token authentication failed", logger.Error(err)) - if isTokenExpired(err) { - abortWithError(c, http.StatusUnauthorized, "token expired") + if errors.Is(err, ErrTokenExpired) { + c.AbortWithError(http.StatusUnauthorized, errors.New("token expired")) } else { - abortWithError(c, http.StatusUnauthorized, "invalid or malformed token") + c.AbortWithError(http.StatusUnauthorized, errors.New("invalid or malformed token")) } return } - // Store authentication claims and identifier in the context for later use. c.Set("claims", principal) c.Set("user_id", principal.GetID()) c.Next() } } -// RequireRoles is middleware that restricts access to endpoints based on user roles. -func (am *AuthMiddleware) RequireRoles(requiredRoles ...string) httpserver.HandlerFunc { - return func(c httpserver.Context) { +func (am *AuthMiddleware) RequireRoles(requiredRoles ...string) http.HandlerFunc { + return func(c *http.Context) { rawClaims, exists := c.Get("claims") if !exists { - abortWithError(c, http.StatusForbidden, "access forbidden") + am.logger.Warn("Claims not found in context") + c.AbortWithError(http.StatusForbidden, ErrAccessForbidden) return } principal, ok := rawClaims.(Principal) if !ok { - abortWithError(c, http.StatusForbidden, "access forbidden") + am.logger.Warn("Invalid claims type in context") + c.AbortWithError(http.StatusForbidden, ErrAccessForbidden) return } if !hasAnyRole(principal.GetRoles(), requiredRoles) { - abortWithError(c, http.StatusForbidden, "access forbidden") + c.AbortWithError(http.StatusForbidden, ErrAccessForbidden) return } @@ -93,21 +88,9 @@ func (am *AuthMiddleware) RequireRoles(requiredRoles ...string) httpserver.Handl } } -// abortWithError sends a JSON error response and aborts further processing. -func abortWithError(c httpserver.Context, statusCode int, message string) { - c.JSON(statusCode, httpserver.H{"error": message}) - c.Abort() -} - -// isTokenExpired checks if the provided error indicates an expired token. -func isTokenExpired(err error) bool { - return errors.Is(err, ErrTokenExpired) -} - -// ErrTokenExpired is returned when a token has expired. var ErrTokenExpired = errors.New("token expired") +var ErrAccessForbidden = errors.New("access forbidden") -// hasAnyRole returns true if any of the requiredRoles is present in userRoles. func hasAnyRole(userRoles, requiredRoles []string) bool { for _, role := range requiredRoles { if containsString(userRoles, role) { @@ -117,7 +100,6 @@ func hasAnyRole(userRoles, requiredRoles []string) bool { return false } -// containsString checks if a slice contains the specified string. func containsString(slice []string, item string) bool { for _, s := range slice { if s == item { diff --git a/transport/httpserver/gin_server.go b/transport/http/gin_server.go similarity index 50% rename from transport/httpserver/gin_server.go rename to transport/http/gin_server.go index b56b0f9..681c7e7 100644 --- a/transport/httpserver/gin_server.go +++ b/transport/http/gin_server.go @@ -1,4 +1,4 @@ -package httpserver +package http import ( "context" @@ -11,8 +11,7 @@ import ( "github.com/gin-gonic/gin" ) -// ginEngine implements the Server interface using Gin. -type ginEngine struct { +type httpServer struct { engine *gin.Engine server *http.Server } @@ -35,12 +34,12 @@ func NewHTTPServer(opts ...Option) HTTPServer { for _, m := range options.Middleware { // Wrap each middleware to convert Gin context to our custom context. engine.Use(func(c *gin.Context) { - m(NewGinContext(c)) + m(c) }) } // Construct and return the ginEngine. - return &ginEngine{ + return &httpServer{ engine: engine, server: &http.Server{ Addr: options.Address, @@ -48,68 +47,15 @@ func NewHTTPServer(opts ...Option) HTTPServer { }, } } - -func (s *ginEngine) GET(route string, handler HandlerFunc) { - s.engine.GET(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (s *ginEngine) POST(route string, handler HandlerFunc) { - s.engine.POST(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (s *ginEngine) PUT(route string, handler HandlerFunc) { - s.engine.PUT(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (s *ginEngine) PATCH(route string, handler HandlerFunc) { - s.engine.PATCH(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (s *ginEngine) DELETE(route string, handler HandlerFunc) { - s.engine.DELETE(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (s *ginEngine) OPTIONS(route string, handler HandlerFunc) { - s.engine.OPTIONS(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (s *ginEngine) HEAD(route string, handler HandlerFunc) { - s.engine.HEAD(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -// Group creates a new RouterGroup with the specified prefix. -func (s *ginEngine) Group(prefix string) RouterGroup { - return &ginRouterGroup{group: s.engine.Group(prefix)} -} - -// Use registers one or more middleware handlers. -func (s *ginEngine) Use(middleware ...HandlerFunc) { - for _, m := range middleware { - s.engine.Use(func(c *gin.Context) { - m(NewGinContext(c)) - }) - } +func (s *httpServer) Engine() *Engine { + return s.engine } // Start launches the web server and blocks until an OS signal is received for shutdown. -func (s *ginEngine) Start() error { +func (s *httpServer) Start() error { // Start the server in a separate goroutine. go func() { - log.Printf("WEB: Starting HTTPServer on %s", s.server.Addr) + log.Printf("WEB: Starting http on %s", s.server.Addr) if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Printf("Server error: %v", err) } @@ -125,7 +71,7 @@ func (s *ginEngine) Start() error { } // Stop gracefully shuts down the server using a configured timeout. -func (s *ginEngine) Stop() error { +func (s *httpServer) Stop() error { // Use a shutdown timeout; options.WriteTimeout is used here, // but you might consider a dedicated shutdown timeout option. ctx, cancel := context.WithTimeout(context.Background(), s.server.WriteTimeout) diff --git a/transport/http/http_server.go b/transport/http/http_server.go new file mode 100644 index 0000000..acf8b08 --- /dev/null +++ b/transport/http/http_server.go @@ -0,0 +1,16 @@ +package http + +import ( + "github.com/ebrickdev/ebrick/transport" +) + +// Server defines the abstraction for the web server. +type HTTPServer interface { + Engine() *Engine + transport.Server +} + +// Routable is an optional interface that modules can implement to register HTTP routes. +type Routable interface { + RegisterRoutes(router RouterGroup) +} diff --git a/transport/httpserver/options.go b/transport/http/options.go similarity index 98% rename from transport/httpserver/options.go rename to transport/http/options.go index 3073568..534e700 100644 --- a/transport/httpserver/options.go +++ b/transport/http/options.go @@ -1,4 +1,4 @@ -package httpserver +package http import ( "time" diff --git a/transport/http/status.go b/transport/http/status.go new file mode 100644 index 0000000..cd90877 --- /dev/null +++ b/transport/http/status.go @@ -0,0 +1,210 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http + +// HTTP status codes as registered with IANA. +// See: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml +const ( + StatusContinue = 100 // RFC 9110, 15.2.1 + StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2 + StatusProcessing = 102 // RFC 2518, 10.1 + StatusEarlyHints = 103 // RFC 8297 + + StatusOK = 200 // RFC 9110, 15.3.1 + StatusCreated = 201 // RFC 9110, 15.3.2 + StatusAccepted = 202 // RFC 9110, 15.3.3 + StatusNonAuthoritativeInfo = 203 // RFC 9110, 15.3.4 + StatusNoContent = 204 // RFC 9110, 15.3.5 + StatusResetContent = 205 // RFC 9110, 15.3.6 + StatusPartialContent = 206 // RFC 9110, 15.3.7 + StatusMultiStatus = 207 // RFC 4918, 11.1 + StatusAlreadyReported = 208 // RFC 5842, 7.1 + StatusIMUsed = 226 // RFC 3229, 10.4.1 + + StatusMultipleChoices = 300 // RFC 9110, 15.4.1 + StatusMovedPermanently = 301 // RFC 9110, 15.4.2 + StatusFound = 302 // RFC 9110, 15.4.3 + StatusSeeOther = 303 // RFC 9110, 15.4.4 + StatusNotModified = 304 // RFC 9110, 15.4.5 + StatusUseProxy = 305 // RFC 9110, 15.4.6 + _ = 306 // RFC 9110, 15.4.7 (Unused) + StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8 + StatusPermanentRedirect = 308 // RFC 9110, 15.4.9 + + StatusBadRequest = 400 // RFC 9110, 15.5.1 + StatusUnauthorized = 401 // RFC 9110, 15.5.2 + StatusPaymentRequired = 402 // RFC 9110, 15.5.3 + StatusForbidden = 403 // RFC 9110, 15.5.4 + StatusNotFound = 404 // RFC 9110, 15.5.5 + StatusMethodNotAllowed = 405 // RFC 9110, 15.5.6 + StatusNotAcceptable = 406 // RFC 9110, 15.5.7 + StatusProxyAuthRequired = 407 // RFC 9110, 15.5.8 + StatusRequestTimeout = 408 // RFC 9110, 15.5.9 + StatusConflict = 409 // RFC 9110, 15.5.10 + StatusGone = 410 // RFC 9110, 15.5.11 + StatusLengthRequired = 411 // RFC 9110, 15.5.12 + StatusPreconditionFailed = 412 // RFC 9110, 15.5.13 + StatusRequestEntityTooLarge = 413 // RFC 9110, 15.5.14 + StatusRequestURITooLong = 414 // RFC 9110, 15.5.15 + StatusUnsupportedMediaType = 415 // RFC 9110, 15.5.16 + StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17 + StatusExpectationFailed = 417 // RFC 9110, 15.5.18 + StatusTeapot = 418 // RFC 9110, 15.5.19 (Unused) + StatusMisdirectedRequest = 421 // RFC 9110, 15.5.20 + StatusUnprocessableEntity = 422 // RFC 9110, 15.5.21 + StatusLocked = 423 // RFC 4918, 11.3 + StatusFailedDependency = 424 // RFC 4918, 11.4 + StatusTooEarly = 425 // RFC 8470, 5.2. + StatusUpgradeRequired = 426 // RFC 9110, 15.5.22 + StatusPreconditionRequired = 428 // RFC 6585, 3 + StatusTooManyRequests = 429 // RFC 6585, 4 + StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 + StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 + + StatusInternalServerError = 500 // RFC 9110, 15.6.1 + StatusNotImplemented = 501 // RFC 9110, 15.6.2 + StatusBadGateway = 502 // RFC 9110, 15.6.3 + StatusServiceUnavailable = 503 // RFC 9110, 15.6.4 + StatusGatewayTimeout = 504 // RFC 9110, 15.6.5 + StatusHTTPVersionNotSupported = 505 // RFC 9110, 15.6.6 + StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1 + StatusInsufficientStorage = 507 // RFC 4918, 11.5 + StatusLoopDetected = 508 // RFC 5842, 7.2 + StatusNotExtended = 510 // RFC 2774, 7 + StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 +) + +// StatusText returns a text for the HTTP status code. It returns the empty +// string if the code is unknown. +func StatusText(code int) string { + switch code { + case StatusContinue: + return "Continue" + case StatusSwitchingProtocols: + return "Switching Protocols" + case StatusProcessing: + return "Processing" + case StatusEarlyHints: + return "Early Hints" + case StatusOK: + return "OK" + case StatusCreated: + return "Created" + case StatusAccepted: + return "Accepted" + case StatusNonAuthoritativeInfo: + return "Non-Authoritative Information" + case StatusNoContent: + return "No Content" + case StatusResetContent: + return "Reset Content" + case StatusPartialContent: + return "Partial Content" + case StatusMultiStatus: + return "Multi-Status" + case StatusAlreadyReported: + return "Already Reported" + case StatusIMUsed: + return "IM Used" + case StatusMultipleChoices: + return "Multiple Choices" + case StatusMovedPermanently: + return "Moved Permanently" + case StatusFound: + return "Found" + case StatusSeeOther: + return "See Other" + case StatusNotModified: + return "Not Modified" + case StatusUseProxy: + return "Use Proxy" + case StatusTemporaryRedirect: + return "Temporary Redirect" + case StatusPermanentRedirect: + return "Permanent Redirect" + case StatusBadRequest: + return "Bad Request" + case StatusUnauthorized: + return "Unauthorized" + case StatusPaymentRequired: + return "Payment Required" + case StatusForbidden: + return "Forbidden" + case StatusNotFound: + return "Not Found" + case StatusMethodNotAllowed: + return "Method Not Allowed" + case StatusNotAcceptable: + return "Not Acceptable" + case StatusProxyAuthRequired: + return "Proxy Authentication Required" + case StatusRequestTimeout: + return "Request Timeout" + case StatusConflict: + return "Conflict" + case StatusGone: + return "Gone" + case StatusLengthRequired: + return "Length Required" + case StatusPreconditionFailed: + return "Precondition Failed" + case StatusRequestEntityTooLarge: + return "Request Entity Too Large" + case StatusRequestURITooLong: + return "Request URI Too Long" + case StatusUnsupportedMediaType: + return "Unsupported Media Type" + case StatusRequestedRangeNotSatisfiable: + return "Requested Range Not Satisfiable" + case StatusExpectationFailed: + return "Expectation Failed" + case StatusTeapot: + return "I'm a teapot" + case StatusMisdirectedRequest: + return "Misdirected Request" + case StatusUnprocessableEntity: + return "Unprocessable Entity" + case StatusLocked: + return "Locked" + case StatusFailedDependency: + return "Failed Dependency" + case StatusTooEarly: + return "Too Early" + case StatusUpgradeRequired: + return "Upgrade Required" + case StatusPreconditionRequired: + return "Precondition Required" + case StatusTooManyRequests: + return "Too Many Requests" + case StatusRequestHeaderFieldsTooLarge: + return "Request Header Fields Too Large" + case StatusUnavailableForLegalReasons: + return "Unavailable For Legal Reasons" + case StatusInternalServerError: + return "Internal Server Error" + case StatusNotImplemented: + return "Not Implemented" + case StatusBadGateway: + return "Bad Gateway" + case StatusServiceUnavailable: + return "Service Unavailable" + case StatusGatewayTimeout: + return "Gateway Timeout" + case StatusHTTPVersionNotSupported: + return "HTTP Version Not Supported" + case StatusVariantAlsoNegotiates: + return "Variant Also Negotiates" + case StatusInsufficientStorage: + return "Insufficient Storage" + case StatusLoopDetected: + return "Loop Detected" + case StatusNotExtended: + return "Not Extended" + case StatusNetworkAuthenticationRequired: + return "Network Authentication Required" + default: + return "" + } +} diff --git a/transport/http/types.go b/transport/http/types.go new file mode 100644 index 0000000..40f9fca --- /dev/null +++ b/transport/http/types.go @@ -0,0 +1,11 @@ +package http + +import "github.com/gin-gonic/gin" + +type H map[string]any +type Error = gin.Error +type Negotiate = gin.Negotiate +type Context = gin.Context +type RouterGroup = gin.RouterGroup +type Engine = gin.Engine +type HandlerFunc = gin.HandlerFunc diff --git a/transport/httpserver/gin_ctx.go b/transport/httpserver/gin_ctx.go deleted file mode 100644 index 6bc8962..0000000 --- a/transport/httpserver/gin_ctx.go +++ /dev/null @@ -1,101 +0,0 @@ -package httpserver - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -// ginContext wraps Gin's context to implement the Context interface. -type ginContext struct { - ctx *gin.Context -} - -// ShouldBindBodyWithJSON implements Context. -func (g *ginContext) ShouldBindBodyWithJSON(obj any) error { - return g.ctx.ShouldBindBodyWithJSON(obj) -} - -// ShouldBindBodyWithTOML implements Context. -func (g *ginContext) ShouldBindBodyWithTOML(obj any) error { - return g.ctx.ShouldBindBodyWithTOML(obj) -} - -// ShouldBindBodyWithXML implements Context. -func (g *ginContext) ShouldBindBodyWithXML(obj any) error { - return g.ctx.ShouldBindBodyWithXML(obj) -} - -// ShouldBindBodyWithYAML implements Context. -func (g *ginContext) ShouldBindBodyWithYAML(obj any) error { - return g.ctx.ShouldBindBodyWithYAML(obj) -} - -// NewGinContext wraps Gin's context and returns a new ginContext. -func NewGinContext(ctx *gin.Context) Context { - return &ginContext{ctx: ctx} -} - -// JSON sends a JSON response with the specified status code. -func (g *ginContext) JSON(statusCode int, obj interface{}) { - g.ctx.JSON(statusCode, obj) -} - -// String sends a string response with the specified status code and format. -func (g *ginContext) String(statusCode int, format string, values ...interface{}) { - g.ctx.String(statusCode, format, values...) -} - -// Param returns the value of the URL parameter with the given key. -func (g *ginContext) Param(key string) string { - return g.ctx.Param(key) -} - -// Query returns the value of the query parameter with the given key. -func (g *ginContext) Query(key string) string { - return g.ctx.Query(key) -} - -// ShouldBindJSON binds the JSON payload to the given object. -func (g *ginContext) ShouldBindJSON(obj interface{}) error { - return g.ctx.ShouldBindJSON(obj) -} - -// Next calls the next handler in the middleware chain. -func (g *ginContext) Next() { - g.ctx.Next() -} - -func (g *ginContext) Get(key string) (value any, exists bool) { - return g.ctx.Get(key) -} - -// Set sets a key-value pair in the context. -func (g *ginContext) Set(key string, value any) { - g.ctx.Set(key, value) -} - -// AbortWithStatus aborts the request with the specified status code. -func (g *ginContext) AbortWithStatus(code int) { - g.ctx.AbortWithStatus(code) -} - -// Abort aborts the request. -func (g *ginContext) Abort() { - g.ctx.Abort() -} - -// Request returns the HTTP request. -func (g *ginContext) Request() *http.Request { - return g.ctx.Request -} - -// ClientIP returns the client's IP address. -func (g *ginContext) ClientIP() string { - return g.ctx.ClientIP() -} - -// SetHeader sets a header key-value pair in the response. -func (g *ginContext) SetHeader(key, value string) { - g.ctx.Header(key, value) -} diff --git a/transport/httpserver/gin_router_group.go b/transport/httpserver/gin_router_group.go deleted file mode 100644 index f6ab5de..0000000 --- a/transport/httpserver/gin_router_group.go +++ /dev/null @@ -1,66 +0,0 @@ -package httpserver - -import ( - "github.com/gin-gonic/gin" -) - -// ginRouterGroup implements the RouterGroup interface -type ginRouterGroup struct { - group *gin.RouterGroup -} - -// Use registers one or more middleware handlers. -func (s *ginRouterGroup) Use(middleware ...HandlerFunc) { - for _, m := range middleware { - s.group.Use(func(c *gin.Context) { - m(NewGinContext(c)) - }) - } -} - -// Group implements RouterGroup. -func (g *ginRouterGroup) Group(prefix string) RouterGroup { - return &ginRouterGroup{group: g.group.Group(prefix)} -} - -func (g *ginRouterGroup) GET(route string, handler HandlerFunc) { - g.group.GET(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (g *ginRouterGroup) POST(route string, handler HandlerFunc) { - g.group.POST(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (g *ginRouterGroup) PUT(route string, handler HandlerFunc) { - g.group.PUT(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (g *ginRouterGroup) PATCH(route string, handler HandlerFunc) { - g.group.PATCH(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (g *ginRouterGroup) DELETE(route string, handler HandlerFunc) { - g.group.DELETE(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (g *ginRouterGroup) OPTIONS(route string, handler HandlerFunc) { - g.group.OPTIONS(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} - -func (g *ginRouterGroup) HEAD(route string, handler HandlerFunc) { - g.group.HEAD(route, func(c *gin.Context) { - handler(NewGinContext(c)) - }) -} diff --git a/transport/httpserver/server.go b/transport/httpserver/server.go deleted file mode 100644 index d5020dd..0000000 --- a/transport/httpserver/server.go +++ /dev/null @@ -1,80 +0,0 @@ -package httpserver - -import ( - "net/http" - - "github.com/ebrickdev/ebrick/transport" -) - -type H map[string]any - -// HandlerFunc defines a generic HTTP handler function type. -type HandlerFunc func(ctx Context) - -// Context abstracts the HTTP context used by the framework. -type Context interface { - // JSON sends a JSON response with the specified status code. - JSON(statusCode int, obj interface{}) - // String sends a formatted string response with the specified status code. - String(statusCode int, format string, values ...interface{}) - // Param retrieves a route parameter by name. - Param(key string) string - // Query retrieves a query parameter by name. - Query(key string) string - // ShouldBindJSON parses the request body as JSON into the given object. - ShouldBindJSON(obj interface{}) error - // ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). - ShouldBindBodyWithJSON(obj any) error - // ShouldBindBodyWithXML is a shortcut for c.ShouldBindBodyWith(obj, binding.XML). - ShouldBindBodyWithXML(obj any) error - // ShouldBindBodyWithYAML is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML). - ShouldBindBodyWithYAML(obj any) error - // ShouldBindBodyWithTOML is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML). - ShouldBindBodyWithTOML(obj any) error - // Next executes the next handler in the middleware chain. - Next() - // Request returns the underlying HTTP request. - Request() *http.Request - // ClientIP returns the client's IP address. - ClientIP() string - // SetHeader sets a header in the response. - SetHeader(key, value string) - // Get retrieves a value from the context by key. - Get(key string) (value any, exists bool) - // Set sets a key-value pair in the context. - Set(key string, value any) - // AbortWithStatus aborts the request with the specified status code. - AbortWithStatus(code int) - // Abort aborts the request. - Abort() -} - -// Router defines the core HTTP routing interface. -type Router interface { - GET(route string, handler HandlerFunc) - POST(route string, handler HandlerFunc) - PUT(route string, handler HandlerFunc) - PATCH(route string, handler HandlerFunc) - DELETE(route string, handler HandlerFunc) - OPTIONS(route string, handler HandlerFunc) - HEAD(route string, handler HandlerFunc) -} - -// RouterGroup abstracts route grouping; it embeds Router. -type RouterGroup interface { - Router - // Group creates a new RouterGroup with the given prefix. - Group(prefix string) RouterGroup - Use(middleware ...HandlerFunc) -} - -// Server defines the abstraction for the web server. -type HTTPServer interface { - RouterGroup - transport.Server -} - -// Routable is an optional interface that modules can implement to register HTTP routes. -type Routable interface { - RegisterRoutes(router RouterGroup) -}