Repository: labstack/echo (go)

Issue:
echo.Bind not working while binding query params to a struct with another nested anonymous struct's pointer in it
### Issue Description
bind the query params into target struct's anonymous filed which is a pointer to struct,

note that if the payload in http body, it seems bind works fine,

issue happens only when bind query params with a struct with an anonymous filed which is a pointer to struct

### Checklist

- [X] Dependencies installed
- [X] No typos
- [X] Searched existing issues and docs

### Expected behaviour

```shell
curl 'http://localhost:8000/test?limit=1' 

{"limit":1,"offset":0}

```

### Actual behaviour
bind skips the anonymous filed

```shell
curl 'http://localhost:8000/test?limit=1' 

{"limit":0,"offset":0}

```
### Steps to reproduce
```shell
curl 'http://localhost:8000/test?limit=1' 
```
### Working code to debug

```go
package main

import (
	"fmt"
	"github.com/labstack/echo/v4"
	"net/http"
)

type PaginationParas struct {
	Limit  int `json:"limit" query:"limit"`
	Offset int `json:"offset" query:"offset"`
}
type Foo struct {
	*PaginationParas
}

func main() {
	e := echo.New()
	e.Debug = true
	e.GET("/test", func(ctx echo.Context) error {
		var f = Foo{PaginationParas: &PaginationParas{}}
		if err := ctx.Bind(&f); err != nil {
			fmt.Println(err)
			return err
		}
		return ctx.JSON(http.StatusOK, f)
	})
	e.Logger.Fatal(e.Start(":8000"))
}

```

### Version/commit
v4.2.3-0.20210417194748-3b07058a1d8f

Pull request:
Fix #1858: Add query params binding support for anonymous struct pointer field
try to fix #1858

if the anonymous struct filed is a pointer,  try dig one step deeper and get the underlaying struct

Patch:
diff --git a/bind.go b/bind.go
index 08d3989..dfdf82d 100644
--- a/bind.go
+++ b/bind.go
@@ -144,11 +144,20 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
 	for i := 0; i < typ.NumField(); i++ {
 		typeField := typ.Field(i)
 		structField := val.Field(i)
+		if typeField.Anonymous {
+			if structField.Kind() == reflect.Ptr {
+				structField = structField.Elem()
+			}
+		}
 		if !structField.CanSet() {
 			continue
 		}
 		structFieldKind := structField.Kind()
 		inputFieldName := typeField.Tag.Get(tag)
+		if typeField.Anonymous && structField.Kind() == reflect.Struct && inputFieldName != "" {
+			// if anonymous struct with query/param/form tags, report an error
+			return errors.New("query/param/form tags are not allowed with anonymous struct field")
+		}
 
 		if inputFieldName == "" {
 			// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).
diff --git a/middleware/jwt.go b/middleware/jwt.go
index da00ea5..cd35b62 100644
--- a/middleware/jwt.go
+++ b/middleware/jwt.go
@@ -29,15 +29,19 @@ type (
 		// ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context.
 		ErrorHandlerWithContext JWTErrorHandlerWithContext
 
-		// Signing key to validate token. Used as fallback if SigningKeys has length 0.
-		// Required. This or SigningKeys.
+		// Signing key to validate token.
+		// This is one of the three options to provide a token validation key.
+		// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
+		// Required if neither user-defined KeyFunc nor SigningKeys is provided.
 		SigningKey interface{}
 
 		// Map of signing keys to validate token with kid field usage.
-		// Required. This or SigningKey.
+		// This is one of the three options to provide a token validation key.
+		// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
+		// Required if neither user-defined KeyFunc nor SigningKey is provided.
 		SigningKeys map[string]interface{}
 
-		// Signing method, used to check token signing method.
+		// Signing method used to check the token's signing algorithm.
 		// Optional. Default value HS256.
 		SigningMethod string
 
@@ -64,7 +68,16 @@ type (
 		// Optional. Default value "Bearer".
 		AuthScheme string
 
-		keyFunc jwt.Keyfunc
+		// KeyFunc defines a user-defined function that supplies the public key for a token validation.
+		// The function shall take care of verifying the signing algorithm and selecting the proper key.
+		// A user-defined KeyFunc can be useful if tokens are issued by an external party.
+		//
+		// When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored.
+		// This is one of the three options to provide a token validation key.
+		// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
+		// Required if neither SigningKeys nor SigningKey is provided.
+		// Default to an internal implementation verifying the signing algorithm and selecting the proper key.
+		KeyFunc jwt.Keyfunc
 	}
 
 	// JWTSuccessHandler defines a function which is executed for a valid token.
@@ -99,6 +112,7 @@ var (
 		TokenLookup:   "header:" + echo.HeaderAuthorization,
 		AuthScheme:    "Bearer",
 		Claims:        jwt.MapClaims{},
+		KeyFunc:       nil,
 	}
 )
 
@@ -123,7 +137,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
 	if config.Skipper == nil {
 		config.Skipper = DefaultJWTConfig.Skipper
 	}
-	if config.SigningKey == nil && len(config.SigningKeys) == 0 {
+	if config.SigningKey == nil && len(config.SigningKeys) == 0 && config.KeyFunc == nil {
 		panic("echo: jwt middleware requires signing key")
 	}
 	if config.SigningMethod == "" {
@@ -141,35 +155,29 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
 	if config.AuthScheme == "" {
 		config.AuthScheme = DefaultJWTConfig.AuthScheme
 	}
-	config.keyFunc = func(t *jwt.Token) (interface{}, error) {
-		// Check the signing method
-		if t.Method.Alg() != config.SigningMethod {
-			return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
-		}
-		if len(config.SigningKeys) > 0 {
-			if kid, ok := t.Header["kid"].(string); ok {
-				if key, ok := config.SigningKeys[kid]; ok {
-					return key, nil
-				}
-			}
-			return nil, fmt.Errorf("unexpected jwt key id=%v", t.Header["kid"])
-		}
-
-		return config.SigningKey, nil
+	if config.KeyFunc == nil {
+		config.KeyFunc = config.defaultKeyFunc
 	}
 
 	// Initialize
-	parts := strings.Split(config.TokenLookup, ":")
-	extractor := jwtFromHeader(parts[1], config.AuthScheme)
-	switch parts[0] {
-	case "query":
-		extractor = jwtFromQuery(parts[1])
-	case "param":
-		extractor = jwtFromParam(parts[1])
-	case "cookie":
-		extractor = jwtFromCookie(parts[1])
-	case "form":
-		extractor = jwtFromForm(parts[1])
+	// Split sources
+	sources := strings.Split(config.TokenLookup, ",")
+	var extractors []jwtExtractor
+	for _, source := range sources {
+		parts := strings.Split(source, ":")
+
+		switch parts[0] {
+		case "query":
+			extractors = append(extractors, jwtFromQuery(parts[1]))
+		case "param":
+			extractors = append(extractors, jwtFromParam(parts[1]))
+		case "cookie":
+			extractors = append(extractors, jwtFromCookie(parts[1]))
+		case "form":
+			extractors = append(extractors, jwtFromForm(parts[1]))
+		case "header":
+			extractors = append(extractors, jwtFromHeader(parts[1], config.AuthScheme))
+		}
 	}
 
 	return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -181,8 +189,17 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
 			if config.BeforeFunc != nil {
 				config.BeforeFunc(c)
 			}
-
-			auth, err := extractor(c)
+			var auth string
+			var err error
+			for _, extractor := range extractors {
+				// Extract token from extractor, if it's not fail break the loop and
+				// set auth
+				auth, err = extractor(c)
+				if err == nil {
+					break
+				}
+			}
+			// If none of extractor has a token, handle error
 			if err != nil {
 				if config.ErrorHandler != nil {
 					return config.ErrorHandler(err)
@@ -193,14 +210,15 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
 				}
 				return err
 			}
+
 			token := new(jwt.Token)
 			// Issue #647, #656
 			if _, ok := config.Claims.(jwt.MapClaims); ok {
-				token, err = jwt.Parse(auth, config.keyFunc)
+				token, err = jwt.Parse(auth, config.KeyFunc)
 			} else {
 				t := reflect.ValueOf(config.Claims).Type().Elem()
 				claims := reflect.New(t).Interface().(jwt.Claims)
-				token, err = jwt.ParseWithClaims(auth, claims, config.keyFunc)
+				token, err = jwt.ParseWithClaims(auth, claims, config.KeyFunc)
 			}
 			if err == nil && token.Valid {
 				// Store user information from token into context.
@@ -225,6 +243,24 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
 	}
 }
 
+// defaultKeyFunc returns a signing key of the given token.
+func (config *JWTConfig) defaultKeyFunc(t *jwt.Token) (interface{}, error) {
+	// Check the signing method
+	if t.Method.Alg() != config.SigningMethod {
+		return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
+	}
+	if len(config.SigningKeys) > 0 {
+		if kid, ok := t.Header["kid"].(string); ok {
+			if key, ok := config.SigningKeys[kid]; ok {
+				return key, nil
+			}
+		}
+		return nil, fmt.Errorf("unexpected jwt key id=%v", t.Header["kid"])
+	}
+
+	return config.SigningKey, nil
+}
+
 // jwtFromHeader returns a `jwtExtractor` that extracts token from the request header.
 func jwtFromHeader(header string, authScheme string) jwtExtractor {
 	return func(c echo.Context) (string, error) {
diff --git a/middleware/key_auth.go b/middleware/key_auth.go
index 94cfd14..fd169aa 100644
--- a/middleware/key_auth.go
+++ b/middleware/key_auth.go
@@ -30,12 +30,19 @@ type (
 		// Validator is a function to validate key.
 		// Required.
 		Validator KeyAuthValidator
+
+		// ErrorHandler defines a function which is executed for an invalid key.
+		// It may be used to define a custom error.
+		ErrorHandler KeyAuthErrorHandler
 	}
 
 	// KeyAuthValidator defines a function to validate KeyAuth credentials.
 	KeyAuthValidator func(string, echo.Context) (bool, error)
 
 	keyExtractor func(echo.Context) (string, error)
+
+	// KeyAuthErrorHandler defines a function which is executed for an invalid key.
+	KeyAuthErrorHandler func(error, echo.Context) error
 )
 
 var (
@@ -95,10 +102,16 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
 			// Extract and verify key
 			key, err := extractor(c)
 			if err != nil {
+				if config.ErrorHandler != nil {
+					return config.ErrorHandler(err, c)
+				}
 				return echo.NewHTTPError(http.StatusBadRequest, err.Error())
 			}
 			valid, err := config.Validator(key, c)
 			if err != nil {
+				if config.ErrorHandler != nil {
+					return config.ErrorHandler(err, c)
+				}
 				return &echo.HTTPError{
 					Code:     http.StatusUnauthorized,
 					Message:  "invalid key",
diff --git a/middleware/static.go b/middleware/static.go
index ae79cb5..0106f7c 100644
--- a/middleware/static.go
+++ b/middleware/static.go
@@ -42,6 +42,10 @@ type (
 		// the filesystem path is not doubled
 		// Optional. Default value false.
 		IgnoreBase bool `yaml:"ignoreBase"`
+
+		// Filesystem provides access to the static content.
+		// Optional. Defaults to http.Dir(config.Root)
+		Filesystem http.FileSystem `yaml:"-"`
 	}
 )
 
@@ -146,6 +150,10 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
 	if config.Index == "" {
 		config.Index = DefaultStaticConfig.Index
 	}
+	if config.Filesystem == nil {
+		config.Filesystem = http.Dir(config.Root)
+		config.Root = "."
+	}
 
 	// Index template
 	t, err := template.New("index").Parse(html)
@@ -178,49 +186,73 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
 				}
 			}
 
-			fi, err := os.Stat(name)
+			file, err := openFile(config.Filesystem, name)
 			if err != nil {
-				if os.IsNotExist(err) {
-					if err = next(c); err != nil {
-						if he, ok := err.(*echo.HTTPError); ok {
-							if config.HTML5 && he.Code == http.StatusNotFound {
-								return c.File(filepath.Join(config.Root, config.Index))
-							}
-						}
-						return
-					}
+				if !os.IsNotExist(err) {
+					return err
+				}
+
+				if err = next(c); err == nil {
+					return err
+				}
+
+				he, ok := err.(*echo.HTTPError)
+				if !(ok && config.HTML5 && he.Code == http.StatusNotFound) {
+					return err
+				}
+
+				file, err = openFile(config.Filesystem, filepath.Join(config.Root, config.Index))
+				if err != nil {
+					return err
 				}
-				return
 			}
 
-			if fi.IsDir() {
-				index := filepath.Join(name, config.Index)
-				fi, err = os.Stat(index)
+			defer file.Close()
+
+			info, err := file.Stat()
+			if err != nil {
+				return err
+			}
 
+			if info.IsDir() {
+				index, err := openFile(config.Filesystem, filepath.Join(name, config.Index))
 				if err != nil {
 					if config.Browse {
-						return listDir(t, name, c.Response())
+						return listDir(t, name, file, c.Response())
 					}
+
 					if os.IsNotExist(err) {
 						return next(c)
 					}
-					return
 				}
 
-				return c.File(index)
+				defer index.Close()
+
+				info, err = index.Stat()
+				if err != nil {
+					return err
+				}
+
+				return serveFile(c, index, info)
 			}
 
-			return c.File(name)
+			return serveFile(c, file, info)
 		}
 	}
 }
 
-func listDir(t *template.Template, name string, res *echo.Response) (err error) {
-	file, err := os.Open(name)
-	if err != nil {
-		return
-	}
-	files, err := file.Readdir(-1)
+func openFile(fs http.FileSystem, name string) (http.File, error) {
+	pathWithSlashes := filepath.ToSlash(name)
+	return fs.Open(pathWithSlashes)
+}
+
+func serveFile(c echo.Context, file http.File, info os.FileInfo) error {
+	http.ServeContent(c.Response(), c.Request(), info.Name(), info.ModTime(), file)
+	return nil
+}
+
+func listDir(t *template.Template, name string, dir http.File, res *echo.Response) (err error) {
+	files, err := dir.Readdir(-1)
 	if err != nil {
 		return
 	}
diff --git a/middleware/timeout.go b/middleware/timeout.go
index d56e463..fb8ae42 100644
--- a/middleware/timeout.go
+++ b/middleware/timeout.go
@@ -92,6 +92,15 @@ func (t echoHandlerFuncWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Reques
 	originalWriter := t.ctx.Response().Writer
 	t.ctx.Response().Writer = rw
 
+	// in case of panic we restore original writer and call panic again
+	// so it could be handled with global middleware Recover()
+	defer func() {
+		if err := recover(); err != nil {
+			t.ctx.Response().Writer = originalWriter
+			panic(err)
+		}
+	}()
+
 	err := t.handler(t.ctx)
 	if ctxErr := r.Context().Err(); ctxErr == context.DeadlineExceeded {
 		if err != nil && t.errHandler != nil {
diff --git a/router.go b/router.go
index 2dd09fa..5b2474b 100644
--- a/router.go
+++ b/router.go
@@ -23,6 +23,10 @@ type (
 		methodHandler  *methodHandler
 		paramChild     *node
 		anyChild       *node
+		// isLeaf indicates that node does not have child routes
+		isLeaf bool
+		// isHandler indicates that node has at least one handler registered to it
+		isHandler bool
 	}
 	kind          uint8
 	children      []*node
@@ -50,6 +54,20 @@ const (
 	anyLabel   = byte('*')
 )
 
+func (m *methodHandler) isHandler() bool {
+	return m.connect != nil ||
+		m.delete != nil ||
+		m.get != nil ||
+		m.head != nil ||
+		m.options != nil ||
+		m.patch != nil ||
+		m.post != nil ||
+		m.propfind != nil ||
+		m.put != nil ||
+		m.trace != nil ||
+		m.report != nil
+}
+
 // NewRouter returns a new Router instance.
 func NewRouter(e *Echo) *Router {
 	return &Router{
@@ -73,6 +91,11 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
 	pnames := []string{} // Param names
 	ppath := path        // Pristine path
 
+	if h == nil && r.echo.Logger != nil {
+		// FIXME: in future we should return error
+		r.echo.Logger.Errorf("Adding route without handler function: %v:%v", method, path)
+	}
+
 	for i, lcpIndex := 0, len(path); i < lcpIndex; i++ {
 		if path[i] == ':' {
 			j := i + 1
@@ -86,6 +109,7 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
 			i, lcpIndex = j, len(path)
 
 			if i == lcpIndex {
+				// path node is last fragment of route path. ie. `/users/:id`
 				r.insert(method, path[:i], h, paramKind, ppath, pnames)
 			} else {
 				r.insert(method, path[:i], nil, paramKind, "", nil)
@@ -136,6 +160,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 				currentNode.ppath = ppath
 				currentNode.pnames = pnames
 			}
+			currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
 		} else if lcpLen < prefixLen {
 			// Split node
 			n := newNode(
@@ -149,7 +174,6 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 				currentNode.paramChild,
 				currentNode.anyChild,
 			)
-
 			// Update parent path for all children to new node
 			for _, child := range currentNode.staticChildren {
 				child.parent = n
@@ -171,6 +195,8 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 			currentNode.pnames = nil
 			currentNode.paramChild = nil
 			currentNode.anyChild = nil
+			currentNode.isLeaf = false
+			currentNode.isHandler = false
 
 			// Only Static children could reach here
 			currentNode.addStaticChild(n)
@@ -188,6 +214,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 				// Only Static children could reach here
 				currentNode.addStaticChild(n)
 			}
+			currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
 		} else if lcpLen < searchLen {
 			search = search[lcpLen:]
 			c := currentNode.findChildWithLabel(search[0])
@@ -207,6 +234,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 			case anyKind:
 				currentNode.anyChild = n
 			}
+			currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
 		} else {
 			// Node already exists
 			if h != nil {
@@ -233,6 +261,8 @@ func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath
 		methodHandler:  mh,
 		paramChild:     paramChildren,
 		anyChild:       anyChildren,
+		isLeaf:         sc == nil && paramChildren == nil && anyChildren == nil,
+		isHandler:      mh.isHandler(),
 	}
 }
 
@@ -289,6 +319,12 @@ func (n *node) addHandler(method string, h HandlerFunc) {
 	case REPORT:
 		n.methodHandler.report = h
 	}
+
+	if h != nil {
+		n.isHandler = true
+	} else {
+		n.isHandler = n.methodHandler.isHandler()
+	}
 }
 
 func (n *node) findHandler(method string) HandlerFunc {
@@ -343,6 +379,8 @@ func (r *Router) Find(method, path string, c Context) {
 	currentNode := r.tree // Current node as root
 
 	var (
+		previousBestMatchNode *node
+		matchedHandler        HandlerFunc
 		// search stores the remaining path to check for match. By each iteration we move from start of path to end of the path
 		// and search value gets shorter and shorter.
 		search      = path
@@ -362,10 +400,11 @@ func (r *Router) Find(method, path string, c Context) {
 		valid = currentNode != nil
 
 		// Next node type by priority
-		// NOTE: With the current implementation we never backtrack from an `any` route, so `previous.kind` is
-		// always `static` or `any`
-		// If this is changed then for any route next kind would be `static` and this statement should be changed
-		nextNodeKind = previous.kind + 1
+		if previous.kind == anyKind {
+			nextNodeKind = staticKind
+		} else {
+			nextNodeKind = previous.kind + 1
+		}
 
 		if fromKind == staticKind {
 			// when backtracking is done from static kind block we did not change search so nothing to restore
@@ -380,6 +419,7 @@ func (r *Router) Find(method, path string, c Context) {
 			// for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
 			// for that index as it would also contain part of path we cut off before moving into node we are backtracking from
 			searchIndex -= len(paramValues[paramIndex])
+			paramValues[paramIndex] = ""
 		}
 		search = path[searchIndex:]
 		return
@@ -421,7 +461,7 @@ func (r *Router) Find(method, path string, c Context) {
 				//	goto Any
 			} else {
 				// Not found (this should never be possible for static node we are looking currently)
-				return
+				break
 			}
 		}
 
@@ -429,9 +469,17 @@ func (r *Router) Find(method, path string, c Context) {
 		search = search[lcpLen:]
 		searchIndex = searchIndex + lcpLen
 
-		// Finish routing if no remaining search and we are on an leaf node
-		if search == "" && currentNode.ppath != "" {
-			break
+		// Finish routing if no remaining search and we are on a node with handler and matching method type
+		if search == "" && currentNode.isHandler {
+			// check if current node has handler registered for http method we are looking for. we store currentNode as
+			// best matching in case we do no find no more routes matching this path+method
+			if previousBestMatchNode == nil {
+				previousBestMatchNode = currentNode
+			}
+			if h := currentNode.findHandler(method); h != nil {
+				matchedHandler = h
+				break
+			}
 		}
 
 		// Static node
@@ -446,10 +494,16 @@ func (r *Router) Find(method, path string, c Context) {
 		// Param node
 		if child := currentNode.paramChild; search != "" && child != nil {
 			currentNode = child
-			// FIXME: when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
-			i, l := 0, len(search)
-			for ; i < l && search[i] != '/'; i++ {
+			i := 0
+			l := len(search)
+			if currentNode.isLeaf {
+				// when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
+				i = l
+			} else {
+				for ; i < l && search[i] != '/'; i++ {
+				}
 			}
+
 			paramValues[paramIndex] = search[:i]
 			paramIndex++
 			search = search[i:]
@@ -463,29 +517,50 @@ func (r *Router) Find(method, path string, c Context) {
 			// If any node is found, use remaining path for paramValues
 			currentNode = child
 			paramValues[len(currentNode.pnames)-1] = search
-			break
+			// update indexes/search in case we need to backtrack when no handler match is found
+			paramIndex++
+			searchIndex += +len(search)
+			search = ""
+
+			// check if current node has handler registered for http method we are looking for. we store currentNode as
+			// best matching in case we do no find no more routes matching this path+method
+			if previousBestMatchNode == nil {
+				previousBestMatchNode = currentNode
+			}
+			if h := currentNode.findHandler(method); h != nil {
+				matchedHandler = h
+				break
+			}
 		}
 
 		// Let's backtrack to the first possible alternative node of the decision path
 		nk, ok := backtrackToNextNodeKind(anyKind)
 		if !ok {
-			return // No other possibilities on the decision path
+			break // No other possibilities on the decision path
 		} else if nk == paramKind {
 			goto Param
 		} else if nk == anyKind {
 			goto Any
 		} else {
 			// Not found
-			return
+			break
 		}
 	}
 
-	ctx.handler = currentNode.findHandler(method)
-	ctx.path = currentNode.ppath
-	ctx.pnames = currentNode.pnames
+	if currentNode == nil && previousBestMatchNode == nil {
+		return // nothing matched at all
+	}
 
-	if ctx.handler == nil {
+	if matchedHandler != nil {
+		ctx.handler = matchedHandler
+	} else {
+		// use previous match as basis. although we have no matching handler we have path match.
+		// so we can send http.StatusMethodNotAllowed (405) instead of http.StatusNotFound (404)
+		currentNode = previousBestMatchNode
 		ctx.handler = currentNode.checkMethodNotAllowed()
 	}
+	ctx.path = currentNode.ppath
+	ctx.pnames = currentNode.pnames
+
 	return
 }


Test patch:
diff --git a/bind_test.go b/bind_test.go
index 7339803..ff03370 100644
--- a/bind_test.go
+++ b/bind_test.go
@@ -100,6 +100,9 @@ type (
 	Struct      struct {
 		Foo string
 	}
+	Bar struct {
+		Baz int `json:"baz" query:"baz"`
+	}
 )
 
 func (t *Timestamp) UnmarshalParam(src string) error {
@@ -330,6 +333,46 @@ func TestBindUnmarshalParamPtr(t *testing.T) {
 	}
 }
 
+func TestBindUnmarshalParamAnonymousFieldPtr(t *testing.T) {
+	e := New()
+	req := httptest.NewRequest(http.MethodGet, "/?baz=1", nil)
+	rec := httptest.NewRecorder()
+	c := e.NewContext(req, rec)
+	result := struct {
+		*Bar
+	}{&Bar{}}
+	err := c.Bind(&result)
+	if assert.NoError(t, err) {
+		assert.Equal(t, 1, result.Baz)
+	}
+}
+
+func TestBindUnmarshalParamAnonymousFieldPtrNil(t *testing.T) {
+	e := New()
+	req := httptest.NewRequest(http.MethodGet, "/?baz=1", nil)
+	rec := httptest.NewRecorder()
+	c := e.NewContext(req, rec)
+	result := struct {
+		*Bar
+	}{}
+	err := c.Bind(&result)
+	if assert.NoError(t, err) {
+		assert.Nil(t, result.Bar)
+	}
+}
+
+func TestBindUnmarshalParamAnonymousFieldPtrCustomTag(t *testing.T) {
+	e := New()
+	req := httptest.NewRequest(http.MethodGet, `/?bar={"baz":100}&baz=1`, nil)
+	rec := httptest.NewRecorder()
+	c := e.NewContext(req, rec)
+	result := struct {
+		*Bar `json:"bar" query:"bar"`
+	}{&Bar{}}
+	err := c.Bind(&result)
+	assert.Contains(t, err.Error(), "query/param/form tags are not allowed with anonymous struct field")
+}
+
 func TestBindUnmarshalTextPtr(t *testing.T) {
 	e := New()
 	req := httptest.NewRequest(GET, "/?ts=2016-12-06T19:09:05Z", nil)
diff --git a/context_test.go b/context_test.go
index 963c91e..2c61ffb 100644
--- a/context_test.go
+++ b/context_test.go
@@ -464,7 +464,9 @@ func TestContextPath(t *testing.T) {
 	e := New()
 	r := e.Router()
 
-	r.Add(http.MethodGet, "/users/:id", nil)
+	handler := func(c Context) error { return c.String(http.StatusOK, "OK") }
+
+	r.Add(http.MethodGet, "/users/:id", handler)
 	c := e.NewContext(nil, nil)
 	r.Find(http.MethodGet, "/users/1", c)
 
@@ -472,7 +474,7 @@ func TestContextPath(t *testing.T) {
 
 	assert.Equal("/users/:id", c.Path())
 
-	r.Add(http.MethodGet, "/users/:uid/files/:fid", nil)
+	r.Add(http.MethodGet, "/users/:uid/files/:fid", handler)
 	c = e.NewContext(nil, nil)
 	r.Find(http.MethodGet, "/users/1/files/1", c)
 	assert.Equal("/users/:uid/files/:fid", c.Path())
diff --git a/middleware/jwt_test.go b/middleware/jwt_test.go
index 205721a..1a02659 100644
--- a/middleware/jwt_test.go
+++ b/middleware/jwt_test.go
@@ -1,6 +1,7 @@
 package middleware
 
 import (
+	"errors"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -178,6 +179,14 @@ func TestJWT(t *testing.T) {
 			hdrCookie: "jwt=" + token,
 			info:      "Valid cookie method",
 		},
+		{
+			config: JWTConfig{
+				SigningKey:  validKey,
+				TokenLookup: "query:jwt,cookie:jwt",
+			},
+			hdrCookie: "jwt=" + token,
+			info:      "Multiple jwt lookuop",
+		},
 		{
 			config: JWTConfig{
 				SigningKey:  validKey,
@@ -220,6 +229,35 @@ func TestJWT(t *testing.T) {
 			expErrCode: http.StatusBadRequest,
 			info:       "Empty form field",
 		},
+		{
+			hdrAuth: validAuth,
+			config: JWTConfig{
+				KeyFunc: func(*jwt.Token) (interface{}, error) {
+					return validKey, nil
+				},
+			},
+			info: "Valid JWT with a valid key using a user-defined KeyFunc",
+		},
+		{
+			hdrAuth: validAuth,
+			config: JWTConfig{
+				KeyFunc: func(*jwt.Token) (interface{}, error) {
+					return invalidKey, nil
+				},
+			},
+			expErrCode: http.StatusUnauthorized,
+			info:       "Valid JWT with an invalid key using a user-defined KeyFunc",
+		},
+		{
+			hdrAuth: validAuth,
+			config: JWTConfig{
+				KeyFunc: func(*jwt.Token) (interface{}, error) {
+					return nil, errors.New("faulty KeyFunc")
+				},
+			},
+			expErrCode: http.StatusUnauthorized,
+			info:       "Token verification does not pass using a user-defined KeyFunc",
+		},
 	} {
 		if tc.reqURL == "" {
 			tc.reqURL = "/"
diff --git a/middleware/key_auth_test.go b/middleware/key_auth_test.go
index b874898..476b402 100644
--- a/middleware/key_auth_test.go
+++ b/middleware/key_auth_test.go
@@ -1,9 +1,9 @@
 package middleware
 
 import (
+	"errors"
 	"net/http"
 	"net/http/httptest"
-	"net/url"
 	"strings"
 	"testing"
 
@@ -11,65 +11,225 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+func testKeyValidator(key string, c echo.Context) (bool, error) {
+	switch key {
+	case "valid-key":
+		return true, nil
+	case "error-key":
+		return false, errors.New("some user defined error")
+	default:
+		return false, nil
+	}
+}
+
 func TestKeyAuth(t *testing.T) {
+	handlerCalled := false
+	handler := func(c echo.Context) error {
+		handlerCalled = true
+		return c.String(http.StatusOK, "test")
+	}
+	middlewareChain := KeyAuth(testKeyValidator)(handler)
+
 	e := echo.New()
 	req := httptest.NewRequest(http.MethodGet, "/", nil)
+	req.Header.Set(echo.HeaderAuthorization, "Bearer valid-key")
 	rec := httptest.NewRecorder()
 	c := e.NewContext(req, rec)
-	config := KeyAuthConfig{
-		Validator: func(key string, c echo.Context) (bool, error) {
-			return key == "valid-key", nil
-		},
-	}
-	h := KeyAuthWithConfig(config)(func(c echo.Context) error {
-		return c.String(http.StatusOK, "test")
-	})
 
-	assert := assert.New(t)
+	err := middlewareChain(c)
 
-	// Valid key
-	auth := DefaultKeyAuthConfig.AuthScheme + " " + "valid-key"
-	req.Header.Set(echo.HeaderAuthorization, auth)
-	assert.NoError(h(c))
+	assert.NoError(t, err)
+	assert.True(t, handlerCalled)
+}
 
-	// Invalid key
-	auth = DefaultKeyAuthConfig.AuthScheme + " " + "invalid-key"
-	req.Header.Set(echo.HeaderAuthorization, auth)
-	he := h(c).(*echo.HTTPError)
-	assert.Equal(http.StatusUnauthorized, he.Code)
+func TestKeyAuthWithConfig(t *testing.T) {
+	var testCases = []struct {
+		name                string
+		givenRequestFunc    func() *http.Request
+		givenRequest        func(req *http.Request)
+		whenConfig          func(conf *KeyAuthConfig)
+		expectHandlerCalled bool
+		expectError         string
+	}{
+		{
+			name: "ok, defaults, key from header",
+			givenRequest: func(req *http.Request) {
+				req.Header.Set(echo.HeaderAuthorization, "Bearer valid-key")
+			},
+			expectHandlerCalled: true,
+		},
+		{
+			name: "ok, custom skipper",
+			givenRequest: func(req *http.Request) {
+				req.Header.Set(echo.HeaderAuthorization, "Bearer error-key")
+			},
+			whenConfig: func(conf *KeyAuthConfig) {
+				conf.Skipper = func(context echo.Context) bool {
+					return true
+				}
+			},
+			expectHandlerCalled: true,
+		},
+		{
+			name: "nok, defaults, invalid key from header, Authorization: Bearer",
+			givenRequest: func(req *http.Request) {
+				req.Header.Set(echo.HeaderAuthorization, "Bearer invalid-key")
+			},
+			expectHandlerCalled: false,
+			expectError:         "code=401, message=Unauthorized",
+		},
+		{
+			name: "nok, defaults, invalid scheme in header",
+			givenRequest: func(req *http.Request) {
+				req.Header.Set(echo.HeaderAuthorization, "Bear valid-key")
+			},
+			expectHandlerCalled: false,
+			expectError:         "code=400, message=invalid key in the request header",
+		},
+		{
+			name:                "nok, defaults, missing header",
+			givenRequest:        func(req *http.Request) {},
+			expectHandlerCalled: false,
+			expectError:         "code=400, message=missing key in request header",
+		},
+		{
+			name: "ok, custom key lookup, header",
+			givenRequest: func(req *http.Request) {
+				req.Header.Set("API-Key", "valid-key")
+			},
+			whenConfig: func(conf *KeyAuthConfig) {
+				conf.KeyLookup = "header:API-Key"
+			},
+			expectHandlerCalled: true,
+		},
+		{
+			name: "nok, custom key lookup, missing header",
+			givenRequest: func(req *http.Request) {
+			},
+			whenConfig: func(conf *KeyAuthConfig) {
+				conf.KeyLookup = "header:API-Key"
+			},
+			expectHandlerCalled: false,
+			expectError:         "code=400, message=missing key in request header",
+		},
+		{
+			name: "ok, custom key lookup, query",
+			givenRequest: func(req *http.Request) {
+				q := req.URL.Query()
+				q.Add("key", "valid-key")
+				req.URL.RawQuery = q.Encode()
+			},
+			whenConfig: func(conf *KeyAuthConfig) {
+				conf.KeyLookup = "query:key"
+			},
+			expectHandlerCalled: true,
+		},
+		{
+			name: "nok, custom key lookup, missing query param",
+			whenConfig: func(conf *KeyAuthConfig) {
+				conf.KeyLookup = "query:key"
+			},
+			expectHandlerCalled: false,
+			expectError:         "code=400, message=missing key in the query string",
+		},
+		{
+			name: "ok, custom key lookup, form",
+			givenRequestFunc: func() *http.Request {
+				req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("key=valid-key"))
+				req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
+				return req
+			},
+			whenConfig: func(conf *KeyAuthConfig) {
+				conf.KeyLookup = "form:key"
+			},
+			expectHandlerCalled: true,
+		},
+		{
+			name: "nok, custom key lookup, missing key in form",
+			givenRequestFunc: func() *http.Request {
+				req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("xxx=valid-key"))
+				req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
+				return req
+			},
+			whenConfig: func(conf *KeyAuthConfig) {
+				conf.KeyLookup = "form:key"
+			},
+			expectHandlerCalled: false,
+			expectError:         "code=400, message=missing key in the form",
+		},
+		{
+			name: "nok, custom errorHandler, error from extractor",
+			whenConfig: func(conf *KeyAuthConfig) {
+				conf.KeyLookup = "header:token"
+				conf.ErrorHandler = func(err error, context echo.Context) error {
+					httpError := echo.NewHTTPError(http.StatusTeapot, "custom")
+					httpError.Internal = err
+					return httpError
+				}
+			},
+			expectHandlerCalled: false,
+			expectError:         "code=418, message=custom, internal=missing key in request header",
+		},
+		{
+			name: "nok, custom errorHandler, error from validator",
+			givenRequest: func(req *http.Request) {
+				req.Header.Set(echo.HeaderAuthorization, "Bearer error-key")
+			},
+			whenConfig: func(conf *KeyAuthConfig) {
+				conf.ErrorHandler = func(err error, context echo.Context) error {
+					httpError := echo.NewHTTPError(http.StatusTeapot, "custom")
+					httpError.Internal = err
+					return httpError
+				}
+			},
+			expectHandlerCalled: false,
+			expectError:         "code=418, message=custom, internal=some user defined error",
+		},
+		{
+			name: "nok, defaults, error from validator",
+			givenRequest: func(req *http.Request) {
+				req.Header.Set(echo.HeaderAuthorization, "Bearer error-key")
+			},
+			whenConfig:          func(conf *KeyAuthConfig) {},
+			expectHandlerCalled: false,
+			expectError:         "code=401, message=invalid key, internal=some user defined error",
+		},
+	}
 
-	// Missing Authorization header
-	req.Header.Del(echo.HeaderAuthorization)
-	he = h(c).(*echo.HTTPError)
-	assert.Equal(http.StatusBadRequest, he.Code)
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			handlerCalled := false
+			handler := func(c echo.Context) error {
+				handlerCalled = true
+				return c.String(http.StatusOK, "test")
+			}
+			config := KeyAuthConfig{
+				Validator: testKeyValidator,
+			}
+			if tc.whenConfig != nil {
+				tc.whenConfig(&config)
+			}
+			middlewareChain := KeyAuthWithConfig(config)(handler)
 
-	// Key from custom header
-	config.KeyLookup = "header:API-Key"
-	h = KeyAuthWithConfig(config)(func(c echo.Context) error {
-		return c.String(http.StatusOK, "test")
-	})
-	req.Header.Set("API-Key", "valid-key")
-	assert.NoError(h(c))
+			e := echo.New()
+			req := httptest.NewRequest(http.MethodGet, "/", nil)
+			if tc.givenRequestFunc != nil {
+				req = tc.givenRequestFunc()
+			}
+			if tc.givenRequest != nil {
+				tc.givenRequest(req)
+			}
+			rec := httptest.NewRecorder()
+			c := e.NewContext(req, rec)
 
-	// Key from query string
-	config.KeyLookup = "query:key"
-	h = KeyAuthWithConfig(config)(func(c echo.Context) error {
-		return c.String(http.StatusOK, "test")
-	})
-	q := req.URL.Query()
-	q.Add("key", "valid-key")
-	req.URL.RawQuery = q.Encode()
-	assert.NoError(h(c))
+			err := middlewareChain(c)
 
-	// Key from form
-	config.KeyLookup = "form:key"
-	h = KeyAuthWithConfig(config)(func(c echo.Context) error {
-		return c.String(http.StatusOK, "test")
-	})
-	f := make(url.Values)
-	f.Set("key", "valid-key")
-	req = httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
-	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
-	c = e.NewContext(req, rec)
-	assert.NoError(h(c))
+			assert.Equal(t, tc.expectHandlerCalled, handlerCalled)
+			if tc.expectError != "" {
+				assert.EqualError(t, err, tc.expectError)
+			} else {
+				assert.NoError(t, err)
+			}
+		})
+	}
 }
diff --git a/middleware/static_1_16_test.go b/middleware/static_1_16_test.go
new file mode 100644
index 0000000..53e02f7
--- /dev/null
+++ b/middleware/static_1_16_test.go
@@ -0,0 +1,106 @@
+// +build go1.16
+
+package middleware
+
+import (
+	"io/fs"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"testing"
+	"testing/fstest"
+
+	"github.com/labstack/echo/v4"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestStatic_CustomFS(t *testing.T) {
+	var testCases = []struct {
+		name           string
+		filesystem     fs.FS
+		root           string
+		whenURL        string
+		expectContains string
+		expectCode     int
+	}{
+		{
+			name:           "ok, serve index with Echo message",
+			whenURL:        "/",
+			filesystem:     os.DirFS("../_fixture"),
+			expectCode:     http.StatusOK,
+			expectContains: "<title>Echo</title>",
+		},
+
+		{
+			name:           "ok, serve index with Echo message",
+			whenURL:        "/_fixture/",
+			filesystem:     os.DirFS(".."),
+			expectCode:     http.StatusOK,
+			expectContains: "<title>Echo</title>",
+		},
+		{
+			name:    "ok, serve file from map fs",
+			whenURL: "/file.txt",
+			filesystem: fstest.MapFS{
+				"file.txt": &fstest.MapFile{Data: []byte("file.txt is ok")},
+			},
+			expectCode:     http.StatusOK,
+			expectContains: "file.txt is ok",
+		},
+		{
+			name:       "nok, missing file in map fs",
+			whenURL:    "/file.txt",
+			expectCode: http.StatusNotFound,
+			filesystem: fstest.MapFS{
+				"file2.txt": &fstest.MapFile{Data: []byte("file2.txt is ok")},
+			},
+		},
+		{
+			name:    "nok, file is not a subpath of root",
+			whenURL: `/../../secret.txt`,
+			root:    "/nested/folder",
+			filesystem: fstest.MapFS{
+				"secret.txt": &fstest.MapFile{Data: []byte("this is a secret")},
+			},
+			expectCode: http.StatusNotFound,
+		},
+		{
+			name:       "nok, backslash is forbidden",
+			whenURL:    `/..\..\secret.txt`,
+			expectCode: http.StatusNotFound,
+			root:       "/nested/folder",
+			filesystem: fstest.MapFS{
+				"secret.txt": &fstest.MapFile{Data: []byte("this is a secret")},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			e := echo.New()
+
+			config := StaticConfig{
+				Root:       ".",
+				Filesystem: http.FS(tc.filesystem),
+			}
+
+			if tc.root != "" {
+				config.Root = tc.root
+			}
+
+			middlewareFunc := StaticWithConfig(config)
+			e.Use(middlewareFunc)
+
+			req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
+			rec := httptest.NewRecorder()
+
+			e.ServeHTTP(rec, req)
+
+			assert.Equal(t, tc.expectCode, rec.Code)
+			if tc.expectContains != "" {
+				responseBody := rec.Body.String()
+				assert.Contains(t, responseBody, tc.expectContains)
+			}
+		})
+	}
+}
diff --git a/middleware/static_test.go b/middleware/static_test.go
index 8c0c97d..af6641f 100644
--- a/middleware/static_test.go
+++ b/middleware/static_test.go
@@ -94,6 +94,32 @@ func TestStatic(t *testing.T) {
 			expectCode:     http.StatusNotFound,
 			expectContains: "{\"message\":\"Not Found\"}\n",
 		},
+		{
+			name:           "ok, do not serve file, when a handler took care of the request",
+			whenURL:        "/regular-handler",
+			expectCode:     http.StatusOK,
+			expectContains: "ok",
+		},
+		{
+			name: "nok, when html5 fail if the index file does not exist",
+			givenConfig: &StaticConfig{
+				Root:  "../_fixture",
+				HTML5: true,
+				Index: "missing.html",
+			},
+			whenURL:    "/random",
+			expectCode: http.StatusInternalServerError,
+		},
+		{
+			name: "ok, serve from http.FileSystem",
+			givenConfig: &StaticConfig{
+				Root:       "_fixture",
+				Filesystem: http.Dir(".."),
+			},
+			whenURL:        "/",
+			expectCode:     http.StatusOK,
+			expectContains: "<title>Echo</title>",
+		},
 	}
 
 	for _, tc := range testCases {
@@ -115,6 +141,9 @@ func TestStatic(t *testing.T) {
 			} else {
 				// middleware is on root level
 				e.Use(middlewareFunc)
+				e.GET("/regular-handler", func(c echo.Context) error {
+					return c.String(http.StatusOK, "ok")
+				})
 			}
 
 			req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
diff --git a/router_test.go b/router_test.go
index 47e4994..71cedf8 100644
--- a/router_test.go
+++ b/router_test.go
@@ -692,11 +692,11 @@ func TestRouterParam(t *testing.T) {
 			expectRoute: "/users/:id",
 			expectParam: map[string]string{"id": "1"},
 		},
-		{ // FIXME: this documents current implementation (slash at end is problematic)
+		{
 			name:        "route /users/1/ to /users/:id",
 			whenURL:     "/users/1/",
-			expectRoute: nil, // FIXME: should be "/users/:id",
-			expectParam: nil, // FIXME: should be  map[string]string{"id": "1/"},
+			expectRoute: "/users/:id",
+			expectParam: map[string]string{"id": "1/"},
 		},
 	}
 
@@ -716,6 +716,69 @@ func TestRouterParam(t *testing.T) {
 	}
 }
 
+func TestMethodNotAllowedAndNotFound(t *testing.T) {
+	e := New()
+	r := e.router
+
+	// Routes
+	r.Add(http.MethodGet, "/*", handlerFunc)
+	r.Add(http.MethodPost, "/users/:id", handlerFunc)
+
+	var testCases = []struct {
+		name        string
+		whenMethod  string
+		whenURL     string
+		expectRoute interface{}
+		expectParam map[string]string
+		expectError error
+	}{
+		{
+			name:        "exact match for route+method",
+			whenMethod:  http.MethodPost,
+			whenURL:     "/users/1",
+			expectRoute: "/users/:id",
+			expectParam: map[string]string{"id": "1"},
+		},
+		{
+			name:        "matches node but not method. sends 405 from best match node",
+			whenMethod:  http.MethodPut,
+			whenURL:     "/users/1",
+			expectRoute: nil,
+			expectError: ErrMethodNotAllowed,
+		},
+		{
+			name:        "best match is any route up in tree",
+			whenMethod:  http.MethodGet,
+			whenURL:     "/users/1",
+			expectRoute: "/*",
+			expectParam: map[string]string{"*": "users/1"},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			c := e.NewContext(nil, nil).(*context)
+
+			method := http.MethodGet
+			if tc.whenMethod != "" {
+				method = tc.whenMethod
+			}
+			r.Find(method, tc.whenURL, c)
+			err := c.handler(c)
+
+			if tc.expectError != nil {
+				assert.Equal(t, tc.expectError, err)
+			} else {
+				assert.NoError(t, err)
+			}
+			assert.Equal(t, tc.expectRoute, c.Get("path"))
+			for param, expectedValue := range tc.expectParam {
+				assert.Equal(t, expectedValue, c.Param(param))
+			}
+			checkUnusedParamValues(t, c, tc.expectParam)
+		})
+	}
+}
+
 func TestRouterTwoParam(t *testing.T) {
 	e := New()
 	r := e.router
@@ -740,8 +803,8 @@ func TestRouterParamWithSlash(t *testing.T) {
 	r.Find(http.MethodGet, "/a/1/c/d/2/3", c) // `2/3` should mapped to path `/a/:b/c/d/:e` and into `:e`
 
 	err := c.handler(c)
-	assert.Equal(t, nil, c.Get("path"))                      // FIXME: should be "/a/:b/c/d/:e"
-	assert.EqualError(t, err, "code=404, message=Not Found") // FIXME: should be .NoError()
+	assert.Equal(t, "/a/:b/c/d/:e", c.Get("path"))
+	assert.NoError(t, err)
 }
 
 // Issue #1754 - router needs to backtrack multiple levels upwards in tree to find the matching route
@@ -2004,10 +2067,10 @@ func TestRouterParam1466(t *testing.T) {
 			expectRoute: "/users/:username",
 			expectParam: map[string]string{"username": "sharewithme"},
 		},
-		{
+		{ // route `/users/signup` is registered for POST. so param route `/users/:username` (lesser priority) is matched as it has GET handler
 			whenURL:     "/users/signup",
-			expectRoute: nil, // method not found as this route is for POST but request is for GET
-			expectParam: map[string]string{"username": ""},
+			expectRoute: "/users/:username",
+			expectParam: map[string]string{"username": "signup"},
 		},
 		// Additional assertions for #1479
 		{