From 5dfffc496b3b424e10da4345aeb5f7d3b9455455 Mon Sep 17 00:00:00 2001 From: Matthew Stobbs Date: Mon, 25 Mar 2024 15:33:04 -0600 Subject: [PATCH] merge migrate_chi_to_new_nethttp no longer using chi for routing --- public/css/style.css | 4 -- router/htmx/htmx.go | 52 -------------- router/htmx/routes.go | 122 +++++++++++++++----------------- router/middleware/fromchi.go | 2 +- router/middleware/logger.go | 1 + router/middleware/middleware.go | 7 ++ router/middleware/nocache.go | 22 ++++++ router/router.go | 4 +- router/routes.go | 57 +++++++++++++++ router/server/root.go | 6 +- router/server/server.go | 48 +++++++++---- 11 files changed, 185 insertions(+), 140 deletions(-) create mode 100644 router/middleware/middleware.go create mode 100644 router/middleware/nocache.go create mode 100644 router/routes.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..c2489ee 100644 --- a/router/htmx/htmx.go +++ b/router/htmx/htmx.go @@ -1,54 +1,2 @@ // Package htmx contains the routes for the WebUI HTMX package htmx - -import ( - "errors" - "fmt" - "net/http" - - "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 -} - -func (h *htmx) MountTo(c *cluster.Cluster, rr chi.Router) error { - var errs []error - for _, r := range h.routes { - switch r.Method { - case http.MethodTrace: - h.router.Trace(r.Path, r.Handler(c)) - case http.MethodOptions: - h.router.Options(r.Path, r.Handler(c)) - case http.MethodConnect: - h.router.Connect(r.Path, r.Handler(c)) - case http.MethodHead: - h.router.Head(r.Path, r.Handler(c)) - case http.MethodGet: - h.router.Get(r.Path, r.Handler(c)) - case http.MethodPost: - h.router.Post(r.Path, r.Handler(c)) - case http.MethodPut: - h.router.Put(r.Path, r.Handler(c)) - case http.MethodPatch: - h.router.Patch(r.Path, r.Handler(c)) - case http.MethodDelete: - h.router.Delete(r.Path, r.Handler(c)) - default: - err := fmt.Errorf("method: %s: %w", r.Method, router.ErrMethodNotExist) - errs = append(errs, err) - continue - } - 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..cb52b8b 100644 --- a/router/htmx/routes.go +++ b/router/htmx/routes.go @@ -8,76 +8,72 @@ import ( "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 = router.Routes{ + { + 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..1f46893 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,8 @@ 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 path prefix, cluster handle and the mux to add to + MountTo(string, *cluster.Cluster, *http.ServeMux) error } // Route defines a route that should be added to a chi router or diff --git a/router/routes.go b/router/routes.go new file mode 100644 index 0000000..badc8f9 --- /dev/null +++ b/router/routes.go @@ -0,0 +1,57 @@ +package router + +import ( + "errors" + "fmt" + "net/http" + + "git.staur.ca/stobbsm/clustvirt/cluster" + "git.staur.ca/stobbsm/clustvirt/lib/log" +) + +type Routes []Route + +func addprefix(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 (rte Routes) MountTo(prefix string, c *cluster.Cluster, mux *http.ServeMux) error { + var errs []error + for _, r := range rte { + switch r.Method { + case http.MethodTrace: + mux.Handle(trace(addprefix(prefix, r.Path)), r.Handler(c)) + case http.MethodOptions: + mux.Handle(options(addprefix(prefix, r.Path)), r.Handler(c)) + case http.MethodConnect: + mux.Handle(connect(addprefix(prefix, r.Path)), r.Handler(c)) + case http.MethodHead: + mux.Handle(head(addprefix(prefix, r.Path)), r.Handler(c)) + case http.MethodGet: + mux.Handle(get(addprefix(prefix, r.Path)), r.Handler(c)) + case http.MethodPost: + mux.Handle(post(addprefix(prefix, r.Path)), r.Handler(c)) + case http.MethodPut: + mux.Handle(put(addprefix(prefix, r.Path)), r.Handler(c)) + case http.MethodPatch: + mux.Handle(patch(addprefix(prefix, r.Path)), r.Handler(c)) + case http.MethodDelete: + mux.Handle(delete(addprefix(prefix, r.Path)), r.Handler(c)) + default: + mux.Handle(addprefix(prefix, r.Path), r.Handler(c)) + } + log.Info("Routes.MoutnTo"). + Str("prefix", prefix). + Str("Route.Path", r.Path). + Str("Route.Method", r.Method). + Msg("route registered") + } + return errors.Join(errs...) +} 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..375aac5 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,17 +55,18 @@ 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 { + if err := s.AddSubRouter("/htmx", htmx.Htmx); err != nil { log.Error("server.Start"). Str("subroute", "htmx"). 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,25 @@ 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) +// AddSubRouter attachs a SubRouter using it's MountTo method. This method +// needs the path prefix and the defined routes +func (s *Server) AddSubRouter(pfx string, sr router.SubRouter) error { + return sr.MountTo(pfx, 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 }