Skip to content

Commit 0c9258a

Browse files
committed
add HEAD method support to Ping middleware, modernize code
- Ping middleware now handles both GET and HEAD methods for /ping endpoint - HEAD returns 200 OK with headers but no body (useful for lightweight health checks) - Update dependencies: golang.org/x/crypto 0.45.0 -> 0.46.0, golang.org/x/sys 0.38.0 -> 0.39.0 - Apply modernize linter fixes: - Replace interface{} with any - Use max() builtin instead of if/else - Use strings.SplitSeq for more efficient iteration
1 parent b8cec47 commit 0c9258a

File tree

10 files changed

+68
-39
lines changed

10 files changed

+68
-39
lines changed

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ linters:
1919
- unconvert
2020
- unparam
2121
- unused
22+
- modernize
23+
- testifylint
2224
settings:
2325
goconst:
2426
min-len: 2

benchmarks.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,7 @@ func (b *Benchmarks) Stats(interval time.Duration) BenchmarkStats {
156156
}
157157

158158
// ensure we calculate rate based on actual interval
159-
actualInterval := fnInterval.Sub(stInterval)
160-
if actualInterval < time.Second {
161-
actualInterval = time.Second
162-
}
159+
actualInterval := max(fnInterval.Sub(stInterval), time.Second)
163160

164161
return BenchmarkStats{
165162
Requests: requests,

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ go 1.24.0
44

55
require (
66
github.com/stretchr/testify v1.10.0
7-
golang.org/x/crypto v0.45.0
7+
golang.org/x/crypto v0.46.0
88
)
99

1010
require (
1111
github.com/davecgh/go-spew v1.1.1 // indirect
1212
github.com/pmezard/go-difflib v1.0.0 // indirect
13-
golang.org/x/sys v0.38.0 // indirect
13+
golang.org/x/sys v0.39.0 // indirect
1414
gopkg.in/yaml.v3 v3.0.1 // indirect
1515
)

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
44
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
55
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
66
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7-
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
8-
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
9-
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
10-
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
7+
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
8+
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
9+
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
10+
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
1111
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1212
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1313
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

gzip.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var gzDefaultContentTypes = []string{
2020
}
2121

2222
var gzPool = sync.Pool{
23-
New: func() interface{} { return gzip.NewWriter(io.Discard) },
23+
New: func() any { return gzip.NewWriter(io.Discard) },
2424
}
2525

2626
type gzipResponseWriter struct {

logger/logger.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type Middleware struct {
3232

3333
// Backend is logging backend
3434
type Backend interface {
35-
Logf(format string, args ...interface{})
35+
Logf(format string, args ...any)
3636
}
3737

3838
type logParts struct {
@@ -51,7 +51,7 @@ type logParts struct {
5151

5252
type stdBackend struct{}
5353

54-
func (s stdBackend) Logf(format string, args ...interface{}) {
54+
func (s stdBackend) Logf(format string, args ...any) {
5555
log.Printf(format, args...)
5656
}
5757

middleware.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ func AppInfo(app, author, version string) func(http.Handler) http.Handler {
3636
return f
3737
}
3838

39-
// Ping middleware response with pong to /ping. Stops chain if ping request detected
39+
// Ping middleware response with pong to /ping. Stops chain if ping request detected.
40+
// Handles both GET and HEAD methods - HEAD returns headers only without body,
41+
// which is useful for lightweight health checks by monitoring tools.
4042
func Ping(next http.Handler) http.Handler {
4143
fn := func(w http.ResponseWriter, r *http.Request) {
42-
43-
if r.Method == "GET" && strings.HasSuffix(strings.ToLower(r.URL.Path), "/ping") {
44+
if (r.Method == "GET" || r.Method == "HEAD") && strings.HasSuffix(strings.ToLower(r.URL.Path), "/ping") {
4445
w.Header().Set("Content-Type", "text/plain")
4546
w.WriteHeader(http.StatusOK)
46-
_, _ = w.Write([]byte("pong"))
47+
if r.Method == "GET" {
48+
_, _ = w.Write([]byte("pong"))
49+
}
4750
return
4851
}
4952
next.ServeHTTP(w, r)

middleware_test.go

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,29 +46,58 @@ func TestMiddleware_AppInfo(t *testing.T) {
4646
}
4747

4848
func TestMiddleware_Ping(t *testing.T) {
49-
5049
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
5150
_, err := w.Write([]byte("blah blah"))
5251
require.NoError(t, err)
5352
})
5453
ts := httptest.NewServer(Ping(handler))
5554
defer ts.Close()
5655

57-
resp, err := http.Get(ts.URL + "/ping")
58-
require.Nil(t, err)
59-
assert.Equal(t, 200, resp.StatusCode)
60-
defer resp.Body.Close()
61-
b, err := io.ReadAll(resp.Body)
62-
assert.NoError(t, err)
63-
assert.Equal(t, "pong", string(b))
56+
t.Run("GET returns pong", func(t *testing.T) {
57+
resp, err := http.Get(ts.URL + "/ping")
58+
require.NoError(t, err)
59+
defer resp.Body.Close()
60+
assert.Equal(t, http.StatusOK, resp.StatusCode)
61+
assert.Equal(t, "text/plain", resp.Header.Get("Content-Type"))
62+
b, err := io.ReadAll(resp.Body)
63+
assert.NoError(t, err)
64+
assert.Equal(t, "pong", string(b))
65+
})
6466

65-
resp, err = http.Get(ts.URL + "/blah")
66-
require.Nil(t, err)
67-
assert.Equal(t, 200, resp.StatusCode)
68-
defer resp.Body.Close()
69-
b, err = io.ReadAll(resp.Body)
70-
assert.NoError(t, err)
71-
assert.Equal(t, "blah blah", string(b))
67+
t.Run("HEAD returns 200 with no body", func(t *testing.T) {
68+
req, err := http.NewRequest(http.MethodHead, ts.URL+"/ping", http.NoBody)
69+
require.NoError(t, err)
70+
resp, err := http.DefaultClient.Do(req)
71+
require.NoError(t, err)
72+
defer resp.Body.Close()
73+
assert.Equal(t, http.StatusOK, resp.StatusCode)
74+
assert.Equal(t, "text/plain", resp.Header.Get("Content-Type"))
75+
b, err := io.ReadAll(resp.Body)
76+
assert.NoError(t, err)
77+
assert.Empty(t, b, "HEAD should return empty body")
78+
})
79+
80+
t.Run("POST passes to next handler", func(t *testing.T) {
81+
req, err := http.NewRequest(http.MethodPost, ts.URL+"/ping", http.NoBody)
82+
require.NoError(t, err)
83+
resp, err := http.DefaultClient.Do(req)
84+
require.NoError(t, err)
85+
defer resp.Body.Close()
86+
assert.Equal(t, http.StatusOK, resp.StatusCode)
87+
b, err := io.ReadAll(resp.Body)
88+
assert.NoError(t, err)
89+
assert.Equal(t, "blah blah", string(b))
90+
})
91+
92+
t.Run("other paths pass to next handler", func(t *testing.T) {
93+
resp, err := http.Get(ts.URL + "/blah")
94+
require.NoError(t, err)
95+
defer resp.Body.Close()
96+
assert.Equal(t, http.StatusOK, resp.StatusCode)
97+
b, err := io.ReadAll(resp.Body)
98+
assert.NoError(t, err)
99+
assert.Equal(t, "blah blah", string(b))
100+
})
72101
}
73102

74103
func TestMiddleware_Recoverer(t *testing.T) {

realip/real.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ func Get(r *http.Request) (string, error) {
5858

5959
// check X-Forwarded-For, find leftmost public IP
6060
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
61-
addresses := strings.Split(xff, ",")
62-
for _, addr := range addresses {
61+
for addr := range strings.SplitSeq(xff, ",") {
6362
ip := strings.TrimSpace(addr)
6463
if parsedIP := net.ParseIP(ip); isPublicIP(parsedIP) {
6564
return ip, nil

rest.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
type JSON map[string]any
1414

1515
// RenderJSON sends data as json
16-
func RenderJSON(w http.ResponseWriter, data interface{}) {
16+
func RenderJSON(w http.ResponseWriter, data any) {
1717
buf := &bytes.Buffer{}
1818
enc := json.NewEncoder(buf)
1919
enc.SetEscapeHTML(true)
@@ -35,9 +35,8 @@ func RenderJSONFromBytes(w http.ResponseWriter, r *http.Request, data []byte) er
3535
}
3636

3737
// RenderJSONWithHTML allows html tags and forces charset=utf-8
38-
func RenderJSONWithHTML(w http.ResponseWriter, r *http.Request, v interface{}) error {
39-
40-
encodeJSONWithHTML := func(v interface{}) ([]byte, error) {
38+
func RenderJSONWithHTML(w http.ResponseWriter, r *http.Request, v any) error {
39+
encodeJSONWithHTML := func(v any) ([]byte, error) {
4140
buf := &bytes.Buffer{}
4241
enc := json.NewEncoder(buf)
4342
enc.SetEscapeHTML(false)
@@ -55,7 +54,7 @@ func RenderJSONWithHTML(w http.ResponseWriter, r *http.Request, v interface{}) e
5554
}
5655

5756
// renderJSONWithStatus sends data as json and enforces status code
58-
func renderJSONWithStatus(w http.ResponseWriter, data interface{}, code int) {
57+
func renderJSONWithStatus(w http.ResponseWriter, data any, code int) {
5958
buf := &bytes.Buffer{}
6059
enc := json.NewEncoder(buf)
6160
enc.SetEscapeHTML(true)

0 commit comments

Comments
 (0)