Skip to content

Commit fcb1ea2

Browse files
committed
feat: add automatic HEAD request generation from GET request, the choice is opt-out
1 parent 675712d commit fcb1ea2

4 files changed

Lines changed: 90 additions & 3 deletions

File tree

echo.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ type Echo struct {
9292

9393
// formParseMaxMemory is passed to Context for multipart form parsing (See http.Request.ParseMultipartForm)
9494
formParseMaxMemory int64
95+
96+
// automatically registers a HEAD request within GET
97+
autoHeadInGet bool
9598
}
9699

97100
// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
@@ -330,6 +333,7 @@ func New() *Echo {
330333
Binder: &DefaultBinder{},
331334
JSONSerializer: &DefaultJSONSerializer{},
332335
formParseMaxMemory: defaultMemory,
336+
autoHeadInGet: true,
333337
}
334338

335339
e.serveHTTPFunc = e.serveHTTP
@@ -341,6 +345,14 @@ func New() *Echo {
341345
return e
342346
}
343347

348+
// AutoHeadCancel turns the flag autoHeadInGet to false.
349+
//
350+
// This flag is used to register HEAD request automatically
351+
// everytime a GET request is registered.
352+
func (e *Echo) AutoHeadCancel() {
353+
e.autoHeadInGet = false
354+
}
355+
344356
// NewContext returns a new Context instance.
345357
//
346358
// Note: both request and response can be left to nil as Echo.ServeHTTP will call c.Reset(req,resp) anyway
@@ -437,7 +449,14 @@ func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo
437449

438450
// GET registers a new GET route for a path with matching handler in the router
439451
// with optional route-level middleware. Panics on error.
452+
//
453+
// Note: if autoHeadInGet flag is true, it will also register a HEAD request
454+
// to the same path.
440455
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
456+
if e.autoHeadInGet {
457+
e.Add(http.MethodHead, path, h, m...)
458+
}
459+
441460
return e.Add(http.MethodGet, path, h, m...)
442461
}
443462

echo_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,16 @@ func TestEchoWrapMiddleware(t *testing.T) {
529529
assert.Equal(t, "/:id", actualPattern)
530530
}
531531

532+
func TestAutoHeadCancel(t *testing.T) {
533+
e := New()
534+
535+
assert.Equal(t, true, e.autoHeadInGet)
536+
537+
e.AutoHeadCancel()
538+
539+
assert.Equal(t, false, e.autoHeadInGet)
540+
}
541+
532542
func TestEchoConnect(t *testing.T) {
533543
e := New()
534544

@@ -580,6 +590,24 @@ func TestEchoGet(t *testing.T) {
580590
assert.Equal(t, "OK", body)
581591
}
582592

593+
func TestEchoAutoHead(t *testing.T) {
594+
e := New()
595+
596+
assert.Equal(t, true, e.autoHeadInGet) // guarantees the flag is true
597+
ri := e.GET("/", func(c *Context) error {
598+
return c.String(http.StatusTeapot, "OK")
599+
})
600+
601+
assert.Equal(t, http.MethodHead, ri.Method)
602+
assert.Equal(t, "/", ri.Path)
603+
assert.Equal(t, http.MethodHead+":/", ri.Name)
604+
assert.Nil(t, ri.Parameters)
605+
606+
status, body := request(http.MethodHead, "/", e)
607+
assert.Equal(t, http.StatusTeapot, status)
608+
assert.Equal(t, "OK", body)
609+
}
610+
583611
func TestEchoHead(t *testing.T) {
584612
e := New()
585613

group.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@ import (
1212
// routes that share a common middleware or functionality that should be separate
1313
// from the parent echo instance while still inheriting from it.
1414
type Group struct {
15-
echo *Echo
16-
prefix string
17-
middleware []MiddlewareFunc
15+
echo *Echo
16+
prefix string
17+
middleware []MiddlewareFunc
18+
autoHeadInGet bool
19+
}
20+
21+
// AutoHeadCancel implements `Echo#AutoHeadCancel()` for the Group struct.
22+
func (g *Group) AutoHeadCancel() {
23+
g.autoHeadInGet = false
1824
}
1925

2026
// Use implements `Echo#Use()` for sub-routes within the Group.
@@ -35,6 +41,10 @@ func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInf
3541

3642
// GET implements `Echo#GET()` for sub-routes within the Group. Panics on error.
3743
func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
44+
if g.autoHeadInGet {
45+
g.Add(http.MethodHead, path, h, m...)
46+
}
47+
3848
return g.Add(http.MethodGet, path, h, m...)
3949
}
4050

group_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,17 @@ func TestGroupRouteMiddlewareWithMatchAny(t *testing.T) {
162162

163163
}
164164

165+
func TestAutoHeadCancel(t *testing.T) {
166+
e := New()
167+
g := e.Group("/group")
168+
169+
assert.Equal(t, true, g.autoHeadInGet)
170+
171+
g.AutoHeadCancel()
172+
173+
assert.Equal(t, false, g.autoHeadInGet)
174+
}
175+
165176
func TestGroup_CONNECT(t *testing.T) {
166177
e := New()
167178

@@ -198,6 +209,25 @@ func TestGroup_DELETE(t *testing.T) {
198209
assert.Equal(t, `OK`, body)
199210
}
200211

212+
func TestGroup_AutoHEAD_in_GET(t *testing.T) {
213+
e := New()
214+
215+
users := e.Group("/users")
216+
ri := users.GET("/activate", func(c *Context) error {
217+
return c.String(http.StatusTeapot, "OK")
218+
})
219+
220+
assert.Equal(t, true, users.autoHeadInGet)
221+
assert.Equal(t, http.MethodHead, ri.Method)
222+
assert.Equal(t, "/users/activate", ri.Path)
223+
assert.Equal(t, http.MethodHead+":/users/activate", ri.Name)
224+
assert.Nil(t, ri.Parameters)
225+
226+
status, body := request(http.MethodHead, "/users/activate", e)
227+
assert.Equal(t, http.StatusTeapot, status)
228+
assert.Equal(t, `OK`, body)
229+
}
230+
201231
func TestGroup_HEAD(t *testing.T) {
202232
e := New()
203233

0 commit comments

Comments
 (0)