package tonic import ( "context" "fmt" "net/http" "time" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/go-logr/logr" ) const ( // defaultPort is the default port for the http server. defaultPort = "9003" // MethodAny http method for catch all route. MethodAny = "ANY" ) var ( // NoRoute http route for not found (404). NoRoute = Route{ Path: "", Action: "", } // ReadyRoute http ready route. ReadyRoute = Route{ Path: "/ready", Action: MethodAny, Handlers: []gin.HandlerFunc{func(c *gin.Context) { c.String(http.StatusOK, "ok") }}, } // HealthRoute http health route. HealthRoute = Route{ Path: "/health", Action: MethodAny, Handlers: []gin.HandlerFunc{func(c *gin.Context) { c.String(http.StatusOK, "ok") }}, } ) // Server represents a http server that is run using the runnable lib. type Server struct { port string mode string routes []Route middlewares []gin.HandlerFunc router *gin.Engine server *http.Server logger logr.Logger } // Route represents a http route. type Route struct { // Path is the http route path e.g. /login Path string // Action is the http route action e.g. POST Action string // Handlers is the list of gin handler funcs called when the route is accessed. Handlers []gin.HandlerFunc } // New returns a new instance of the Server with defaults. func New() *Server { return NewWithOptions(defaultPort, gin.ReleaseMode) } // NewWithOptions returns a new instance of Server with the specified options. func NewWithOptions(port string, mode string) *Server { return &Server{ mode: mode, port: port, routes: make([]Route, 0), } } // SetRoutes sets the routes that can be accessed in the http server. func (b *Server) SetRoutes(routes ...Route) *Server { b.routes = append(b.routes, routes...) return b } // SetMiddlewares sets the middleware for the http server. func (b *Server) SetMiddlewares(middlewares ...gin.HandlerFunc) *Server { b.middlewares = middlewares return b } // Start runs the gin server. func (b *Server) Start(_ context.Context) error { gin.SetMode(b.mode) gin.DisableConsoleColor() b.router = gin.New() b.registerMiddlewares() b.registerRoutes() b.registerServer() b.logger.Info("running http server", "port", b.port, "mode", b.mode) return b.server.ListenAndServe() } // SetLogger sets the logger. func (b *Server) SetLogger(logger logr.Logger) *Server { b.logger = logger return b } // registerMiddlewares registers the middlewares to the gin server. func (b *Server) registerMiddlewares() { for _, middleware := range b.middlewares { b.router.Use(middleware) } } // registerRoutes registers the routes to the gin server. func (b *Server) registerRoutes() { for _, route := range b.routes { switch route.Action { case http.MethodGet: b.router.GET(route.Path, route.Handlers...) case http.MethodPost: b.router.POST(route.Path, route.Handlers...) case http.MethodPatch: b.router.PATCH(route.Path, route.Handlers...) case http.MethodDelete: b.router.DELETE(route.Path, route.Handlers...) case http.MethodPut: b.router.PUT(route.Path, route.Handlers...) case http.MethodHead: b.router.HEAD(route.Path, route.Handlers...) case http.MethodOptions: b.router.OPTIONS(route.Path, route.Handlers...) case MethodAny: b.router.Any(route.Path, route.Handlers...) default: if route.Path == "" { b.router.NoRoute(func(c *gin.Context) { c.AbortWithStatus(404) }) } } } } // registerServer registers the http server. func (b *Server) registerServer() { b.server = &http.Server{ Addr: fmt.Sprintf(":%s", b.port), Handler: b.router, ReadHeaderTimeout: 60 * time.Second, } } // WithHealthRoute adds the default health route to the server. func (b *Server) WithHealthRoute() *Server { b.routes = append(b.routes, HealthRoute) return b } // WithReadyRoute adds the default ready route to the server. func (b *Server) WithReadyRoute() *Server { b.routes = append(b.routes, ReadyRoute) return b } // With404Route adds a not found (404) route to the server. func (b *Server) With404Route() *Server { b.routes = append(b.routes, NoRoute) return b } // CorsMiddleware returns an initialized cors middleware. func CorsMiddleware() gin.HandlerFunc { corsConfig := cors.DefaultConfig() corsConfig.AllowAllOrigins = true corsConfig.AllowHeaders = []string{"*"} corsConfig.AllowCredentials = true return cors.New(corsConfig) }