Repository: labstack/echo
Pull request #1443 (2019-11-11 06:27:41)

added installation command in guide
I have come across many people doing default go get for installation which installs version 3 not latest version so I added installation command which helps for people who are new go Go


Patch:
diff --git a/.travis.yml b/.travis.yml
index 200f508..a1fc876 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,8 @@ go:
   - 1.12.x
   - 1.13.x
   - tip
+env:
+  - GO111MODULE=on
 install:
   - go get -v golang.org/x/lint/golint
 script:
diff --git a/README.md b/README.md
index 0da0312..c57d478 100644
--- a/README.md
+++ b/README.md
@@ -50,6 +50,13 @@ Lower is better!
 
 ## [Guide](https://echo.labstack.com/guide)
 
+### Installation
+
+```go
+// go get github.com/labstack/echo/{version}
+go get github.com/labstack/echo/v4
+```
+
 ### Example
 
 ```go
diff --git a/bind.go b/bind.go
index c8c88bb..f891474 100644
--- a/bind.go
+++ b/bind.go
@@ -115,7 +115,7 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
 		if inputFieldName == "" {
 			inputFieldName = typeField.Name
 			// If tag is nil, we inspect if the field is a struct.
-			if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
+			if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
 				if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
 					return err
 				}
@@ -129,9 +129,8 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
 			// url params are bound case sensitive which is inconsistent.  To
 			// fix this we must check all of the map values in a
 			// case-insensitive search.
-			inputFieldName = strings.ToLower(inputFieldName)
 			for k, v := range data {
-				if strings.ToLower(k) == inputFieldName {
+				if strings.EqualFold(k, inputFieldName) {
 					inputValue = v
 					exists = true
 					break
@@ -221,40 +220,13 @@ func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bo
 	}
 }
 
-// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler
-func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) {
-	ptr := reflect.New(field.Type())
-	if ptr.CanInterface() {
-		iface := ptr.Interface()
-		if unmarshaler, ok := iface.(BindUnmarshaler); ok {
-			return unmarshaler, ok
-		}
-	}
-	return nil, false
-}
-
-// textUnmarshaler attempts to unmarshal a reflect.Value into a TextUnmarshaler
-func textUnmarshaler(field reflect.Value) (encoding.TextUnmarshaler, bool) {
-	ptr := reflect.New(field.Type())
-	if ptr.CanInterface() {
-		iface := ptr.Interface()
-		if unmarshaler, ok := iface.(encoding.TextUnmarshaler); ok {
-			return unmarshaler, ok
-		}
-	}
-	return nil, false
-}
-
 func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
-	if unmarshaler, ok := bindUnmarshaler(field); ok {
-		err := unmarshaler.UnmarshalParam(value)
-		field.Set(reflect.ValueOf(unmarshaler).Elem())
-		return true, err
+	fieldIValue := field.Addr().Interface()
+	if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok {
+		return true, unmarshaler.UnmarshalParam(value)
 	}
-	if unmarshaler, ok := textUnmarshaler(field); ok {
-		err := unmarshaler.UnmarshalText([]byte(value))
-		field.Set(reflect.ValueOf(unmarshaler).Elem())
-		return true, err
+	if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok {
+		return true, unmarshaler.UnmarshalText([]byte(value))
 	}
 
 	return false, nil
diff --git a/context.go b/context.go
index 91e7c04..0046e5d 100644
--- a/context.go
+++ b/context.go
@@ -183,6 +183,9 @@ type (
 		// Logger returns the `Logger` instance.
 		Logger() Logger
 
+		// Set the logger
+		SetLogger(l Logger)
+
 		// Echo returns the `Echo` instance.
 		Echo() *Echo
 
@@ -202,6 +205,7 @@ type (
 		handler  HandlerFunc
 		store    Map
 		echo     *Echo
+		logger   Logger
 		lock     sync.RWMutex
 	}
 )
@@ -308,7 +312,10 @@ func (c *context) ParamValues() []string {
 }
 
 func (c *context) SetParamValues(values ...string) {
-	c.pvalues = values
+	// NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam at all times
+	for i, val := range values {
+		c.pvalues[i] = val
+	}
 }
 
 func (c *context) QueryParam(name string) string {
@@ -598,9 +605,17 @@ func (c *context) SetHandler(h HandlerFunc) {
 }
 
 func (c *context) Logger() Logger {
+	res := c.logger
+	if res != nil {
+		return res
+	}
 	return c.echo.Logger
 }
 
+func (c *context) SetLogger(l Logger) {
+	c.logger = l
+}
+
 func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
 	c.request = r
 	c.response.reset(w)
@@ -609,6 +624,7 @@ func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
 	c.store = nil
 	c.path = ""
 	c.pnames = nil
+	c.logger = nil
 	// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
 	for i := 0; i < *c.echo.maxParam; i++ {
 		c.pvalues[i] = ""
diff --git a/echo.go b/echo.go
index 5f6671e..ed65888 100644
--- a/echo.go
+++ b/echo.go
@@ -59,6 +59,8 @@ import (
 	"github.com/labstack/gommon/log"
 	"golang.org/x/crypto/acme"
 	"golang.org/x/crypto/acme/autocert"
+	"golang.org/x/net/http2"
+	"golang.org/x/net/http2/h2c"
 )
 
 type (
@@ -227,7 +229,7 @@ const (
 
 const (
 	// Version of Echo
-	Version = "4.1.11"
+	Version = "4.1.14"
 	website = "https://echo.labstack.com"
 	// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
 	banner = `
@@ -723,6 +725,34 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
 	return s.Serve(e.TLSListener)
 }
 
+// StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext).
+func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
+	// Setup
+	s := e.Server
+	s.Addr = address
+	e.colorer.SetOutput(e.Logger.Output())
+	s.ErrorLog = e.StdLogger
+	s.Handler = h2c.NewHandler(e, h2s)
+	if e.Debug {
+		e.Logger.SetLevel(log.DEBUG)
+	}
+
+	if !e.HideBanner {
+		e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
+	}
+
+	if e.Listener == nil {
+		e.Listener, err = newListener(s.Addr)
+		if err != nil {
+			return err
+		}
+	}
+	if !e.HidePort {
+		e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
+	}
+	return s.Serve(e.Listener)
+}
+
 // Close immediately stops the server.
 // It internally calls `http.Server#Close()`.
 func (e *Echo) Close() error {
diff --git a/go.mod b/go.mod
index 5a058ee..17e57fe 100644
--- a/go.mod
+++ b/go.mod
@@ -4,14 +4,13 @@ go 1.12
 
 require (
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
-	github.com/labstack/echo v3.3.10+incompatible // indirect
 	github.com/labstack/gommon v0.3.0
 	github.com/mattn/go-colorable v0.1.4 // indirect
-	github.com/mattn/go-isatty v0.0.10 // indirect
+	github.com/mattn/go-isatty v0.0.11 // indirect
 	github.com/stretchr/testify v1.4.0
 	github.com/valyala/fasttemplate v1.1.0
-	golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
-	golang.org/x/net v0.0.0-20191021144547-ec77196f6094 // indirect
-	golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb // indirect
+	golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
+	golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa
+	golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 // indirect
 	golang.org/x/text v0.3.2 // indirect
 )
diff --git a/go.sum b/go.sum
index ad7345d..08ef80e 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,6 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
-github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
 github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
@@ -13,8 +11,8 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
-github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
-github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -27,27 +25,26 @@ github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU
 github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
 github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
+golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8=
-golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb h1:ZxSglHghKPYD8WDeRUzRJrUJtDF0PxsTUSxyqr9/5BI=
-golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 h1:JA8d3MPx/IToSyXZG/RhwYEtfrKO1Fxrqe8KrkiLXKM=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/middleware/jwt.go b/middleware/jwt.go
index 55a9863..3c7c486 100644
--- a/middleware/jwt.go
+++ b/middleware/jwt.go
@@ -25,7 +25,7 @@ type (
 		// ErrorHandler defines a function which is executed for an invalid token.
 		// It may be used to define a custom JWT error.
 		ErrorHandler JWTErrorHandler
-		
+
 		// ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context.
 		ErrorHandlerWithContext JWTErrorHandlerWithContext
 
@@ -74,7 +74,7 @@ type (
 
 	// JWTErrorHandlerWithContext is almost identical to JWTErrorHandler, but it's passed the current context.
 	JWTErrorHandlerWithContext func(error, echo.Context) error
-	
+
 	jwtExtractor func(echo.Context) (string, error)
 )
 
@@ -183,7 +183,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
 				if config.ErrorHandler != nil {
 					return config.ErrorHandler(err)
 				}
-				
+
 				if config.ErrorHandlerWithContext != nil {
 					return config.ErrorHandlerWithContext(err, c)
 				}
@@ -210,7 +210,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
 				return config.ErrorHandler(err)
 			}
 			if config.ErrorHandlerWithContext != nil {
-					return config.ErrorHandlerWithContext(err, c)
+				return config.ErrorHandlerWithContext(err, c)
 			}
 			return &echo.HTTPError{
 				Code:     http.StatusUnauthorized,
diff --git a/router.go b/router.go
index 70bf409..9db9ea9 100644
--- a/router.go
+++ b/router.go
@@ -1,6 +1,9 @@
 package echo
 
-import "net/http"
+import (
+	"net/http"
+	"strings"
+)
 
 type (
 	// Router is the registry of all registered routes for an `Echo` instance for
@@ -20,8 +23,8 @@ type (
 		pnames        []string
 		methodHandler *methodHandler
 	}
-	kind uint8
-	children []*node
+	kind          uint8
+	children      []*node
 	methodHandler struct {
 		connect  HandlerFunc
 		delete   HandlerFunc
@@ -133,6 +136,11 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 			// Split node
 			n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)
 
+			// Update parent path for all children to new node
+			for _, child := range cn.children {
+				child.parent = n
+			}
+
 			// Reset parent node
 			cn.kind = skind
 			cn.label = cn.prefix[0]
@@ -336,7 +344,6 @@ func (r *Router) Find(method, path string, c Context) {
 			}
 		}
 
-
 		if l == pl {
 			// Continue search
 			search = search[l:]
@@ -398,16 +405,37 @@ func (r *Router) Find(method, path string, c Context) {
 	Any:
 		if cn = cn.findChildByKind(akind); cn == nil {
 			if nn != nil {
-				cn = nn
-				nn = cn.parent // Next (Issue #954)
-				if nn != nil {
-					nk = nn.kind
-				}
+				// No next node to go down in routing (issue #954)
+				// Find nearest "any" route going up the routing tree
 				search = ns
-				if nk == pkind {
+				np := nn.parent
+				// Consider param route one level up only
+				// if no slash is remaining in search string
+				if cn = nn.findChildByKind(pkind); cn != nil && strings.IndexByte(ns, '/') == -1 {
+					pvalues[len(cn.pnames)-1] = search
+					break
+				} else if cn != nil && strings.IndexByte(ns, '/') != 1 {
+					// If slash is present, it means that this is a parameterized route.
+					cn = cn.parent
 					goto Param
-				} else if nk == akind {
-					goto Any
+				}
+				for {
+					np = nn.parent
+					if cn = nn.findChildByKind(akind); cn != nil {
+						break
+					}
+					if np == nil {
+						break // no further parent nodes in tree, abort
+					}
+					var str strings.Builder
+					str.WriteString(nn.prefix)
+					str.WriteString(search)
+					search = str.String()
+					nn = np
+				}
+				if cn != nil { // use the found "any" route and update path
+					pvalues[len(cn.pnames)-1] = search
+					break
 				}
 			}
 			return // Not found


Test patch:
diff --git a/bind_test.go b/bind_test.go
index 84ac871..943cfd5 100644
--- a/bind_test.go
+++ b/bind_test.go
@@ -56,6 +56,43 @@ type (
 		Tptr        *Timestamp
 		SA          StringArray
 	}
+	bindTestStructWithTags struct {
+		I           int      `json:"I" form:"I"`
+		PtrI        *int     `json:"PtrI" form:"PtrI"`
+		I8          int8     `json:"I8" form:"I8"`
+		PtrI8       *int8    `json:"PtrI8" form:"PtrI8"`
+		I16         int16    `json:"I16" form:"I16"`
+		PtrI16      *int16   `json:"PtrI16" form:"PtrI16"`
+		I32         int32    `json:"I32" form:"I32"`
+		PtrI32      *int32   `json:"PtrI32" form:"PtrI32"`
+		I64         int64    `json:"I64" form:"I64"`
+		PtrI64      *int64   `json:"PtrI64" form:"PtrI64"`
+		UI          uint     `json:"UI" form:"UI"`
+		PtrUI       *uint    `json:"PtrUI" form:"PtrUI"`
+		UI8         uint8    `json:"UI8" form:"UI8"`
+		PtrUI8      *uint8   `json:"PtrUI8" form:"PtrUI8"`
+		UI16        uint16   `json:"UI16" form:"UI16"`
+		PtrUI16     *uint16  `json:"PtrUI16" form:"PtrUI16"`
+		UI32        uint32   `json:"UI32" form:"UI32"`
+		PtrUI32     *uint32  `json:"PtrUI32" form:"PtrUI32"`
+		UI64        uint64   `json:"UI64" form:"UI64"`
+		PtrUI64     *uint64  `json:"PtrUI64" form:"PtrUI64"`
+		B           bool     `json:"B" form:"B"`
+		PtrB        *bool    `json:"PtrB" form:"PtrB"`
+		F32         float32  `json:"F32" form:"F32"`
+		PtrF32      *float32 `json:"PtrF32" form:"PtrF32"`
+		F64         float64  `json:"F64" form:"F64"`
+		PtrF64      *float64 `json:"PtrF64" form:"PtrF64"`
+		S           string   `json:"S" form:"S"`
+		PtrS        *string  `json:"PtrS" form:"PtrS"`
+		cantSet     string
+		DoesntExist string      `json:"DoesntExist" form:"DoesntExist"`
+		GoT         time.Time   `json:"GoT" form:"GoT"`
+		GoTptr      *time.Time  `json:"GoTptr" form:"GoTptr"`
+		T           Timestamp   `json:"T" form:"T"`
+		Tptr        *Timestamp  `json:"Tptr" form:"Tptr"`
+		SA          StringArray `json:"SA" form:"SA"`
+	}
 	Timestamp   time.Time
 	TA          []Timestamp
 	StringArray []string
@@ -295,6 +332,7 @@ func TestBindbindData(t *testing.T) {
 
 func TestBindParam(t *testing.T) {
 	e := New()
+	*e.maxParam = 2
 	req := httptest.NewRequest(GET, "/", nil)
 	rec := httptest.NewRecorder()
 	c := e.NewContext(req, rec)
@@ -325,6 +363,7 @@ func TestBindParam(t *testing.T) {
 	// Bind something with param and post data payload
 	body := bytes.NewBufferString(`{ "name": "Jon Snow" }`)
 	e2 := New()
+	*e2.maxParam = 2
 	req2 := httptest.NewRequest(POST, "/", body)
 	req2.Header.Set(HeaderContentType, MIMEApplicationJSON)
 
@@ -433,6 +472,34 @@ func TestBindSetFields(t *testing.T) {
 	}
 }
 
+func BenchmarkBindbindData(b *testing.B) {
+	b.ReportAllocs()
+	assert := assert.New(b)
+	ts := new(bindTestStruct)
+	binder := new(DefaultBinder)
+	var err error
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		err = binder.bindData(ts, values, "form")
+	}
+	assert.NoError(err)
+	assertBindTestStruct(assert, ts)
+}
+
+func BenchmarkBindbindDataWithTags(b *testing.B) {
+	b.ReportAllocs()
+	assert := assert.New(b)
+	ts := new(bindTestStructWithTags)
+	binder := new(DefaultBinder)
+	var err error
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		err = binder.bindData(ts, values, "form")
+	}
+	assert.NoError(err)
+	assertBindTestStruct(assert, (*bindTestStruct)(ts))
+}
+
 func assertBindTestStruct(a *assert.Assertions, ts *bindTestStruct) {
 	a.Equal(0, ts.I)
 	a.Equal(int8(8), ts.I8)
diff --git a/context_test.go b/context_test.go
index d85a17e..866d064 100644
--- a/context_test.go
+++ b/context_test.go
@@ -18,6 +18,7 @@ import (
 	"text/template"
 	"time"
 
+	"github.com/labstack/gommon/log"
 	testify "github.com/stretchr/testify/assert"
 )
 
@@ -92,6 +93,7 @@ func (responseWriterErr) WriteHeader(statusCode int) {
 
 func TestContext(t *testing.T) {
 	e := New()
+	*e.maxParam = 1
 	req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON))
 	rec := httptest.NewRecorder()
 	c := e.NewContext(req, rec).(*context)
@@ -470,6 +472,7 @@ func TestContextPath(t *testing.T) {
 
 func TestContextPathParam(t *testing.T) {
 	e := New()
+	*e.maxParam = 2
 	req := httptest.NewRequest(http.MethodGet, "/", nil)
 	c := e.NewContext(req, nil)
 
@@ -486,6 +489,26 @@ func TestContextPathParam(t *testing.T) {
 	testify.Equal(t, "", c.Param("undefined"))
 }
 
+func TestContextGetAndSetParam(t *testing.T) {
+	e := New()
+	*e.maxParam = 2
+	req := httptest.NewRequest(http.MethodGet, "/:foo", nil)
+	c := e.NewContext(req, nil)
+	c.SetParamNames("foo")
+
+	// round-trip param values with modification
+	paramVals := c.ParamValues()
+	testify.EqualValues(t, []string{""}, c.ParamValues())
+	paramVals[0] = "bar"
+	c.SetParamValues(paramVals...)
+	testify.EqualValues(t, []string{"bar"}, c.ParamValues())
+
+	// shouldn't explode during Reset() afterwards!
+	testify.NotPanics(t, func() {
+		c.Reset(nil, nil)
+	})
+}
+
 func TestContextFormValue(t *testing.T) {
 	f := make(url.Values)
 	f.Set("name", "Jon Snow")
@@ -800,7 +823,16 @@ func TestContext_Logger(t *testing.T) {
 	e := New()
 	c := e.NewContext(nil, nil)
 
-	testify.NotNil(t, c.Logger())
+	log1 := c.Logger()
+	testify.NotNil(t, log1)
+
+	log2 := log.New("echo2")
+	c.SetLogger(log2)
+	testify.Equal(t, log2, c.Logger())
+
+	// Resetting the context returns the initial logger
+	c.Reset(nil, nil)
+	testify.Equal(t, log1, c.Logger())
 }
 
 func TestContext_RealIP(t *testing.T) {
diff --git a/echo_test.go b/echo_test.go
index 3f2e48e..68c556f 100644
--- a/echo_test.go
+++ b/echo_test.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"golang.org/x/net/http2"
 )
 
 type (
@@ -512,6 +513,17 @@ func TestEchoStartAutoTLS(t *testing.T) {
 	}
 }
 
+func TestEchoStartH2CServer(t *testing.T) {
+	e := New()
+	e.Debug = true
+	h2s := &http2.Server{}
+
+	go func() {
+		assert.NoError(t, e.StartH2CServer(":0", h2s))
+	}()
+	time.Sleep(200 * time.Millisecond)
+}
+
 func testMethod(t *testing.T, method, path string, e *Echo) {
 	p := reflect.ValueOf(path)
 	h := reflect.ValueOf(func(c Context) error {
diff --git a/group_test.go b/group_test.go
index de98ce3..342cd29 100644
--- a/group_test.go
+++ b/group_test.go
@@ -65,3 +65,42 @@ func TestGroupRouteMiddleware(t *testing.T) {
 	c, _ = request(http.MethodGet, "/group/405", e)
 	assert.Equal(t, 405, c)
 }
+
+func TestGroupRouteMiddlewareWithMatchAny(t *testing.T) {
+	// Ensure middleware and match any routes do not conflict
+	e := New()
+	g := e.Group("/group")
+	m1 := func(next HandlerFunc) HandlerFunc {
+		return func(c Context) error {
+			return next(c)
+		}
+	}
+	m2 := func(next HandlerFunc) HandlerFunc {
+		return func(c Context) error {
+			return c.String(http.StatusOK, c.Path())
+		}
+	}
+	h := func(c Context) error {
+		return c.String(http.StatusOK, c.Path())
+	}
+	g.Use(m1)
+	g.GET("/help", h, m2)
+	g.GET("/*", h, m2)
+	g.GET("", h, m2)
+	e.GET("unrelated", h, m2)
+	e.GET("*", h, m2)
+
+	_, m := request(http.MethodGet, "/group/help", e)
+	assert.Equal(t, "/group/help", m)
+	_, m = request(http.MethodGet, "/group/help/other", e)
+	assert.Equal(t, "/group/*", m)
+	_, m = request(http.MethodGet, "/group/404", e)
+	assert.Equal(t, "/group/*", m)
+	_, m = request(http.MethodGet, "/group", e)
+	assert.Equal(t, "/group", m)
+	_, m = request(http.MethodGet, "/other", e)
+	assert.Equal(t, "/*", m)
+	_, m = request(http.MethodGet, "/", e)
+	assert.Equal(t, "/*", m)
+
+}
diff --git a/middleware/cors_test.go b/middleware/cors_test.go
index acfdf47..456ec7b 100644
--- a/middleware/cors_test.go
+++ b/middleware/cors_test.go
@@ -73,7 +73,7 @@ func TestCORS(t *testing.T) {
 	c = e.NewContext(req, rec)
 	req.Header.Set(echo.HeaderOrigin, "http://aaa.example.com")
 	cors = CORSWithConfig(CORSConfig{
-		AllowOrigins:     []string{"http://*.example.com"},
+		AllowOrigins: []string{"http://*.example.com"},
 	})
 	h = cors(echo.NotFoundHandler)
 	h(c)
diff --git a/middleware/jwt_test.go b/middleware/jwt_test.go
index 7f15bd4..1731d90 100644
--- a/middleware/jwt_test.go
+++ b/middleware/jwt_test.go
@@ -60,6 +60,8 @@ func TestJWTRace(t *testing.T) {
 
 func TestJWT(t *testing.T) {
 	e := echo.New()
+	r := e.Router()
+	r.Add("GET", "/:jwt", func(echo.Context) error { return nil })
 	handler := func(c echo.Context) error {
 		return c.String(http.StatusOK, "test")
 	}
@@ -203,7 +205,7 @@ func TestJWT(t *testing.T) {
 		req.Header.Set(echo.HeaderCookie, tc.hdrCookie)
 		c := e.NewContext(req, res)
 
-		if tc.reqURL == "/" + token {
+		if tc.reqURL == "/"+token {
 			c.SetParamNames("jwt")
 			c.SetParamValues(token)
 		}
diff --git a/router_test.go b/router_test.go
index 80f1d0e..f47225d 100644
--- a/router_test.go
+++ b/router_test.go
@@ -499,6 +499,15 @@ var (
 		{"GET", "/people/:userId/moments/:collection", ""},
 		{"DELETE", "/moments/:id", ""},
 	}
+
+	// handlerHelper created a function that will set a context key for assertion
+	handlerHelper = func(key string, value int) func(c Context) error {
+		return func(c Context) error {
+			c.Set(key, value)
+			c.Set("path", c.Path())
+			return nil
+		}
+	}
 )
 
 func TestRouterStatic(t *testing.T) {
@@ -595,20 +604,91 @@ func TestRouterMatchAnyMultiLevel(t *testing.T) {
 	// Routes
 	r.Add(http.MethodGet, "/api/users/jack", handler)
 	r.Add(http.MethodGet, "/api/users/jill", handler)
+	r.Add(http.MethodGet, "/api/users/*", handler)
+	r.Add(http.MethodGet, "/api/*", handler)
+	r.Add(http.MethodGet, "/other/*", handler)
 	r.Add(http.MethodGet, "/*", handler)
 
 	c := e.NewContext(nil, nil).(*context)
 	r.Find(http.MethodGet, "/api/users/jack", c)
 	c.handler(c)
 	assert.Equal(t, "/api/users/jack", c.Get("path"))
+	assert.Equal(t, "", c.Param("*"))
 
 	r.Find(http.MethodGet, "/api/users/jill", c)
 	c.handler(c)
 	assert.Equal(t, "/api/users/jill", c.Get("path"))
+	assert.Equal(t, "", c.Param("*"))
 
 	r.Find(http.MethodGet, "/api/users/joe", c)
 	c.handler(c)
+	assert.Equal(t, "/api/users/*", c.Get("path"))
+	assert.Equal(t, "joe", c.Param("*"))
+
+	r.Find(http.MethodGet, "/api/nousers/joe", c)
+	c.handler(c)
+	assert.Equal(t, "/api/*", c.Get("path"))
+	assert.Equal(t, "nousers/joe", c.Param("*"))
+
+	r.Find(http.MethodGet, "/api/none", c)
+	c.handler(c)
+	assert.Equal(t, "/api/*", c.Get("path"))
+	assert.Equal(t, "none", c.Param("*"))
+
+	r.Find(http.MethodGet, "/noapi/users/jim", c)
+	c.handler(c)
 	assert.Equal(t, "/*", c.Get("path"))
+	assert.Equal(t, "noapi/users/jim", c.Param("*"))
+}
+func TestRouterMatchAnyMultiLevelWithPost(t *testing.T) {
+	e := New()
+	r := e.router
+	handler := func(c Context) error {
+		c.Set("path", c.Path())
+		return nil
+	}
+
+	// Routes
+	e.POST("/api/auth/login", handler)
+	e.POST("/api/auth/forgotPassword", handler)
+	e.Any("/api/*", handler)
+	e.Any("/*", handler)
+
+	// POST /api/auth/login shall choose login method
+	c := e.NewContext(nil, nil).(*context)
+	r.Find(http.MethodPost, "/api/auth/login", c)
+	c.handler(c)
+	assert.Equal(t, "/api/auth/login", c.Get("path"))
+	assert.Equal(t, "", c.Param("*"))
+
+	// GET /api/auth/login shall choose any route
+	// c = e.NewContext(nil, nil).(*context)
+	// r.Find(http.MethodGet, "/api/auth/login", c)
+	// c.handler(c)
+	// assert.Equal(t, "/api/*", c.Get("path"))
+	// assert.Equal(t, "auth/login", c.Param("*"))
+
+	// POST /api/auth/logout shall choose nearest any route
+	c = e.NewContext(nil, nil).(*context)
+	r.Find(http.MethodPost, "/api/auth/logout", c)
+	c.handler(c)
+	assert.Equal(t, "/api/*", c.Get("path"))
+	assert.Equal(t, "auth/logout", c.Param("*"))
+
+	// POST to /api/other/test shall choose nearest any route
+	c = e.NewContext(nil, nil).(*context)
+	r.Find(http.MethodPost, "/api/other/test", c)
+	c.handler(c)
+	assert.Equal(t, "/api/*", c.Get("path"))
+	assert.Equal(t, "other/test", c.Param("*"))
+
+	// GET to /api/other/test shall choose nearest any route
+	c = e.NewContext(nil, nil).(*context)
+	r.Find(http.MethodGet, "/api/other/test", c)
+	c.handler(c)
+	assert.Equal(t, "/api/*", c.Get("path"))
+	assert.Equal(t, "other/test", c.Param("*"))
+
 }
 
 func TestRouterMicroParam(t *testing.T) {
@@ -674,71 +754,123 @@ func TestRouterPriority(t *testing.T) {
 	r := e.router
 
 	// Routes
-	r.Add(http.MethodGet, "/users", func(c Context) error {
-		c.Set("a", 1)
-		return nil
-	})
-	r.Add(http.MethodGet, "/users/new", func(c Context) error {
-		c.Set("b", 2)
-		return nil
-	})
-	r.Add(http.MethodGet, "/users/:id", func(c Context) error {
-		c.Set("c", 3)
-		return nil
-	})
-	r.Add(http.MethodGet, "/users/dew", func(c Context) error {
-		c.Set("d", 4)
-		return nil
-	})
-	r.Add(http.MethodGet, "/users/:id/files", func(c Context) error {
-		c.Set("e", 5)
-		return nil
-	})
-	r.Add(http.MethodGet, "/users/newsee", func(c Context) error {
-		c.Set("f", 6)
-		return nil
-	})
-	r.Add(http.MethodGet, "/users/*", func(c Context) error {
-		c.Set("g", 7)
-		return nil
-	})
-	c := e.NewContext(nil, nil).(*context)
+	r.Add(http.MethodGet, "/users", handlerHelper("a", 1))
+	r.Add(http.MethodGet, "/users/new", handlerHelper("b", 2))
+	r.Add(http.MethodGet, "/users/:id", handlerHelper("c", 3))
+	r.Add(http.MethodGet, "/users/dew", handlerHelper("d", 4))
+	r.Add(http.MethodGet, "/users/:id/files", handlerHelper("e", 5))
+	r.Add(http.MethodGet, "/users/newsee", handlerHelper("f", 6))
+	r.Add(http.MethodGet, "/users/*", handlerHelper("g", 7))
+	r.Add(http.MethodGet, "/users/new/*", handlerHelper("h", 8))
+	r.Add(http.MethodGet, "/*", handlerHelper("i", 9))
 
 	// Route > /users
+	c := e.NewContext(nil, nil).(*context)
 	r.Find(http.MethodGet, "/users", c)
 	c.handler(c)
 	assert.Equal(t, 1, c.Get("a"))
+	assert.Equal(t, "/users", c.Get("path"))
 
 	// Route > /users/new
+	c = e.NewContext(nil, nil).(*context)
 	r.Find(http.MethodGet, "/users/new", c)
 	c.handler(c)
 	assert.Equal(t, 2, c.Get("b"))
+	assert.Equal(t, "/users/new", c.Get("path"))
 
 	// Route > /users/:id
+	c = e.NewContext(nil, nil).(*context)
 	r.Find(http.MethodGet, "/users/1", c)
 	c.handler(c)
 	assert.Equal(t, 3, c.Get("c"))
+	assert.Equal(t, "/users/:id", c.Get("path"))
 
 	// Route > /users/dew
+	c = e.NewContext(nil, nil).(*context)
 	r.Find(http.MethodGet, "/users/dew", c)
 	c.handler(c)
 	assert.Equal(t, 4, c.Get("d"))
+	assert.Equal(t, "/users/dew", c.Get("path"))
 
 	// Route > /users/:id/files
+	c = e.NewContext(nil, nil).(*context)
 	r.Find(http.MethodGet, "/users/1/files", c)
 	c.handler(c)
 	assert.Equal(t, 5, c.Get("e"))
+	assert.Equal(t, "/users/:id/files", c.Get("path"))
 
 	// Route > /users/:id
+	c = e.NewContext(nil, nil).(*context)
 	r.Find(http.MethodGet, "/users/news", c)
 	c.handler(c)
 	assert.Equal(t, 3, c.Get("c"))
+	assert.Equal(t, "/users/:id", c.Get("path"))
+
+	// Route > /users/newsee
+	c = e.NewContext(nil, nil).(*context)
+	r.Find(http.MethodGet, "/users/newsee", c)
+	c.handler(c)
+	assert.Equal(t, 6, c.Get("f"))
+	assert.Equal(t, "/users/newsee", c.Get("path"))
+
+	// Route > /users/newsee
+	r.Find(http.MethodGet, "/users/newsee", c)
+	c.handler(c)
+	assert.Equal(t, 6, c.Get("f"))
+
+	// Route > /users/newsee
+	r.Find(http.MethodGet, "/users/newsee", c)
+	c.handler(c)
+	assert.Equal(t, 6, c.Get("f"))
 
 	// Route > /users/*
+	c = e.NewContext(nil, nil).(*context)
 	r.Find(http.MethodGet, "/users/joe/books", c)
 	c.handler(c)
 	assert.Equal(t, 7, c.Get("g"))
+	assert.Equal(t, "/users/*", c.Get("path"))
 	assert.Equal(t, "joe/books", c.Param("*"))
+
+	// Route > /users/new/* should be matched
+	c = e.NewContext(nil, nil).(*context)
+	r.Find(http.MethodGet, "/users/new/someone", c)
+	c.handler(c)
+	assert.Equal(t, 8, c.Get("h"))
+	assert.Equal(t, "/users/new/*", c.Get("path"))
+	assert.Equal(t, "someone", c.Param("*"))
+
+	// Route > /users/* should be matched although /users/dew exists
+	c = e.NewContext(nil, nil).(*context)
+	r.Find(http.MethodGet, "/users/dew/someone", c)
+	c.handler(c)
+	assert.Equal(t, 7, c.Get("g"))
+	assert.Equal(t, "/users/*", c.Get("path"))
+
+	assert.Equal(t, "dew/someone", c.Param("*"))
+
+	// Route > /users/* should be matched although /users/dew exists
+	c = e.NewContext(nil, nil).(*context)
+	r.Find(http.MethodGet, "/users/notexists/someone", c)
+	c.handler(c)
+	assert.Equal(t, 7, c.Get("g"))
+	assert.Equal(t, "/users/*", c.Get("path"))
+	assert.Equal(t, "notexists/someone", c.Param("*"))
+
+	// Route > *
+	c = e.NewContext(nil, nil).(*context)
+	r.Find(http.MethodGet, "/nousers", c)
+	c.handler(c)
+	assert.Equal(t, 9, c.Get("i"))
+	assert.Equal(t, "/*", c.Get("path"))
+	assert.Equal(t, "nousers", c.Param("*"))
+
+	// Route > *
+	c = e.NewContext(nil, nil).(*context)
+	r.Find(http.MethodGet, "/nousers/new", c)
+	c.handler(c)
+	assert.Equal(t, 9, c.Get("i"))
+	assert.Equal(t, "/*", c.Get("path"))
+	assert.Equal(t, "nousers/new", c.Param("*"))
 }
 
 func TestRouterIssue1348(t *testing.T) {
@@ -819,36 +951,59 @@ func TestRouterParamNames(t *testing.T) {
 	assert.Equal(t, "1", c.Param("fid"))
 }
 
-// Issue #623
+// Issue #623 and #1406
 func TestRouterStaticDynamicConflict(t *testing.T) {
 	e := New()
 	r := e.router
-	c := e.NewContext(nil, nil)
 
-	r.Add(http.MethodGet, "/dictionary/skills", func(c Context) error {
-		c.Set("a", 1)
-		return nil
-	})
-	r.Add(http.MethodGet, "/dictionary/:name", func(c Context) error {
-		c.Set("b", 2)
-		return nil
-	})
-	r.Add(http.MethodGet, "/server", func(c Context) error {
-		c.Set("c", 3)
-		return nil
-	})
+	r.Add(http.MethodGet, "/dictionary/skills", handlerHelper("a", 1))
+	r.Add(http.MethodGet, "/dictionary/:name", handlerHelper("b", 2))
+	r.Add(http.MethodGet, "/users/new", handlerHelper("d", 4))
+	r.Add(http.MethodGet, "/users/:name", handlerHelper("e", 5))
+	r.Add(http.MethodGet, "/server", handlerHelper("c", 3))
+	r.Add(http.MethodGet, "/", handlerHelper("f", 6))
 
+	c := e.NewContext(nil, nil)
 	r.Find(http.MethodGet, "/dictionary/skills", c)
 	c.Handler()(c)
 	assert.Equal(t, 1, c.Get("a"))
+	assert.Equal(t, "/dictionary/skills", c.Get("path"))
+
+	c = e.NewContext(nil, nil)
+	r.Find(http.MethodGet, "/dictionary/skillsnot", c)
+	c.Handler()(c)
+	assert.Equal(t, 2, c.Get("b"))
+	assert.Equal(t, "/dictionary/:name", c.Get("path"))
+
 	c = e.NewContext(nil, nil)
 	r.Find(http.MethodGet, "/dictionary/type", c)
 	c.Handler()(c)
 	assert.Equal(t, 2, c.Get("b"))
+	assert.Equal(t, "/dictionary/:name", c.Get("path"))
+
 	c = e.NewContext(nil, nil)
 	r.Find(http.MethodGet, "/server", c)
 	c.Handler()(c)
 	assert.Equal(t, 3, c.Get("c"))
+	assert.Equal(t, "/server", c.Get("path"))
+
+	c = e.NewContext(nil, nil)
+	r.Find(http.MethodGet, "/users/new", c)
+	c.Handler()(c)
+	assert.Equal(t, 4, c.Get("d"))
+	assert.Equal(t, "/users/new", c.Get("path"))
+
+	c = e.NewContext(nil, nil)
+	r.Find(http.MethodGet, "/users/new2", c)
+	c.Handler()(c)
+	assert.Equal(t, 5, c.Get("e"))
+	assert.Equal(t, "/users/:name", c.Get("path"))
+
+	c = e.NewContext(nil, nil)
+	r.Find(http.MethodGet, "/", c)
+	c.Handler()(c)
+	assert.Equal(t, 6, c.Get("f"))
+	assert.Equal(t, "/", c.Get("path"))
 }
 
 // Issue #1348
@@ -963,6 +1118,72 @@ func TestRouterMixedParams(t *testing.T) {
 	testRouterAPI(t, api2)
 }
 
+// Issue #1466
+func TestRouterParam1466(t *testing.T) {
+	e := New()
+	r := e.router
+
+	r.Add(http.MethodPost, "/users/signup", func(c Context) error {
+		return nil
+	})
+	r.Add(http.MethodPost, "/users/signup/bulk", func(c Context) error {
+		return nil
+	})
+	r.Add(http.MethodPost, "/users/survey", func(c Context) error {
+		return nil
+	})
+	r.Add(http.MethodGet, "/users/:username", func(c Context) error {
+		return nil
+	})
+	r.Add(http.MethodGet, "/interests/:name/users", func(c Context) error {
+		return nil
+	})
+	r.Add(http.MethodGet, "/skills/:name/users", func(c Context) error {
+		return nil
+	})
+	// Additional routes for Issue 1479
+	r.Add(http.MethodGet, "/users/:username/likes/projects/ids", func(c Context) error {
+		return nil
+	})
+	r.Add(http.MethodGet, "/users/:username/profile", func(c Context) error {
+		return nil
+	})
+	r.Add(http.MethodGet, "/users/:username/uploads/:type", func(c Context) error {
+		return nil
+	})
+
+	c := e.NewContext(nil, nil).(*context)
+
+	r.Find(http.MethodGet, "/users/ajitem", c)
+	assert.Equal(t, "ajitem", c.Param("username"))
+
+	r.Find(http.MethodGet, "/users/sharewithme", c)
+	assert.Equal(t, "sharewithme", c.Param("username"))
+
+	r.Find(http.MethodGet, "/users/signup", c)
+	assert.Equal(t, "", c.Param("username"))
+	// Additional assertions for #1479
+	r.Find(http.MethodGet, "/users/sharewithme/likes/projects/ids", c)
+	assert.Equal(t, "sharewithme", c.Param("username"))
+
+	r.Find(http.MethodGet, "/users/ajitem/likes/projects/ids", c)
+	assert.Equal(t, "ajitem", c.Param("username"))
+
+	r.Find(http.MethodGet, "/users/sharewithme/profile", c)
+	assert.Equal(t, "sharewithme", c.Param("username"))
+
+	r.Find(http.MethodGet, "/users/ajitem/profile", c)
+	assert.Equal(t, "ajitem", c.Param("username"))
+
+	r.Find(http.MethodGet, "/users/sharewithme/uploads/self", c)
+	assert.Equal(t, "sharewithme", c.Param("username"))
+	assert.Equal(t, "self", c.Param("type"))
+
+	r.Find(http.MethodGet, "/users/ajitem/uploads/self", c)
+	assert.Equal(t, "ajitem", c.Param("username"))
+	assert.Equal(t, "self", c.Param("type"))
+}
+
 func benchmarkRouterRoutes(b *testing.B, routes []*Route) {
 	e := New()
 	r := e.router