removed chi router

- switched to go 1.22 net/http ServeMux for routing
- seems to be working really well
This commit is contained in:
Matthew Stobbs 2024-03-23 23:59:56 -06:00
parent 8e62f7e271
commit 4c132c4abf
10 changed files with 152 additions and 106 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

@ -9,46 +9,52 @@ import (
"git.staur.ca/stobbsm/clustvirt/cluster" "git.staur.ca/stobbsm/clustvirt/cluster"
"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"
"github.com/go-chi/chi/v5"
) )
type htmx struct { type htmx []router.Route
router chi.Router
routes []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 var errs []error
for _, r := range h.routes { for _, r := range h {
switch r.Method { switch r.Method {
case http.MethodTrace: 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: 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: 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: 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: 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: 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: 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: 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: case http.MethodDelete:
h.router.Delete(r.Path, r.Handler(c)) mux.Handle(delete(prefix(h.Prefix(), r.Path)), r.Handler(c))
default: default:
err := fmt.Errorf("method: %s: %w", r.Method, router.ErrMethodNotExist) mux.Handle(prefix(h.Prefix(), r.Path), r.Handler(c))
errs = append(errs, err)
continue
} }
log.Info("htmx.MoutnTo"). log.Info("htmx.MoutnTo").
Str("Route.Path", r.Path). Str("Route.Path", r.Path).
Str("Route.Method", r.Method). Str("Route.Method", r.Method).
Msg("route registered") Msg("route registered")
} }
rr.Mount("/htmx", h.router)
return errors.Join(errs...) return errors.Join(errs...)
} }

View File

@ -6,78 +6,73 @@ import (
"git.staur.ca/stobbsm/clustvirt/cluster" "git.staur.ca/stobbsm/clustvirt/cluster"
"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/view" "git.staur.ca/stobbsm/clustvirt/view"
"github.com/go-chi/chi/v5"
) )
var Htmx = &htmx{ var Htmx = htmx{
router: chi.NewRouter(), {
routes: []router.Route{ Method: http.MethodGet,
{ Path: "/cluster",
Method: http.MethodGet, Handler: func(c *cluster.Cluster) http.HandlerFunc {
Path: "/cluster", return func(w http.ResponseWriter, r *http.Request) {
Handler: func(c *cluster.Cluster) http.HandlerFunc { if err := view.ClusterInfo(c).Render(context.Background(), w); err != nil {
return func(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusBadRequest)
if err := view.ClusterInfo(c).Render(context.Background(), w); err != nil { log.Error("htmx.Handler").
http.Error(w, err.Error(), http.StatusBadRequest) Str("uri", r.RequestURI).
log.Error("htmx.Handler"). Err(err).
Str("uri", r.RequestURI). Int("statusCode", http.StatusBadRequest).Send()
Err(err).
Int("statusCode", http.StatusBadRequest).Send()
}
} }
}, }
}, },
{ },
Method: http.MethodGet, {
Path: "/host/{hostname}", Method: http.MethodGet,
Handler: func(c *cluster.Cluster) http.HandlerFunc { Path: "/host/{hostname}",
return func(w http.ResponseWriter, r *http.Request) { Handler: func(c *cluster.Cluster) http.HandlerFunc {
host, err := c.GetHost(chi.URLParam(r, "hostname")) return func(w http.ResponseWriter, r *http.Request) {
if err != nil { host, err := c.GetHost(r.PathValue("hostname"))
http.Error(w, err.Error(), http.StatusBadRequest) if err != nil {
log.Error("htmx.Handler"). http.Error(w, err.Error(), http.StatusBadRequest)
Str("uri", r.RequestURI). log.Error("htmx.Handler").
Err(err). Str("uri", r.RequestURI).
Int("statusCode", http.StatusBadRequest). Err(err).
Send() 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()
}
} }
}, 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", Method: http.MethodGet,
Handler: func(c *cluster.Cluster) http.HandlerFunc { Path: "/host/{hostname}/stats",
return func(w http.ResponseWriter, r *http.Request) { Handler: func(c *cluster.Cluster) http.HandlerFunc {
host, err := c.GetHost(chi.URLParam(r, "hostname")) return func(w http.ResponseWriter, r *http.Request) {
if err != nil { host, err := c.GetHost(r.PathValue("hostname"))
http.Error(w, err.Error(), http.StatusBadRequest) if err != nil {
log.Error("htmx.Handler"). http.Error(w, err.Error(), http.StatusBadRequest)
Str("uri", r.RequestURI). log.Error("htmx.Handler").
Err(err). Str("uri", r.RequestURI).
Int("statusCode", http.StatusBadRequest). Err(err).
Send() 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()
}
} }
}, 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()
}
}
}, },
}, },
} }

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,10 @@ 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 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 // Route defines a route that should be added to a chi router or

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,8 +55,9 @@ 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); err != nil {
@ -62,7 +66,7 @@ func (s *Server) Start() {
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,24 @@ 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
func (s *Server) AddSubRouter(sr router.SubRouter) error { 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
} }