From 3536b4338b0953349dc25ab913c1b6586ee7065e Mon Sep 17 00:00:00 2001 From: Levente Batuska Date: Tue, 22 Oct 2024 00:21:44 +0200 Subject: [PATCH] feat(SimpleRouter): initial version --- simplerouter/router.go | 155 +++++++++++++++++++++++++++++++++++++++++ simplerouter/types.go | 11 +++ 2 files changed, 166 insertions(+) create mode 100644 simplerouter/router.go create mode 100644 simplerouter/types.go diff --git a/simplerouter/router.go b/simplerouter/router.go new file mode 100644 index 0000000..0c19614 --- /dev/null +++ b/simplerouter/router.go @@ -0,0 +1,155 @@ +package SimpleRouter + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +// Use this router for graceful shutdown +func (rg *RouteGroup) StartServer(addr string) *http.Server { + server := &http.Server{ + Addr: addr, + Handler: rg, + } + + go func() { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + panic(fmt.Sprintf("HTTP server error: %v", err)) + } + }() + + return server +} + +func (rg *RouteGroup) StartWithGracefulShutdown(addr string) { + server := &http.Server{ + Addr: addr, + Handler: rg, // This will use your RouteGroup (with ServeMux) as the handler + } + + // Start the server in a separate goroutine + go func() { + fmt.Println("Starting server on", addr) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + panic(fmt.Sprintf("HTTP server error: %v", err)) + } + }() + + // Set up signal catching for graceful shutdown (Ctrl+C, SIGINT, SIGTERM) + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + // Block until a signal is received + <-sigChan + + fmt.Println("Shutting down server...") + + // Set a timeout for the shutdown process (10 seconds) + shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) + defer shutdownRelease() + + // Perform graceful shutdown + if err := server.Shutdown(shutdownCtx); err != nil { + panic(fmt.Sprintf("HTTP shutdown error: %v", err)) + } + + fmt.Println("Graceful shutdown complete.") +} + +// http.ListenAndServe takes a Handler interface defined as: ServeHTTP(ResponseWriter, *Request) +func (rg *RouteGroup) ServeHTTP(w http.ResponseWriter, r *http.Request) { + rg.mux.ServeHTTP(w, r) +} + +// / Handler returns the handler to use for the given request, ... propagate call to http library +func (rg *RouteGroup) Handler(r *http.Request) (h http.Handler, pattern string) { + return rg.mux.Handler(r) +} + +// Creates a new SimpleRouter with the basePath of "/" +func SimpleRouter() *RouteGroup { + return &RouteGroup{mux: http.NewServeMux(), basePath: "/"} +} + +// Puts middlewares on top of existing ones in order +func (rg *RouteGroup) PushMiddleware(first Middleware, others ...Middleware) *RouteGroup { + rg.middlewares = append(rg.middlewares, first) + // append supposedly keeps the order of elements, so no extra work is needed + rg.middlewares = append(rg.middlewares, others...) + return rg +} + +// Removes the top middleware and returns it to the caller +func (rg *RouteGroup) PopMiddleware() Middleware { + if len(rg.middlewares) == 0 { + return nil + } + + lastIndex := len(rg.middlewares) - 1 + lastMiddleware := rg.middlewares[lastIndex] + rg.middlewares = rg.middlewares[:lastIndex] + + return lastMiddleware +} + +// applies the array of middlewares on the handler +func (rg *RouteGroup) applyMiddlewares(handler http.Handler) http.Handler { + mwlen := len(rg.middlewares) - 1 // We need to start from the last element going to 0 for correct ordering + for i := range rg.middlewares { + handler = rg.middlewares[mwlen-i](handler) + } + return handler +} + +func (rg *RouteGroup) registerRoute(method string, path string, handler http.HandlerFunc) { + if path == "/" { + path = rg.basePath[:len(rg.basePath)-1] + } else { + path = rg.basePath + path + } + + fullPath := method + " " + path + + rg.mux.HandleFunc(fullPath, rg.applyMiddlewares(handler).ServeHTTP) +} + +func (rg *RouteGroup) HandleFunc(method string, path string, handler http.HandlerFunc) { + rg.registerRoute(method, path, handler) +} + +func (rg *RouteGroup) Handle(method string, path string, handler http.Handler) { + rg.HandleFunc(method, path, handler.ServeHTTP) +} + +func (rg *RouteGroup) GET(path string, handler http.Handler) { + rg.Handle(http.MethodGet, path, handler) +} + +func (rg *RouteGroup) HEAD(path string, handler http.Handler) { + rg.Handle(http.MethodHead, path, handler) +} + +func (rg *RouteGroup) OPTIONS(path string, handler http.Handler) { + rg.Handle(http.MethodOptions, path, handler) +} + +func (rg *RouteGroup) POST(path string, handler http.Handler) { + rg.Handle(http.MethodPost, path, handler) +} + +func (rg *RouteGroup) PUT(path string, handler http.Handler) { + rg.Handle(http.MethodPut, path, handler) +} + +func (rg *RouteGroup) PATCH(path string, handler http.Handler) { + rg.Handle(http.MethodPatch, path, handler) +} + +func (rg *RouteGroup) DELETE(path string, handler http.Handler) { + rg.Handle(http.MethodDelete, path, handler) +} diff --git a/simplerouter/types.go b/simplerouter/types.go new file mode 100644 index 0000000..c17c6f2 --- /dev/null +++ b/simplerouter/types.go @@ -0,0 +1,11 @@ +package SimpleRouter + +import "net/http" + +type Middleware func(http.Handler) http.Handler + +type RouteGroup struct { + mux *http.ServeMux // A ponter to the underlying ServeMux, this allows us to call ListenAndServe on any instance + basePath string // The current path we are defining handlers on / appending to + middlewares []Middleware // Stack of middlewares that will be applied on a handler in order +}