1 Commits
Author SHA1 Message Date
clawbot db4f58d737 Answer HEAD requests and give 405 a plain message (closes #111)
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
2026-10-20 00:56:41 +00:00
2 changed files with 11 additions and 7 deletions
+6 -3
View File
@@ -14,8 +14,8 @@ 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 is the Allow header of their 405 answer: every one of
// them takes GET and HEAD, and no other method.
jsonRoutesAllow = "GET, HEAD"
)
@@ -71,7 +71,10 @@ func (s *Server) setupRoutes() {
r.Use(JSONValidationMiddleware)
r.NotFound(jsonNotFound)
r.MethodNotAllowed(jsonMethodNotAllowed)
r.Get("/", s.handleIPInfo()) // Client IP
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
})
+5 -4
View File
@@ -7,16 +7,17 @@ import (
"testing"
)
// TestRoutesAnswerHEAD sends HEAD for a page and a JSON route through the
// server's router and checks that each answers as GET does. Before the fix
// every route answered HEAD with 405
// TestRoutesAnswerHEAD sends HEAD for a page and JSON routes through the
// server's router and checks that each answers as GET does. /ip and /ip/, the
// lookup of the caller's own address, are in the list because chi's GetHead
// does not reach them. Before the fix every route answered HEAD with 405
// (https://git.eeqj.de/sneak/routewatch/issues/111).
func TestRoutesAnswerHEAD(t *testing.T) {
t.Parallel()
s := newServerWithTestRoutes(t)
for _, path := range []string{"/", statsPath} {
for _, path := range []string{"/", statsPath, "/ip", "/ip/"} {
rec := httptest.NewRecorder()
s.router.ServeHTTP(rec, httptest.NewRequestWithContext(
t.Context(), http.MethodHead, path, nil))