Compare commits

..
1 Commits
Author SHA1 Message Date
clawbot 6ce4ee255b Answer HEAD requests and give 405 a plain message (closes #111)
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
2026-10-20 00:41:34 +00:00
2 changed files with 7 additions and 11 deletions

No files matched your search

+3 -6
View File
@@ -14,8 +14,8 @@ const (
notFoundMessage = "Not found" notFoundMessage = "Not found"
methodNotAllowedMessage = "Method not allowed" methodNotAllowedMessage = "Method not allowed"
// jsonRoutesAllow is the Allow header of their 405 answer: every one of // jsonRoutesAllow is the Allow header of their 405 answer: they are all
// them takes GET and HEAD, and no other method. // GET routes, and GetHead serves HEAD on them as GET.
jsonRoutesAllow = "GET, HEAD" jsonRoutesAllow = "GET, HEAD"
) )
@@ -71,10 +71,7 @@ func (s *Server) setupRoutes() {
r.Use(JSONValidationMiddleware) r.Use(JSONValidationMiddleware)
r.NotFound(jsonNotFound) r.NotFound(jsonNotFound)
r.MethodNotAllowed(jsonMethodNotAllowed) 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 r.Get("/{addr}", s.handleIPInfo()) // Specified IP
}) })
+4 -5
View File
@@ -7,17 +7,16 @@ import (
"testing" "testing"
) )
// TestRoutesAnswerHEAD sends HEAD for a page and JSON routes through the // TestRoutesAnswerHEAD sends HEAD for a page and a JSON route through the
// server's router and checks that each answers as GET does. /ip and /ip/, the // server's router and checks that each answers as GET does. Before the fix
// lookup of the caller's own address, are in the list because chi's GetHead // every route answered HEAD with 405
// does not reach them. Before the fix every route answered HEAD with 405
// (https://git.eeqj.de/sneak/routewatch/issues/111). // (https://git.eeqj.de/sneak/routewatch/issues/111).
func TestRoutesAnswerHEAD(t *testing.T) { func TestRoutesAnswerHEAD(t *testing.T) {
t.Parallel() t.Parallel()
s := newServerWithTestRoutes(t) s := newServerWithTestRoutes(t)
for _, path := range []string{"/", statsPath, "/ip", "/ip/"} { for _, path := range []string{"/", statsPath} {
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
s.router.ServeHTTP(rec, httptest.NewRequestWithContext( s.router.ServeHTTP(rec, httptest.NewRequestWithContext(
t.Context(), http.MethodHead, path, nil)) t.Context(), http.MethodHead, path, nil))