Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion echo.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ type Echo struct {

// formParseMaxMemory is passed to Context for multipart form parsing (See http.Request.ParseMultipartForm)
formParseMaxMemory int64

// automatically registers a HEAD request within GET
autoHeadInGet bool
}

// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
Expand Down Expand Up @@ -330,6 +333,7 @@ func New() *Echo {
Binder: &DefaultBinder{},
JSONSerializer: &DefaultJSONSerializer{},
formParseMaxMemory: defaultMemory,
autoHeadInGet: true,
}

e.serveHTTPFunc = e.serveHTTP
Expand All @@ -341,6 +345,14 @@ func New() *Echo {
return e
}

// AutoHeadCancel turns the flag autoHeadInGet to false.
//
// This flag is used to register HEAD request automatically
// everytime a GET request is registered.
func (e *Echo) AutoHeadCancel() {
e.autoHeadInGet = false
}

// NewContext returns a new Context instance.
//
// Note: both request and response can be left to nil as Echo.ServeHTTP will call c.Reset(req,resp) anyway
Expand Down Expand Up @@ -437,7 +449,14 @@ func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo

// GET registers a new GET route for a path with matching handler in the router
// with optional route-level middleware. Panics on error.
//
// Note: if autoHeadInGet flag is true, it will also register a HEAD request
// to the same path.
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
if e.autoHeadInGet {
_ = e.HEAD(path, h, m...)
}

return e.Add(http.MethodGet, path, h, m...)
}

Expand Down Expand Up @@ -639,7 +658,7 @@ func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...Middl

// Group creates a new router group with prefix and optional group-level middleware.
func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
g = &Group{prefix: prefix, echo: e}
g = &Group{prefix: prefix, echo: e, autoHeadInGet: true}
g.Use(m...)
return
}
Expand Down
31 changes: 30 additions & 1 deletion echo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,16 @@ func TestEchoWrapMiddleware(t *testing.T) {
assert.Equal(t, "/:id", actualPattern)
}

func TestAutoHeadCancel(t *testing.T) {
e := New()

assert.Equal(t, true, e.autoHeadInGet)

e.AutoHeadCancel()

assert.Equal(t, false, e.autoHeadInGet)
}

func TestEchoConnect(t *testing.T) {
e := New()

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

func TestEchoAutoHead(t *testing.T) {
e := New()

assert.Equal(t, true, e.autoHeadInGet) // guarantees the flag is true
ri := e.GET("/", func(c *Context) error {
return c.String(http.StatusTeapot, "OK")
})

assert.Equal(t, http.MethodGet, ri.Method)
assert.Equal(t, "/", ri.Path)
assert.Equal(t, http.MethodGet+":/", ri.Name)
assert.Nil(t, ri.Parameters)

status, body := request(http.MethodHead, "/", e)
assert.Equal(t, http.StatusTeapot, status)
assert.Equal(t, "OK", body)
}

func TestEchoHead(t *testing.T) {
e := New()

Expand Down Expand Up @@ -909,7 +937,7 @@ func TestEchoMethodNotAllowed(t *testing.T) {
e.ServeHTTP(rec, req)

assert.Equal(t, http.StatusMethodNotAllowed, rec.Code)
assert.Equal(t, "OPTIONS, GET", rec.Header().Get(HeaderAllow))
assert.Equal(t, "OPTIONS, GET, HEAD", rec.Header().Get(HeaderAllow))
}

func TestEcho_OnAddRoute(t *testing.T) {
Expand Down Expand Up @@ -950,6 +978,7 @@ func TestEcho_OnAddRoute(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {

e := New()
e.AutoHeadCancel()

added := make([]string, 0)
cnt := 0
Expand Down
17 changes: 14 additions & 3 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ import (
// routes that share a common middleware or functionality that should be separate
// from the parent echo instance while still inheriting from it.
type Group struct {
echo *Echo
prefix string
middleware []MiddlewareFunc
echo *Echo
prefix string
middleware []MiddlewareFunc
autoHeadInGet bool
}

// AutoHeadCancel implements `Echo#AutoHeadCancel()` for the Group struct.
func (g *Group) AutoHeadCancel() {
g.autoHeadInGet = false
}

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

// GET implements `Echo#GET()` for sub-routes within the Group. Panics on error.
func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
if g.autoHeadInGet {
_ = g.HEAD(path, h, m...)
}

return g.Add(http.MethodGet, path, h, m...)
}

Expand Down Expand Up @@ -105,6 +115,7 @@ func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) (sg *Group) {
m = append(m, g.middleware...)
m = append(m, middleware...)
sg = g.echo.Group(g.prefix+prefix, m...)
sg.autoHeadInGet = true
return
}

Expand Down
30 changes: 30 additions & 0 deletions group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ func TestGroupRouteMiddlewareWithMatchAny(t *testing.T) {

}

func TestAutoHeadCancelInGroup(t *testing.T) {
e := New()
g := e.Group("/group")

assert.Equal(t, true, g.autoHeadInGet)

g.AutoHeadCancel()

assert.Equal(t, false, g.autoHeadInGet)
}

func TestGroup_CONNECT(t *testing.T) {
e := New()

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

func TestGroup_AutoHEAD_in_GET(t *testing.T) {
e := New()

users := e.Group("/users")
ri := users.GET("/activate", func(c *Context) error {
return c.String(http.StatusTeapot, "OK")
})

assert.Equal(t, true, users.autoHeadInGet)
assert.Equal(t, http.MethodGet, ri.Method)
assert.Equal(t, "/users/activate", ri.Path)
assert.Equal(t, http.MethodGet+":/users/activate", ri.Name)
assert.Nil(t, ri.Parameters)

status, body := request(http.MethodHead, "/users/activate", e)
assert.Equal(t, http.StatusTeapot, status)
assert.Equal(t, "OK", body)
}

func TestGroup_HEAD(t *testing.T) {
e := New()

Expand Down