check / check (push) Canceled after 0s
Every route was registered for GET only, so HEAD answered 405, and on the JSON routes that 405 said "Internal server error". chi's GetHead now serves HEAD with the GET handler. The /api/v1 and /ip route groups answer a path they lack with a JSON 404 "Not found", and the other methods the router knows (POST, PUT, PATCH, DELETE, OPTIONS, TRACE, CONNECT) with a JSON 405 "Method not allowed" whose Allow header names GET and HEAD. JSONValidationMiddleware gives a 4xx sent without a JSON body the status's standard text instead of "Internal server error". Model: opus-5-5
91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
)
|
|
|
|
// The error messages the JSON routes under /api/v1 and /ip answer with when no
|
|
// route matches the path, and when none takes the method.
|
|
const (
|
|
notFoundMessage = "Not found"
|
|
methodNotAllowedMessage = "Method not allowed"
|
|
|
|
// jsonRoutesAllow is the Allow header of their 405 answer: they are all
|
|
// GET routes, and GetHead serves HEAD on them as GET.
|
|
jsonRoutesAllow = "GET, HEAD"
|
|
)
|
|
|
|
// setupRoutes configures the HTTP routes
|
|
func (s *Server) setupRoutes() {
|
|
r := chi.NewRouter()
|
|
|
|
// Middleware
|
|
r.Use(DecodedPathMiddleware)
|
|
// GetHead serves HEAD with the GET handler. It looks the route up by the
|
|
// path DecodedPathMiddleware sets, so it comes after it.
|
|
r.Use(middleware.GetHead)
|
|
r.Use(SecurityHeadersMiddleware)
|
|
r.Use(middleware.RequestID)
|
|
r.Use(RealIPMiddleware(s.trustedProxies))
|
|
r.Use(RequestLoggerMiddleware(s.logger.Logger)) // Structured request logging
|
|
r.Use(middleware.Recoverer)
|
|
r.Use(JSONResponseMiddleware)
|
|
// Timeout must come after JSONResponseMiddleware, which sends its answer
|
|
// when the handler returns and would send a 200 ahead of Timeout's 504 for
|
|
// a handler that wrote nothing.
|
|
const requestTimeout = 30 * time.Second // Increased from 8s for slow queries
|
|
r.Use(middleware.Timeout(requestTimeout))
|
|
|
|
// Routes
|
|
r.Get("/", s.handleIndex())
|
|
r.Get("/status", s.handleStatusHTML())
|
|
r.Get("/status.json", JSONValidationMiddleware(s.handleStatusJSON()).ServeHTTP)
|
|
r.Get(
|
|
"/.well-known/healthcheck.json",
|
|
JSONValidationMiddleware(s.handleHealthCheck()).ServeHTTP,
|
|
)
|
|
|
|
// AS and prefix detail pages
|
|
r.Get("/as/{asn}", s.handleASDetail())
|
|
r.Get("/prefix/{prefix}/{len}", s.handlePrefixDetail())
|
|
r.Get("/prefixlength/{length}", s.handlePrefixLength())
|
|
r.Get("/prefixlength6/{length}", s.handlePrefixLength6())
|
|
|
|
// The JSON routes answer a path or method they do not take with the JSON
|
|
// error body, as they answer everything else.
|
|
jsonNotFound := func(w http.ResponseWriter, _ *http.Request) {
|
|
s.writeJSONError(w, http.StatusNotFound, notFoundMessage)
|
|
}
|
|
jsonMethodNotAllowed := func(w http.ResponseWriter, _ *http.Request) {
|
|
// chi's own 405 answer sets Allow; one set in its place must too.
|
|
w.Header().Set("Allow", jsonRoutesAllow)
|
|
s.writeJSONError(w, http.StatusMethodNotAllowed, methodNotAllowedMessage)
|
|
}
|
|
|
|
// IP info JSON endpoints (replaces old /ip redirect)
|
|
r.Route("/ip", func(r chi.Router) {
|
|
r.Use(JSONValidationMiddleware)
|
|
r.NotFound(jsonNotFound)
|
|
r.MethodNotAllowed(jsonMethodNotAllowed)
|
|
r.Get("/", s.handleIPInfo()) // Client IP
|
|
r.Get("/{addr}", s.handleIPInfo()) // Specified IP
|
|
})
|
|
|
|
// API routes
|
|
r.Route("/api/v1", func(r chi.Router) {
|
|
r.Use(JSONValidationMiddleware)
|
|
r.NotFound(jsonNotFound)
|
|
r.MethodNotAllowed(jsonMethodNotAllowed)
|
|
r.Get("/stats", s.handleStats())
|
|
r.Get("/ip/{ip}", s.handleIPLookup())
|
|
r.Get("/as/{asn}", s.handleASDetailJSON())
|
|
r.Get("/prefix/{prefix}/{len}", s.handlePrefixDetailJSON())
|
|
})
|
|
|
|
s.router = r
|
|
}
|