From 4c132c4abfe6fd6c650b9ddbaca84dcb2205d3f2 Mon Sep 17 00:00:00 2001 From: Matthew Stobbs Date: Sat, 23 Mar 2024 23:59:56 -0600 Subject: [PATCH] removed chi router - switched to go 1.22 net/http ServeMux for routing - seems to be working really well --- public/css/style.css | 4 -- router/htmx/htmx.go | 46 ++++++------ router/htmx/routes.go | 123 +++++++++++++++----------------- router/middleware/fromchi.go | 2 +- router/middleware/logger.go | 1 + router/middleware/middleware.go | 7 ++ router/middleware/nocache.go | 22 ++++++ router/router.go | 6 +- router/server/root.go | 6 +- router/server/server.go | 41 +++++++---- 10 files changed, 152 insertions(+), 106 deletions(-) create mode 100644 router/middleware/middleware.go create mode 100644 router/middleware/nocache.go diff --git a/public/css/style.css b/public/css/style.css index c11cd8a..6e42746 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -962,10 +962,6 @@ span>a:visited { flex-wrap: nowrap; } -.items-end { - align-items: flex-end; -} - .items-center { align-items: center; } diff --git a/router/htmx/htmx.go b/router/htmx/htmx.go index 3a480e7..5abe647 100644 --- a/router/htmx/htmx.go +++ b/router/htmx/htmx.go @@ -9,46 +9,52 @@ import ( "git.staur.ca/stobbsm/clustvirt/cluster" "git.staur.ca/stobbsm/clustvirt/lib/log" "git.staur.ca/stobbsm/clustvirt/router" - "github.com/go-chi/chi/v5" ) -type htmx struct { - router chi.Router - routes []router.Route -} +type htmx []router.Route -func (h *htmx) MountTo(c *cluster.Cluster, rr chi.Router) error { +func prefix(b, p string) string { return fmt.Sprintf("%s%s", b, p)} +func trace(p string) string { return fmt.Sprintf("TRACE %s", p) } +func options(p string) string { return fmt.Sprintf("OPTIONS %s", p) } +func connect(p string) string { return fmt.Sprintf("CONNECT %s", p) } +func head(p string) string { return fmt.Sprintf("HEAD %s", p) } +func get(p string) string { return fmt.Sprintf("GET %s", p) } +func post(p string) string { return fmt.Sprintf("POST %s", p) } +func put(p string) string { return fmt.Sprintf("PUT %s", p) } +func patch(p string) string { return fmt.Sprintf("PATCH %s", p) } +func delete(p string) string { return fmt.Sprintf("DELETE %s", p) } + +func (h htmx) Prefix() string { return "/htmx" } + +func (h htmx) MountTo(c *cluster.Cluster, mux *http.ServeMux) error { var errs []error - for _, r := range h.routes { + for _, r := range h { switch r.Method { case http.MethodTrace: - h.router.Trace(r.Path, r.Handler(c)) + mux.Handle(trace(prefix(h.Prefix(), r.Path)), r.Handler(c)) case http.MethodOptions: - h.router.Options(r.Path, r.Handler(c)) + mux.Handle(options(prefix(h.Prefix(), r.Path)), r.Handler(c)) case http.MethodConnect: - h.router.Connect(r.Path, r.Handler(c)) + mux.Handle(connect(prefix(h.Prefix(), r.Path)), r.Handler(c)) case http.MethodHead: - h.router.Head(r.Path, r.Handler(c)) + mux.Handle(head(prefix(h.Prefix(), r.Path)), r.Handler(c)) case http.MethodGet: - h.router.Get(r.Path, r.Handler(c)) + mux.Handle(get(prefix(h.Prefix(), r.Path)), r.Handler(c)) case http.MethodPost: - h.router.Post(r.Path, r.Handler(c)) + mux.Handle(post(prefix(h.Prefix(), r.Path)), r.Handler(c)) case http.MethodPut: - h.router.Put(r.Path, r.Handler(c)) + mux.Handle(put(prefix(h.Prefix(), r.Path)), r.Handler(c)) case http.MethodPatch: - h.router.Patch(r.Path, r.Handler(c)) + mux.Handle(patch(prefix(h.Prefix(), r.Path)), r.Handler(c)) case http.MethodDelete: - h.router.Delete(r.Path, r.Handler(c)) + mux.Handle(delete(prefix(h.Prefix(), r.Path)), r.Handler(c)) default: - err := fmt.Errorf("method: %s: %w", r.Method, router.ErrMethodNotExist) - errs = append(errs, err) - continue + mux.Handle(prefix(h.Prefix(), r.Path), r.Handler(c)) } log.Info("htmx.MoutnTo"). Str("Route.Path", r.Path). Str("Route.Method", r.Method). Msg("route registered") } - rr.Mount("/htmx", h.router) return errors.Join(errs...) } diff --git a/router/htmx/routes.go b/router/htmx/routes.go index cad477c..32cfaf8 100644 --- a/router/htmx/routes.go +++ b/router/htmx/routes.go @@ -6,78 +6,73 @@ import ( "git.staur.ca/stobbsm/clustvirt/cluster" "git.staur.ca/stobbsm/clustvirt/lib/log" - "git.staur.ca/stobbsm/clustvirt/router" "git.staur.ca/stobbsm/clustvirt/view" - "github.com/go-chi/chi/v5" ) -var Htmx = &htmx{ - router: chi.NewRouter(), - routes: []router.Route{ - { - Method: http.MethodGet, - Path: "/cluster", - Handler: func(c *cluster.Cluster) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if err := view.ClusterInfo(c).Render(context.Background(), w); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - log.Error("htmx.Handler"). - Str("uri", r.RequestURI). - Err(err). - Int("statusCode", http.StatusBadRequest).Send() - } +var Htmx = htmx{ + { + Method: http.MethodGet, + Path: "/cluster", + Handler: func(c *cluster.Cluster) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := view.ClusterInfo(c).Render(context.Background(), w); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + log.Error("htmx.Handler"). + Str("uri", r.RequestURI). + Err(err). + Int("statusCode", http.StatusBadRequest).Send() } - }, + } }, - { - Method: http.MethodGet, - Path: "/host/{hostname}", - Handler: func(c *cluster.Cluster) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - host, err := c.GetHost(chi.URLParam(r, "hostname")) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - log.Error("htmx.Handler"). - Str("uri", r.RequestURI). - Err(err). - Int("statusCode", http.StatusBadRequest). - Send() - } - if err = view.HostInfo(host).Render(context.Background(), w); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - log.Error("htmx.Handler"). - Str("uri", r.RequestURI). - Err(err). - Int("statusCode", http.StatusInternalServerError). - Send() - } + }, + { + Method: http.MethodGet, + Path: "/host/{hostname}", + Handler: func(c *cluster.Cluster) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + host, err := c.GetHost(r.PathValue("hostname")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + log.Error("htmx.Handler"). + Str("uri", r.RequestURI). + Err(err). + Int("statusCode", http.StatusBadRequest). + Send() } - }, + if err = view.HostInfo(host).Render(context.Background(), w); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Error("htmx.Handler"). + Str("uri", r.RequestURI). + Err(err). + Int("statusCode", http.StatusInternalServerError). + Send() + } + } }, - { - Method: http.MethodGet, - Path: "/host/{hostname}/stats", - Handler: func(c *cluster.Cluster) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - host, err := c.GetHost(chi.URLParam(r, "hostname")) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - log.Error("htmx.Handler"). - Str("uri", r.RequestURI). - Err(err). - Int("statusCode", http.StatusBadRequest). - Send() - } - if err = view.HostStats(host).Render(context.Background(), w); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - log.Error("htmx.Handler"). - Str("uri", r.RequestURI). - Err(err). - Int("statusCode", http.StatusInternalServerError). - Send() - } + }, + { + Method: http.MethodGet, + Path: "/host/{hostname}/stats", + Handler: func(c *cluster.Cluster) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + host, err := c.GetHost(r.PathValue("hostname")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + log.Error("htmx.Handler"). + Str("uri", r.RequestURI). + Err(err). + Int("statusCode", http.StatusBadRequest). + Send() } - }, + if err = view.HostStats(host).Render(context.Background(), w); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Error("htmx.Handler"). + Str("uri", r.RequestURI). + Err(err). + Int("statusCode", http.StatusInternalServerError). + Send() + } + } }, }, } diff --git a/router/middleware/fromchi.go b/router/middleware/fromchi.go index 3297203..934aa9c 100644 --- a/router/middleware/fromchi.go +++ b/router/middleware/fromchi.go @@ -9,7 +9,7 @@ var ( BasicAuth = middleware.BasicAuth Compress = middleware.Compress Heartbeat = middleware.Heartbeat - NoCache = middleware.NoCache + //NoCache = middleware.NoCache Profiler = middleware.Profiler RealIP = middleware.RealIP Recoverer = middleware.Recoverer diff --git a/router/middleware/logger.go b/router/middleware/logger.go index e8a517c..c52f0b5 100644 --- a/router/middleware/logger.go +++ b/router/middleware/logger.go @@ -9,6 +9,7 @@ import ( // Logger uses the in package log module to handle route logging func Logger(next http.Handler) http.Handler { + log.Info("logger.Logger").Str("middleware", "Logger").Msg("middleware loaded") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() defer func() { diff --git a/router/middleware/middleware.go b/router/middleware/middleware.go new file mode 100644 index 0000000..3f81324 --- /dev/null +++ b/router/middleware/middleware.go @@ -0,0 +1,7 @@ +package middleware + +import "net/http" + +// Middleware is a function that is meant to be chained with other functions +// through an http request lifecycle +type Middleware func(http.Handler) http.Handler diff --git a/router/middleware/nocache.go b/router/middleware/nocache.go new file mode 100644 index 0000000..e55f419 --- /dev/null +++ b/router/middleware/nocache.go @@ -0,0 +1,22 @@ +package middleware + +import ( + "net/http" + "time" + + "git.staur.ca/stobbsm/clustvirt/lib/log" +) + +// NoCache adds headers indicating the browser shouldn't cache +// any of the responses. Useful for debugging, should not be used +// on production +func NoCache(next http.Handler) http.Handler { + log.Info("router.middleware").Str("middleware", "NoCache").Msg("middleware loaded") + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Cache-Control", "no-cache, no-store, no-transform, must-revalidate, private, max-age=0") + w.Header().Add("Expires", time.Unix(0, 0).UTC().Format(http.TimeFormat)) + w.Header().Add("Pragma", "no-cache") + + next.ServeHTTP(w, r) + }) +} diff --git a/router/router.go b/router/router.go index 1f81aa0..5f1d75a 100644 --- a/router/router.go +++ b/router/router.go @@ -4,7 +4,6 @@ import ( "net/http" "git.staur.ca/stobbsm/clustvirt/cluster" - "github.com/go-chi/chi/v5" ) // Types that are shared among routers @@ -12,7 +11,10 @@ import ( // SubRouter defines an interface to be able to add a subrouter to a // chi router type SubRouter interface { - MountTo(*cluster.Cluster, chi.Router) error + // MountTo needs the cluster handle and the mux to add to + MountTo(*cluster.Cluster, *http.ServeMux) error + // Prefix returns the path prefix + Prefix() string } // Route defines a route that should be added to a chi router or diff --git a/router/server/root.go b/router/server/root.go index f1d4ed7..8f3450d 100644 --- a/router/server/root.go +++ b/router/server/root.go @@ -13,9 +13,9 @@ import ( func (s *Server) baseRoutes() { // fileserver for static content fs := http.StripPrefix("/static", http.FileServer(http.Dir("public"))) - s.r.Get("/static/*", fs.ServeHTTP) + s.mux.Handle("GET /static/*", fs) // Root defaults to the cluster view - s.r.Get("/", templ.Handler(layouts.Manager("ClustVirt", "libvirt made simple")).ServeHTTP) - s.r.Get("/about", templ.Handler(static.About()).ServeHTTP) + s.mux.Handle("GET /", templ.Handler(layouts.Manager("ClustVirt", "libvirt made simple"))) + s.mux.Handle("GET /about", templ.Handler(static.About())) } diff --git a/router/server/server.go b/router/server/server.go index 8ac34c8..c80917a 100644 --- a/router/server/server.go +++ b/router/server/server.go @@ -12,15 +12,18 @@ import ( "git.staur.ca/stobbsm/clustvirt/router" "git.staur.ca/stobbsm/clustvirt/router/htmx" "git.staur.ca/stobbsm/clustvirt/router/middleware" - "github.com/go-chi/chi/v5" ) -// Server represents an HTTP server that uses chi for routes +// Server represents an HTTP server that uses net/http ServeMux to route requests +// Originally done with chi, but a migration to the new net/http patterns seems like +// a good choice to reduce dependencies, but does mean middleware needs to be implemented +// in the server type Server struct { - bindAddr string - ssl bool - c *cluster.Cluster - r chi.Router + bindAddr string + ssl bool + c *cluster.Cluster + middleware []middleware.Middleware + mux *http.ServeMux } // New creates a new HTTP Server instance. @@ -39,7 +42,7 @@ func (s *Server) Start() { Dur("upTime", time.Since(tstart)). Msg("http server stopped") }() - s.r = chi.NewRouter() + s.mux = http.NewServeMux() indev, _ := os.LookupEnv("CLUSTVIRT_DEV") indev = strings.ToLower(indev) @@ -52,8 +55,9 @@ func (s *Server) Start() { fallthrough case "on": s.AddMiddleware(middleware.NoCache) - s.r.Get("/_/debug", middleware.Profiler().ServeHTTP) + s.mux.Handle("GET /_/debug", middleware.Profiler()) } + s.AddMiddleware(middleware.Logger) // Add routes s.baseRoutes() if err := s.AddSubRouter(htmx.Htmx); err != nil { @@ -62,7 +66,7 @@ func (s *Server) Start() { Err(err).Send() } // Start the server - if err := http.ListenAndServe(s.bindAddr, s.r); err != nil { + if err := http.ListenAndServe(s.bindAddr, s); err != nil { log.Error("router.Server.Start"). Str("bindaddr", s.bindAddr). Err(err).Send() @@ -70,11 +74,24 @@ func (s *Server) Start() { } // AddMiddleware appends middleware to the chi router -func (s *Server) AddMiddleware(m ...func(h http.Handler) http.Handler) { - s.r.Use(m...) +func (s *Server) AddMiddleware(m ...middleware.Middleware) { + s.middleware = append(s.middleware, m...) } // AddSubRouter attachs a SubRouter using it's MountTo method func (s *Server) AddSubRouter(sr router.SubRouter) error { - return sr.MountTo(s.c, s.r) + return sr.MountTo(s.c, s.mux) +} + +// ServeHTTP implements the Handler interface +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.chain().ServeHTTP(w, r) +} + +func (s *Server) chain() http.Handler { + h := http.Handler(s.mux) + for i := range s.middleware { + h = s.middleware[len(s.middleware)-1-i](h) + } + return h }