Repository: labstack/echo
Pull request #1092 (2018-03-21 07:23:02)

the logging interface add SetHeader method
Add SetHeader method in Logger interface, and we can customize log formats like this:

```
e := echo.New()
e.Logger.SetHeader(`[${time_unix}] [${level}] {"time":"${time_unix}","file":"${short_file}","line":"${line}"}`)
```

output:
```
[2018-03-21T14:49:06+08:00] [ERROR] {"time":":1521616167,","file":"log.go","line":"309","message":"code=404, message=Not Found"}
```

codecov[bot]: # [Codecov](https://codecov.io/gh/labstack/echo/pull/1092?src=pr&el=h1) Report
> Merging [#1092](https://codecov.io/gh/labstack/echo/pull/1092?src=pr&el=desc) into [master](https://codecov.io/gh/labstack/echo/commit/a5d81b8d4a627f3f49dcb3164ef2bb5239682227?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/labstack/echo/pull/1092/graphs/tree.svg?token=vqnT76l79i&width=650&height=150&src=pr)](https://codecov.io/gh/labstack/echo/pull/1092?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master    #1092   +/-   ##
=======================================
  Coverage   81.06%   81.06%           
=======================================
  Files          27       27           
  Lines        1901     1901           
=======================================
  Hits         1541     1541           
  Misses        253      253           
  Partials      107      107
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/labstack/echo/pull/1092?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/labstack/echo/pull/1092?src=pr&el=footer). Last update [a5d81b8...e465d80](https://codecov.io/gh/labstack/echo/pull/1092?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.travis.yml b/.travis.yml
index 05e53b1..7de4f96 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,7 @@ language: go
 go:
   - 1.9.x
   - 1.10.x
+  - 1.11.x
   - tip
 install:
   - make dependency
diff --git a/Gopkg.lock b/Gopkg.lock
index f3c3b8d..a25473a 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -2,74 +2,113 @@
 
 
 [[projects]]
+  digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
   name = "github.com/davecgh/go-spew"
   packages = ["spew"]
+  pruneopts = ""
   revision = "346938d642f2ec3594ed81d874461961cd0faa76"
   version = "v1.1.0"
 
 [[projects]]
+  digest = "1:6098222470fe0172157ce9bbef5d2200df4edde17ee649c5d6e48330e4afa4c6"
   name = "github.com/dgrijalva/jwt-go"
   packages = ["."]
+  pruneopts = ""
   revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
   version = "v3.2.0"
 
 [[projects]]
+  digest = "1:f0c583bb1189614701bb928f1a73a7f71a3d31b76f1fbbe6619a83d92cbd2def"
   name = "github.com/labstack/gommon"
-  packages = ["bytes","color","log","random"]
-  revision = "6fe1405d73ec4bd4cd8a4ac8e2a2b2bf95d03954"
-  version = "0.2.4"
+  packages = [
+    "bytes",
+    "color",
+    "log",
+    "random",
+  ]
+  pruneopts = ""
+  revision = "2a618302b929cc20862dda3aa6f02f64dbe740dd"
+  version = "v0.2.7"
 
 [[projects]]
+  digest = "1:9ea83adf8e96d6304f394d40436f2eb44c1dc3250d223b74088cc253a6cd0a1c"
   name = "github.com/mattn/go-colorable"
   packages = ["."]
+  pruneopts = ""
   revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
   version = "v0.0.9"
 
 [[projects]]
+  digest = "1:78229b46ddb7434f881390029bd1af7661294af31f6802e0e1bedaad4ab0af3c"
   name = "github.com/mattn/go-isatty"
   packages = ["."]
+  pruneopts = ""
   revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
   version = "v0.0.3"
 
 [[projects]]
+  digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
   name = "github.com/pmezard/go-difflib"
   packages = ["difflib"]
+  pruneopts = ""
   revision = "792786c7400a136282c1664665ae0a8db921c6c2"
   version = "v1.0.0"
 
 [[projects]]
+  digest = "1:a30066593578732a356dc7e5d7f78d69184ca65aeeff5939241a3ab10559bb06"
   name = "github.com/stretchr/testify"
   packages = ["assert"]
+  pruneopts = ""
   revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
   version = "v1.2.1"
 
 [[projects]]
   branch = "master"
+  digest = "1:857a9ecd5cb13379ecc8f798f6e6b6b574c98b9355657d91e068275f1120aaf7"
   name = "github.com/valyala/bytebufferpool"
   packages = ["."]
+  pruneopts = ""
   revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
 
 [[projects]]
   branch = "master"
+  digest = "1:bf6f8915c0338e875383cb7fdebd58a4d360a232f461d9a029d7ccb12f90c5d7"
   name = "github.com/valyala/fasttemplate"
   packages = ["."]
+  pruneopts = ""
   revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
 
 [[projects]]
   branch = "master"
+  digest = "1:7fdbf5fb3eb8256d4c01abe969062a77fa5818cd900dc3302a2400c6ed668860"
   name = "golang.org/x/crypto"
-  packages = ["acme","acme/autocert"]
+  packages = [
+    "acme",
+    "acme/autocert",
+  ]
+  pruneopts = ""
   revision = "182114d582623c1caa54f73de9c7224e23a48487"
 
 [[projects]]
   branch = "master"
+  digest = "1:4b4c7ad782ed549fccf044f2d0f6c3b6f76aca593db6649a9b2e6c12bd733f9a"
   name = "golang.org/x/sys"
   packages = ["unix"]
+  pruneopts = ""
   revision = "c28acc882ebcbfbe8ce9f0f14b9ac26ee138dd51"
 
 [solve-meta]
   analyzer-name = "dep"
   analyzer-version = 1
-  inputs-digest = "9c7b45e80fe353405800cf01f429b3a203cfb8d4468a04c64a908e11a98ea764"
+  input-imports = [
+    "github.com/dgrijalva/jwt-go",
+    "github.com/labstack/gommon/bytes",
+    "github.com/labstack/gommon/color",
+    "github.com/labstack/gommon/log",
+    "github.com/labstack/gommon/random",
+    "github.com/stretchr/testify/assert",
+    "github.com/valyala/fasttemplate",
+    "golang.org/x/crypto/acme/autocert",
+  ]
   solver-name = "gps-cdcl"
   solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index 61de60c..3ebbc7f 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -27,7 +27,7 @@
 
 [[constraint]]
   name = "github.com/labstack/gommon"
-  version = "0.2.4"
+  version = "v0.2.7"
 
 [[constraint]]
   name = "github.com/stretchr/testify"
diff --git a/Makefile b/Makefile
index 494667d..68d3a04 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-DEP_VERSION=0.4.1
+DEP_VERSION=0.5.0
 
 dependency:
 	curl -fsSL -o ${GOPATH}/bin/dep https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64
diff --git a/bind.go b/bind.go
index 04aa812..8f54ae4 100644
--- a/bind.go
+++ b/bind.go
@@ -33,7 +33,7 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
 	if req.ContentLength == 0 {
 		if req.Method == GET || req.Method == DELETE {
 			if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
-				return NewHTTPError(http.StatusBadRequest, err.Error())
+				return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
 			}
 			return
 		}
@@ -44,30 +44,32 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
 	case strings.HasPrefix(ctype, MIMEApplicationJSON):
 		if err = json.NewDecoder(req.Body).Decode(i); err != nil {
 			if ute, ok := err.(*json.UnmarshalTypeError); ok {
-				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset))
+				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
 			} else if se, ok := err.(*json.SyntaxError); ok {
-				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error()))
+				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
 			} else {
-				return NewHTTPError(http.StatusBadRequest, err.Error())
+				return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
 			}
+			return NewHTTPError(http.StatusBadRequest, err.Error())
 		}
 	case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
 		if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
 			if ute, ok := err.(*xml.UnsupportedTypeError); ok {
-				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error()))
+				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
 			} else if se, ok := err.(*xml.SyntaxError); ok {
-				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error()))
+				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
 			} else {
-				return NewHTTPError(http.StatusBadRequest, err.Error())
+				return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
 			}
+			return NewHTTPError(http.StatusBadRequest, err.Error())
 		}
 	case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
 		params, err := c.FormParams()
 		if err != nil {
-			return NewHTTPError(http.StatusBadRequest, err.Error())
+			return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
 		}
 		if err = b.bindData(i, params, "form"); err != nil {
-			return NewHTTPError(http.StatusBadRequest, err.Error())
+			return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
 		}
 	default:
 		return ErrUnsupportedMediaType
@@ -96,14 +98,29 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
 			inputFieldName = typeField.Name
 			// If tag is nil, we inspect if the field is a struct.
 			if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
-				err := b.bindData(structField.Addr().Interface(), data, tag)
-				if err != nil {
+				if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
 					return err
 				}
 				continue
 			}
 		}
+
 		inputValue, exists := data[inputFieldName]
+		if !exists {
+			// Go json.Unmarshal supports case insensitive binding.  However the
+			// 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 {
+					inputValue = v
+					exists = true
+					break
+				}
+			}
+		}
+
 		if !exists {
 			continue
 		}
@@ -126,10 +143,9 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
 				}
 			}
 			val.Field(i).Set(slice)
-		} else {
-			if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
-				return err
-			}
+		} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
+			return err
+
 		}
 	}
 	return nil
diff --git a/context.go b/context.go
index cf780c5..9a9d133 100644
--- a/context.go
+++ b/context.go
@@ -256,14 +256,13 @@ func (c *context) Scheme() string {
 }
 
 func (c *context) RealIP() string {
-	ra := c.request.RemoteAddr
 	if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
-		ra = strings.Split(ip, ", ")[0]
-	} else if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
-		ra = ip
-	} else {
-		ra, _, _ = net.SplitHostPort(ra)
+		return strings.Split(ip, ", ")[0]
+	}
+	if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
+		return ip
 	}
+	ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
 	return ra
 }
 
diff --git a/echo.go b/echo.go
index 8e552c5..996bc4b 100644
--- a/echo.go
+++ b/echo.go
@@ -62,7 +62,7 @@ import (
 type (
 	// Echo is the top-level framework instance.
 	Echo struct {
-		stdLogger        *stdLog.Logger
+		StdLogger        *stdLog.Logger
 		colorer          *color.Color
 		premiddleware    []MiddlewareFunc
 		middleware       []MiddlewareFunc
@@ -103,7 +103,7 @@ type (
 	// MiddlewareFunc defines a function to process middleware.
 	MiddlewareFunc func(HandlerFunc) HandlerFunc
 
-	// HandlerFunc defines a function to server HTTP requests.
+	// HandlerFunc defines a function to serve HTTP requests.
 	HandlerFunc func(Context) error
 
 	// HTTPErrorHandler is a centralized HTTP error handler.
@@ -255,6 +255,7 @@ var (
 	ErrForbidden                   = NewHTTPError(http.StatusForbidden)
 	ErrMethodNotAllowed            = NewHTTPError(http.StatusMethodNotAllowed)
 	ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
+	ErrTooManyRequests             = NewHTTPError(http.StatusTooManyRequests)
 	ErrValidatorNotRegistered      = errors.New("validator not registered")
 	ErrRendererNotRegistered       = errors.New("renderer not registered")
 	ErrInvalidRedirectCode         = errors.New("invalid redirect status code")
@@ -289,7 +290,7 @@ func New() (e *Echo) {
 	e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
 	e.Binder = &DefaultBinder{}
 	e.Logger.SetLevel(log.ERROR)
-	e.stdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
+	e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
 	e.pool.New = func() interface{} {
 		return e.NewContext(nil, nil)
 	}
@@ -326,7 +327,7 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
 		code = he.Code
 		msg = he.Message
 		if he.Internal != nil {
-			msg = fmt.Sprintf("%v, %v", err, he.Internal)
+			err = fmt.Errorf("%v, %v", err, he.Internal)
 		}
 	} else if e.Debug {
 		msg = err.Error()
@@ -557,18 +558,17 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	c := e.pool.Get().(*context)
 	c.Reset(r, w)
 
-	m := r.Method
 	h := NotFoundHandler
 
 	if e.premiddleware == nil {
-		e.router.Find(m, getPath(r), c)
+		e.router.Find(r.Method, getPath(r), c)
 		h = c.Handler()
 		for i := len(e.middleware) - 1; i >= 0; i-- {
 			h = e.middleware[i](h)
 		}
 	} else {
 		h = func(c Context) error {
-			e.router.Find(m, getPath(r), c)
+			e.router.Find(r.Method, getPath(r), c)
 			h := c.Handler()
 			for i := len(e.middleware) - 1; i >= 0; i-- {
 				h = e.middleware[i](h)
@@ -635,7 +635,7 @@ func (e *Echo) startTLS(address string) error {
 func (e *Echo) StartServer(s *http.Server) (err error) {
 	// Setup
 	e.colorer.SetOutput(e.Logger.Output())
-	s.ErrorLog = e.stdLogger
+	s.ErrorLog = e.StdLogger
 	s.Handler = e
 	if e.Debug {
 		e.Logger.SetLevel(log.DEBUG)
@@ -702,6 +702,11 @@ func (he *HTTPError) Error() string {
 	return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message)
 }
 
+func (he *HTTPError) SetInternal(err error) *HTTPError {
+	he.Internal = err
+	return he
+}
+
 // WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
 func WrapHandler(h http.Handler) HandlerFunc {
 	return func(c Context) error {
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..b2b9f18
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,15 @@
+module github.com/labstack/echo
+
+require (
+	github.com/davecgh/go-spew v1.1.0 // indirect
+	github.com/dgrijalva/jwt-go v3.2.0+incompatible
+	github.com/labstack/gommon v0.2.7
+	github.com/mattn/go-colorable v0.0.9 // indirect
+	github.com/mattn/go-isatty v0.0.3 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/stretchr/testify v1.2.1
+	github.com/valyala/bytebufferpool v0.0.0-20160817181652-e746df99fe4a // indirect
+	github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4
+	golang.org/x/crypto v0.0.0-20180312195533-182114d58262
+	golang.org/x/sys v0.0.0-20180312081825-c28acc882ebc // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..7d5ffe0
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,22 @@
+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/gommon v0.2.7 h1:2qOPq/twXDrQ6ooBGrn3mrmVOC+biLlatwgIu8lbzRM=
+github.com/labstack/gommon v0.2.7/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
+github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+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/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U=
+github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/valyala/bytebufferpool v0.0.0-20160817181652-e746df99fe4a h1:AOcehBWpFhYPYw0ioDTppQzgI8pAAahVCiMSKTp9rbo=
+github.com/valyala/bytebufferpool v0.0.0-20160817181652-e746df99fe4a/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
+github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw=
+golang.org/x/crypto v0.0.0-20180312195533-182114d58262 h1:1NLVUmR8SQ7cNNA5Vo7ronpXbR+5A+9IwIC/bLE7D8Y=
+golang.org/x/crypto v0.0.0-20180312195533-182114d58262/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/sys v0.0.0-20180312081825-c28acc882ebc h1:eCzBKjjvhDDXMoH5l7eA+YK1PEtyJ2QLj3f4IArr+zg=
+golang.org/x/sys v0.0.0-20180312081825-c28acc882ebc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/log.go b/log.go
index b194c39..3f8de59 100644
--- a/log.go
+++ b/log.go
@@ -15,6 +15,7 @@ type (
 		SetPrefix(p string)
 		Level() log.Lvl
 		SetLevel(v log.Lvl)
+		SetHeader(h string)
 		Print(i ...interface{})
 		Printf(format string, args ...interface{})
 		Printj(j log.JSON)
diff --git a/middleware/cors.go b/middleware/cors.go
index 771000a..a6126a6 100644
--- a/middleware/cors.go
+++ b/middleware/cors.go
@@ -94,6 +94,10 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
 
 			// Check allowed origins
 			for _, o := range config.AllowOrigins {
+				if o == "*" && config.AllowCredentials {
+					allowOrigin = origin
+					break
+				}
 				if o == "*" || o == origin {
 					allowOrigin = o
 					break
diff --git a/middleware/jwt.go b/middleware/jwt.go
index cad6839..051c589 100644
--- a/middleware/jwt.go
+++ b/middleware/jwt.go
@@ -23,6 +23,7 @@ type (
 		SuccessHandler JWTSuccessHandler
 
 		// ErrorHandler defines a function which is executed for an invalid token.
+		// It may be used to define a custom JWT error.
 		ErrorHandler JWTErrorHandler
 
 		// Signing key to validate token.
@@ -61,7 +62,7 @@ type (
 	JWTSuccessHandler func(echo.Context)
 
 	// JWTErrorHandler defines a function which is executed for an invalid token.
-	JWTErrorHandler func(echo.Context, echo.HandlerFunc) error
+	JWTErrorHandler func(error) error
 
 	jwtExtractor func(echo.Context) (string, error)
 )
@@ -74,7 +75,6 @@ const (
 // Errors
 var (
 	ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
-	ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt")
 )
 
 var (
@@ -158,6 +158,9 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
 
 			auth, err := extractor(c)
 			if err != nil {
+				if config.ErrorHandler != nil {
+					return config.ErrorHandler(err)
+				}
 				return err
 			}
 			token := new(jwt.Token)
@@ -178,11 +181,11 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
 				return next(c)
 			}
 			if config.ErrorHandler != nil {
-				return config.ErrorHandler(c, next)
+				return config.ErrorHandler(err)
 			}
 			return &echo.HTTPError{
-				Code:     ErrJWTInvalid.Code,
-				Message:  ErrJWTInvalid.Message,
+				Code:     http.StatusUnauthorized,
+				Message:  "invalid or expired jwt",
 				Internal: err,
 			}
 		}
diff --git a/middleware/logger.go b/middleware/logger.go
index 336eb58..b778ee3 100644
--- a/middleware/logger.go
+++ b/middleware/logger.go
@@ -33,6 +33,7 @@ type (
 		// - host
 		// - method
 		// - path
+		// - protocol
 		// - referer
 		// - user_agent
 		// - status
@@ -154,6 +155,8 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
 						p = "/"
 					}
 					return buf.WriteString(p)
+				case "protocol":
+					return buf.WriteString(req.Proto)
 				case "referer":
 					return buf.WriteString(req.Referer())
 				case "user_agent":
diff --git a/middleware/rewrite.go b/middleware/rewrite.go
index e7ddce3..1b32212 100644
--- a/middleware/rewrite.go
+++ b/middleware/rewrite.go
@@ -58,6 +58,7 @@ func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
 	// Initialize
 	for k, v := range config.Rules {
 		k = strings.Replace(k, "*", "(.*)", -1)
+		k = k + "$"
 		config.rulesRegex[regexp.MustCompile(k)] = v
 	}
 
@@ -74,9 +75,9 @@ func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
 				replacer := captureTokens(k, req.URL.Path)
 				if replacer != nil {
 					req.URL.Path = replacer.Replace(v)
+					break
 				}
 			}
-
 			return next(c)
 		}
 	}


Test patch:
diff --git a/bind_test.go b/bind_test.go
index 8b12ebd..72b0ed7 100644
--- a/bind_test.go
+++ b/bind_test.go
@@ -2,11 +2,15 @@ package echo
 
 import (
 	"bytes"
+	"encoding/json"
+	"encoding/xml"
+	"errors"
 	"io"
 	"mime/multipart"
 	"net/http"
 	"net/http/httptest"
 	"reflect"
+	"strconv"
 	"strings"
 	"testing"
 	"time"
@@ -117,19 +121,24 @@ var values = map[string][]string{
 
 func TestBindJSON(t *testing.T) {
 	testBindOkay(t, strings.NewReader(userJSON), MIMEApplicationJSON)
-	testBindError(t, strings.NewReader(invalidContent), MIMEApplicationJSON)
+	testBindError(t, strings.NewReader(invalidContent), MIMEApplicationJSON, &json.SyntaxError{})
+	testBindError(t, strings.NewReader(userJSONInvalidType), MIMEApplicationJSON, &json.UnmarshalTypeError{})
 }
 
 func TestBindXML(t *testing.T) {
 	testBindOkay(t, strings.NewReader(userXML), MIMEApplicationXML)
-	testBindError(t, strings.NewReader(invalidContent), MIMEApplicationXML)
+	testBindError(t, strings.NewReader(invalidContent), MIMEApplicationXML, errors.New(""))
+	testBindError(t, strings.NewReader(userXMLConvertNumberError), MIMEApplicationXML, &strconv.NumError{})
+	testBindError(t, strings.NewReader(userXMLUnsupportedTypeError), MIMEApplicationXML, &xml.SyntaxError{})
 	testBindOkay(t, strings.NewReader(userXML), MIMETextXML)
-	testBindError(t, strings.NewReader(invalidContent), MIMETextXML)
+	testBindError(t, strings.NewReader(invalidContent), MIMETextXML, errors.New(""))
+	testBindError(t, strings.NewReader(userXMLConvertNumberError), MIMETextXML, &strconv.NumError{})
+	testBindError(t, strings.NewReader(userXMLUnsupportedTypeError), MIMETextXML, &xml.SyntaxError{})
 }
 
 func TestBindForm(t *testing.T) {
 	testBindOkay(t, strings.NewReader(userForm), MIMEApplicationForm)
-	testBindError(t, nil, MIMEApplicationForm)
+	testBindError(t, nil, MIMEApplicationForm, nil)
 	e := New()
 	req := httptest.NewRequest(POST, "/", strings.NewReader(userForm))
 	rec := httptest.NewRecorder()
@@ -152,6 +161,32 @@ func TestBindQueryParams(t *testing.T) {
 	}
 }
 
+func TestBindQueryParamsCaseInsensitive(t *testing.T) {
+	e := New()
+	req := httptest.NewRequest(GET, "/?ID=1&NAME=Jon+Snow", nil)
+	rec := httptest.NewRecorder()
+	c := e.NewContext(req, rec)
+	u := new(user)
+	err := c.Bind(u)
+	if assert.NoError(t, err) {
+		assert.Equal(t, 1, u.ID)
+		assert.Equal(t, "Jon Snow", u.Name)
+	}
+}
+
+func TestBindQueryParamsCaseSensitivePrioritized(t *testing.T) {
+	e := New()
+	req := httptest.NewRequest(GET, "/?id=1&ID=2&NAME=Jon+Snow&name=Jon+Doe", nil)
+	rec := httptest.NewRecorder()
+	c := e.NewContext(req, rec)
+	u := new(user)
+	err := c.Bind(u)
+	if assert.NoError(t, err) {
+		assert.Equal(t, 1, u.ID)
+		assert.Equal(t, "Jon Doe", u.Name)
+	}
+}
+
 func TestBindUnmarshalParam(t *testing.T) {
 	e := New()
 	req := httptest.NewRequest(GET, "/?ts=2016-12-06T19:09:05Z&sa=one,two,three&ta=2016-12-06T19:09:05Z&ta=2016-12-06T19:09:05Z&ST=baz", nil)
@@ -198,7 +233,7 @@ func TestBindMultipartForm(t *testing.T) {
 }
 
 func TestBindUnsupportedMediaType(t *testing.T) {
-	testBindError(t, strings.NewReader(invalidContent), MIMEApplicationJSON)
+	testBindError(t, strings.NewReader(invalidContent), MIMEApplicationJSON, &json.SyntaxError{})
 }
 
 func TestBindbindData(t *testing.T) {
@@ -220,7 +255,7 @@ func TestBindUnmarshalTypeError(t *testing.T) {
 
 	err := c.Bind(u)
 
-	he := &HTTPError{Code: http.StatusBadRequest, Message: "Unmarshal type error: expected=int, got=string, field=id, offset=14"}
+	he := &HTTPError{Code: http.StatusBadRequest, Message: "Unmarshal type error: expected=int, got=string, field=id, offset=14", Internal: err.(*HTTPError).Internal}
 
 	assert.Equal(t, he, err)
 }
@@ -327,7 +362,7 @@ func testBindOkay(t *testing.T, r io.Reader, ctype string) {
 	}
 }
 
-func testBindError(t *testing.T, r io.Reader, ctype string) {
+func testBindError(t *testing.T, r io.Reader, ctype string, expectedInternal error) {
 	e := New()
 	req := httptest.NewRequest(POST, "/", r)
 	rec := httptest.NewRecorder()
@@ -341,10 +376,12 @@ func testBindError(t *testing.T, r io.Reader, ctype string) {
 		strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
 		if assert.IsType(t, new(HTTPError), err) {
 			assert.Equal(t, http.StatusBadRequest, err.(*HTTPError).Code)
+			assert.IsType(t, expectedInternal, err.(*HTTPError).Internal)
 		}
 	default:
 		if assert.IsType(t, new(HTTPError), err) {
 			assert.Equal(t, ErrUnsupportedMediaType, err)
+			assert.IsType(t, expectedInternal, err.(*HTTPError).Internal)
 		}
 	}
 }
diff --git a/echo_test.go b/echo_test.go
index 17c64f8..6bd1941 100644
--- a/echo_test.go
+++ b/echo_test.go
@@ -22,10 +22,13 @@ type (
 )
 
 const (
-	userJSON       = `{"id":1,"name":"Jon Snow"}`
-	userXML        = `<user><id>1</id><name>Jon Snow</name></user>`
-	userForm       = `id=1&name=Jon Snow`
-	invalidContent = "invalid content"
+	userJSON                    = `{"id":1,"name":"Jon Snow"}`
+	userXML                     = `<user><id>1</id><name>Jon Snow</name></user>`
+	userForm                    = `id=1&name=Jon Snow`
+	invalidContent              = "invalid content"
+	userJSONInvalidType         = `{"id":"1","name":"Jon Snow"}`
+	userXMLConvertNumberError   = `<user><id>Number one</id><name>Jon Snow</name></user>`
+	userXMLUnsupportedTypeError = `<user><>Number one</><name>Jon Snow</name></user>`
 )
 
 const userJSONPretty = `{
@@ -457,7 +460,7 @@ func request(method, path string, e *Echo) (int, string) {
 }
 
 func TestHTTPError(t *testing.T) {
-	err := NewHTTPError(400, map[string]interface{}{
+	err := NewHTTPError(http.StatusBadRequest, map[string]interface{}{
 		"code": 12,
 	})
 	assert.Equal(t, "code=400, message=map[code:12]", err.Error())
diff --git a/middleware/body_dump_test.go b/middleware/body_dump_test.go
index 188ed4f..8649034 100644
--- a/middleware/body_dump_test.go
+++ b/middleware/body_dump_test.go
@@ -60,12 +60,7 @@ func TestBodyDumpFails(t *testing.T) {
 		return errors.New("some error")
 	}
 
-	requestBody := ""
-	responseBody := ""
-	mw := BodyDump(func(c echo.Context, reqBody, resBody []byte) {
-		requestBody = string(reqBody)
-		responseBody = string(resBody)
-	})
+	mw := BodyDump(func(c echo.Context, reqBody, resBody []byte) {})
 
 	if !assert.Error(t, mw(h)(c)) {
 		t.FailNow()
@@ -84,8 +79,6 @@ func TestBodyDumpFails(t *testing.T) {
 				return true
 			},
 			Handler: func(c echo.Context, reqBody, resBody []byte) {
-				requestBody = string(reqBody)
-				responseBody = string(resBody)
 			},
 		})
 
diff --git a/middleware/cors_test.go b/middleware/cors_test.go
index 7031214..5a9d5f2 100644
--- a/middleware/cors_test.go
+++ b/middleware/cors_test.go
@@ -47,4 +47,22 @@ func TestCORS(t *testing.T) {
 	assert.NotEmpty(t, rec.Header().Get(echo.HeaderAccessControlAllowMethods))
 	assert.Equal(t, "true", rec.Header().Get(echo.HeaderAccessControlAllowCredentials))
 	assert.Equal(t, "3600", rec.Header().Get(echo.HeaderAccessControlMaxAge))
+
+	// Preflight request with `AllowOrigins` *
+	req = httptest.NewRequest(echo.OPTIONS, "/", nil)
+	rec = httptest.NewRecorder()
+	c = e.NewContext(req, rec)
+	req.Header.Set(echo.HeaderOrigin, "localhost")
+	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+	cors = CORSWithConfig(CORSConfig{
+		AllowOrigins:     []string{"*"},
+		AllowCredentials: true,
+		MaxAge:           3600,
+	})
+	h = cors(echo.NotFoundHandler)
+	h(c)
+	assert.Equal(t, "localhost", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
+	assert.NotEmpty(t, rec.Header().Get(echo.HeaderAccessControlAllowMethods))
+	assert.Equal(t, "true", rec.Header().Get(echo.HeaderAccessControlAllowCredentials))
+	assert.Equal(t, "3600", rec.Header().Get(echo.HeaderAccessControlMaxAge))
 }
diff --git a/middleware/logger_test.go b/middleware/logger_test.go
index 5aa988f..ea43bef 100644
--- a/middleware/logger_test.go
+++ b/middleware/logger_test.go
@@ -92,7 +92,7 @@ func TestLoggerTemplate(t *testing.T) {
 		Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}","user_agent":"${user_agent}",` +
 			`"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` +
 			`"latency_human":"${latency_human}","bytes_in":${bytes_in}, "path":"${path}", "referer":"${referer}",` +
-			`"bytes_out":${bytes_out},"ch":"${header:X-Custom-Header}",` +
+			`"bytes_out":${bytes_out},"ch":"${header:X-Custom-Header}", "protocol":"${protocol}"` +
 			`"us":"${query:username}", "cf":"${form:username}", "session":"${cookie:session}"}` + "\n",
 		Output: buf,
 	}))
diff --git a/middleware/rewrite_test.go b/middleware/rewrite_test.go
index 03f21ce..667f1fc 100644
--- a/middleware/rewrite_test.go
+++ b/middleware/rewrite_test.go
@@ -1,6 +1,7 @@
 package middleware
 
 import (
+	"io/ioutil"
 	"net/http/httptest"
 	"testing"
 
@@ -61,3 +62,35 @@ func TestEchoRewritePreMiddleware(t *testing.T) {
 	assert.Equal(t, "/new", req.URL.Path)
 	assert.Equal(t, 200, rec.Code)
 }
+
+// Issue #1143
+func TestRewriteWithConfigPreMiddleware_Issue1143(t *testing.T) {
+	e := echo.New()
+	r := e.Router()
+
+	e.Pre(RewriteWithConfig(RewriteConfig{
+		Rules: map[string]string{
+			"/api/*/mgmt/proj/*/agt": "/api/$1/hosts/$2",
+			"/api/*/mgmt/proj":       "/api/$1/eng",
+		},
+	}))
+
+	r.Add(echo.GET, "/api/:version/hosts/:name", func(c echo.Context) error {
+		return c.String(200, "hosts")
+	})
+	r.Add(echo.GET, "/api/:version/eng", func(c echo.Context) error {
+		return c.String(200, "eng")
+	})
+
+	for i := 0; i < 100; i++ {
+		req := httptest.NewRequest(echo.GET, "/api/v1/mgmt/proj/test/agt", nil)
+		rec := httptest.NewRecorder()
+		e.ServeHTTP(rec, req)
+		assert.Equal(t, "/api/v1/hosts/test", req.URL.Path)
+		assert.Equal(t, 200, rec.Code)
+
+		defer rec.Result().Body.Close()
+		bodyBytes, _ := ioutil.ReadAll(rec.Result().Body)
+		assert.Equal(t, "hosts", string(bodyBytes))
+	}
+}