merge migrate_chi_to_new_nethttp

no longer using chi for routing
This commit is contained in:
Matthew Stobbs 2024-03-25 15:33:04 -06:00
parent 8e62f7e271
commit 5dfffc496b
11 changed files with 185 additions and 140 deletions

View File

@ -962,10 +962,6 @@ span>a:visited {
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.items-end {
align-items: flex-end;
}
.items-center { .items-center {
align-items: center; align-items: center;
} }

View File

@ -1,54 +1,2 @@
// Package htmx contains the routes for the WebUI HTMX // Package htmx contains the routes for the WebUI HTMX
package 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...)
}

View File

@ -8,12 +8,9 @@ import (
"git.staur.ca/stobbsm/clustvirt/lib/log" "git.staur.ca/stobbsm/clustvirt/lib/log"
"git.staur.ca/stobbsm/clustvirt/router" "git.staur.ca/stobbsm/clustvirt/router"
"git.staur.ca/stobbsm/clustvirt/view" "git.staur.ca/stobbsm/clustvirt/view"
"github.com/go-chi/chi/v5"
) )
var Htmx = &htmx{ var Htmx = router.Routes{
router: chi.NewRouter(),
routes: []router.Route{
{ {
Method: http.MethodGet, Method: http.MethodGet,
Path: "/cluster", Path: "/cluster",
@ -34,7 +31,7 @@ var Htmx = &htmx{
Path: "/host/{hostname}", Path: "/host/{hostname}",
Handler: func(c *cluster.Cluster) http.HandlerFunc { Handler: func(c *cluster.Cluster) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
host, err := c.GetHost(chi.URLParam(r, "hostname")) host, err := c.GetHost(r.PathValue("hostname"))
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
log.Error("htmx.Handler"). log.Error("htmx.Handler").
@ -59,7 +56,7 @@ var Htmx = &htmx{
Path: "/host/{hostname}/stats", Path: "/host/{hostname}/stats",
Handler: func(c *cluster.Cluster) http.HandlerFunc { Handler: func(c *cluster.Cluster) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
host, err := c.GetHost(chi.URLParam(r, "hostname")) host, err := c.GetHost(r.PathValue("hostname"))
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
log.Error("htmx.Handler"). log.Error("htmx.Handler").
@ -79,5 +76,4 @@ var Htmx = &htmx{
} }
}, },
}, },
},
} }

View File

@ -9,7 +9,7 @@ var (
BasicAuth = middleware.BasicAuth BasicAuth = middleware.BasicAuth
Compress = middleware.Compress Compress = middleware.Compress
Heartbeat = middleware.Heartbeat Heartbeat = middleware.Heartbeat
NoCache = middleware.NoCache //NoCache = middleware.NoCache
Profiler = middleware.Profiler Profiler = middleware.Profiler
RealIP = middleware.RealIP RealIP = middleware.RealIP
Recoverer = middleware.Recoverer Recoverer = middleware.Recoverer

View File

@ -9,6 +9,7 @@ import (
// Logger uses the in package log module to handle route logging // Logger uses the in package log module to handle route logging
func Logger(next http.Handler) http.Handler { 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) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now() start := time.Now()
defer func() { defer func() {

View File

@ -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

View File

@ -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)
})
}

View File

@ -4,7 +4,6 @@ import (
"net/http" "net/http"
"git.staur.ca/stobbsm/clustvirt/cluster" "git.staur.ca/stobbsm/clustvirt/cluster"
"github.com/go-chi/chi/v5"
) )
// Types that are shared among routers // Types that are shared among routers
@ -12,7 +11,8 @@ import (
// SubRouter defines an interface to be able to add a subrouter to a // SubRouter defines an interface to be able to add a subrouter to a
// chi router // chi router
type SubRouter interface { 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 // Route defines a route that should be added to a chi router or

57
router/routes.go Normal file
View File

@ -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...)
}

View File

@ -13,9 +13,9 @@ import (
func (s *Server) baseRoutes() { func (s *Server) baseRoutes() {
// fileserver for static content // fileserver for static content
fs := http.StripPrefix("/static", http.FileServer(http.Dir("public"))) 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 // Root defaults to the cluster view
s.r.Get("/", templ.Handler(layouts.Manager("ClustVirt", "libvirt made simple")).ServeHTTP) s.mux.Handle("GET /", templ.Handler(layouts.Manager("ClustVirt", "libvirt made simple")))
s.r.Get("/about", templ.Handler(static.About()).ServeHTTP) s.mux.Handle("GET /about", templ.Handler(static.About()))
} }

View File

@ -12,15 +12,18 @@ import (
"git.staur.ca/stobbsm/clustvirt/router" "git.staur.ca/stobbsm/clustvirt/router"
"git.staur.ca/stobbsm/clustvirt/router/htmx" "git.staur.ca/stobbsm/clustvirt/router/htmx"
"git.staur.ca/stobbsm/clustvirt/router/middleware" "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 { type Server struct {
bindAddr string bindAddr string
ssl bool ssl bool
c *cluster.Cluster c *cluster.Cluster
r chi.Router middleware []middleware.Middleware
mux *http.ServeMux
} }
// New creates a new HTTP Server instance. // New creates a new HTTP Server instance.
@ -39,7 +42,7 @@ func (s *Server) Start() {
Dur("upTime", time.Since(tstart)). Dur("upTime", time.Since(tstart)).
Msg("http server stopped") Msg("http server stopped")
}() }()
s.r = chi.NewRouter() s.mux = http.NewServeMux()
indev, _ := os.LookupEnv("CLUSTVIRT_DEV") indev, _ := os.LookupEnv("CLUSTVIRT_DEV")
indev = strings.ToLower(indev) indev = strings.ToLower(indev)
@ -52,17 +55,18 @@ func (s *Server) Start() {
fallthrough fallthrough
case "on": case "on":
s.AddMiddleware(middleware.NoCache) s.AddMiddleware(middleware.NoCache)
s.r.Get("/_/debug", middleware.Profiler().ServeHTTP) s.mux.Handle("GET /_/debug", middleware.Profiler())
} }
s.AddMiddleware(middleware.Logger)
// Add routes // Add routes
s.baseRoutes() s.baseRoutes()
if err := s.AddSubRouter(htmx.Htmx); err != nil { if err := s.AddSubRouter("/htmx", htmx.Htmx); err != nil {
log.Error("server.Start"). log.Error("server.Start").
Str("subroute", "htmx"). Str("subroute", "htmx").
Err(err).Send() Err(err).Send()
} }
// Start the server // 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"). log.Error("router.Server.Start").
Str("bindaddr", s.bindAddr). Str("bindaddr", s.bindAddr).
Err(err).Send() Err(err).Send()
@ -70,11 +74,25 @@ func (s *Server) Start() {
} }
// AddMiddleware appends middleware to the chi router // AddMiddleware appends middleware to the chi router
func (s *Server) AddMiddleware(m ...func(h http.Handler) http.Handler) { func (s *Server) AddMiddleware(m ...middleware.Middleware) {
s.r.Use(m...) s.middleware = append(s.middleware, m...)
} }
// AddSubRouter attachs a SubRouter using it's MountTo method // AddSubRouter attachs a SubRouter using it's MountTo method. This method
func (s *Server) AddSubRouter(sr router.SubRouter) error { // needs the path prefix and the defined routes
return sr.MountTo(s.c, s.r) 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
} }