Seed from go-template-repo, renamed to simplexcalc
The template's files at a77fd30, without its history or LICENSE, after script/rename simplexcalc. Model: opus-5-5
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"sneak.berlin/go/simplexcalc/static"
|
||||
)
|
||||
|
||||
// staticPrefix is where the embedded assets are mounted.
|
||||
const staticPrefix = "/static/"
|
||||
|
||||
// staticCacheControl is a year, because the asset set is baked into the
|
||||
// binary: a new build is a new deployment, and a deployment is the only
|
||||
// thing that can change these bytes. Bump the path if that stops being
|
||||
// true.
|
||||
const staticCacheControl = "public, max-age=31536000, immutable"
|
||||
|
||||
// SetupRoutes builds the router. The middleware order is the contract
|
||||
// of this file, and it is this way for reasons:
|
||||
//
|
||||
// 1. RequestID first, so every later line of log and every captured
|
||||
// error can name the request it came from.
|
||||
// 2. Recoverer next-to-outermost, so it covers every handler and every
|
||||
// middleware below it. Above the logger, so a panic still produces
|
||||
// a logged request line.
|
||||
// 3. RequestLogger and Metrics before the work, so both see the final
|
||||
// status of every request including the 500 the recoverer wrote.
|
||||
// 4. SecurityHeaders before anything can write a body — the headers
|
||||
// have to be set before the first Write, and a 404 or a panic
|
||||
// response needs them as much as a page does.
|
||||
// 5. Timeout and BodyLimit before any handler reads a body.
|
||||
// 6. CSRF innermost of the global chain, wrapping only the routes that
|
||||
// can change state.
|
||||
//
|
||||
// /metrics and the healthcheck sit outside CSRF (neither is
|
||||
// state-changing, and a scraper has no token) and outside nothing else.
|
||||
func (s *Server) SetupRoutes() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(s.mw.RequestID())
|
||||
r.Use(s.mw.Recoverer())
|
||||
r.Use(s.mw.RequestLogger())
|
||||
r.Use(s.mw.Metrics())
|
||||
r.Use(s.mw.SecurityHeaders())
|
||||
r.Use(s.mw.Timeout())
|
||||
r.Use(s.mw.BodyLimit())
|
||||
|
||||
r.NotFound(s.h.NotFound())
|
||||
r.MethodNotAllowed(s.h.MethodNotAllowed())
|
||||
|
||||
// Operational endpoints: no CSRF, no session, no HTML.
|
||||
r.Get("/.well-known/healthcheck.json", s.h.Healthcheck())
|
||||
r.Method(http.MethodGet, "/metrics", s.metrics.Handler())
|
||||
|
||||
r.Handle(staticPrefix+"*", s.staticHandler())
|
||||
|
||||
// Everything a browser drives, behind CSRF. Safe methods are
|
||||
// unaffected by it except that they are issued a token.
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(s.mw.CSRF())
|
||||
|
||||
r.Get("/", s.h.Index())
|
||||
r.Post("/widgets", s.h.CreateWidget())
|
||||
|
||||
if s.params.Config.Debug {
|
||||
// Only with DEBUG=true: a route that panics on demand is a
|
||||
// denial-of-service primitive in production.
|
||||
r.Get("/debug/panic", s.h.Panic())
|
||||
}
|
||||
})
|
||||
|
||||
s.router = r
|
||||
}
|
||||
|
||||
// staticHandler serves the embedded assets, without directory listings
|
||||
// and with a long cache lifetime.
|
||||
func (s *Server) staticHandler() http.Handler {
|
||||
fileServer := http.FileServer(filesOnly{inner: http.FS(static.FS)})
|
||||
|
||||
return http.StripPrefix(staticPrefix, http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", staticCacheControl)
|
||||
fileServer.ServeHTTP(w, r)
|
||||
},
|
||||
))
|
||||
}
|
||||
Reference in New Issue
Block a user