check / check (push) Canceled after 0s
The AS Name Search card on the home page said the search was coming soon. It now asks the new GET /api/v1/search/as?q=<text>, which searches the AS list built into asinfo, and lists the matches as links to their AS pages. The answer holds the query, the number of matches and the first 50 of them; an empty q answers 400. asinfo.Search now ignores case, uses strings.Contains instead of its own substring loop, and returns its matches in AS number order. A number, or AS and a number, typed into the card still opens that AS's page. The card builds the list with textContent, since the names come from outside data. Model: opus-5-5
95 lines
3.2 KiB
Go
95 lines
3.2 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())
|
|
r.Get("/search/as", s.handleASSearch())
|
|
})
|
|
|
|
s.router = r
|
|
}
|