check / check (push) Waiting to run
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; /ip and /ip/ register HEAD themselves, as GetHead does not reach them. 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
94 lines
3.1 KiB
Go
94 lines
3.1 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: every one of
|
|
// them takes GET and HEAD, and no other method.
|
|
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
|
|
// GetHead does not reach this route: mounting the group registers
|
|
// /ip and /ip/ for every method, so HEAD arrives here as HEAD.
|
|
r.Head("/", s.handleIPInfo())
|
|
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
|
|
}
|