Repository: labstack/echo
Pull request #2127 (2022-03-13 15:34:41)

Add new value binding methods (UnixTimeMilli,TextUnmarshaler,JSONUnmarshaler) to Valuebinder
Add new value binding methods (UnixTimeMilli,TextUnmarshaler,JSONUnmarshaler) to Valuebinder. As it adds only new methods to struct it is backwards compatible change.

* `UnixTimeMilli` binds `1647184410140` to `2022-03-13T15:13:30.140000000+00:00` useful when dealing with Javascript

* `TextUnmarshaler` binds value to destination implementing `encoding.TextUnmarshaler` interface
* `JSONUnmarshaler` binds value to destination implementing `json.Unmarshaler` interface

Requested in https://github.com/labstack/echo/discussions/2000#discussioncomment-2315815

codecov[bot]: # [Codecov](https://codecov.io/gh/labstack/echo/pull/2127?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack) Report
> Merging [#2127](https://codecov.io/gh/labstack/echo/pull/2127?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack) (7e32ac4) into [master](https://codecov.io/gh/labstack/echo/commit/3f5b733425617138573e3768381278f619561f7e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack) (3f5b733) will **increase** coverage by `0.10%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/labstack/echo/pull/2127/graphs/tree.svg?width=650&height=150&src=pr&token=vqnT76l79i&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack)](https://codecov.io/gh/labstack/echo/pull/2127?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack)

```diff
@@            Coverage Diff             @@
##           master    #2127      +/-   ##
==========================================
+ Coverage   92.10%   92.20%   +0.10%     
==========================================
  Files          37       37              
  Lines        3028     3067      +39     
==========================================
+ Hits         2789     2828      +39     
  Misses        150      150              
  Partials       89       89              
```


| [Impacted Files](https://codecov.io/gh/labstack/echo/pull/2127?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack) | Coverage Δ | |
|---|---|---|
| [binder.go](https://codecov.io/gh/labstack/echo/pull/2127/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack#diff-YmluZGVyLmdv) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/labstack/echo/pull/2127?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/labstack/echo/pull/2127?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack). Last update [3f5b733...7e32ac4](https://codecov.io/gh/labstack/echo/pull/2127?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=labstack).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 461ac89..ba75d71 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
 # Changelog
 
+## v4.7.2 - 2022-03-16
+
+**Fixes**
+
+* Fix nil pointer exception when calling Start again after address binding error [#2131](https://github.com/labstack/echo/pull/2131)
+* Fix CSRF middleware not being able to extract token from multipart/form-data form [#2136](https://github.com/labstack/echo/pull/2136)
+* Fix Timeout middleware write race [#2126](https://github.com/labstack/echo/pull/2126)
+
+**Enhancements**
+
+* Recover middleware should not log panic for aborted handler [#2134](https://github.com/labstack/echo/pull/2134)
+
+
+## v4.7.1 - 2022-03-13
+
+**Fixes**
+
+* Fix `e.Static`, `.File()`, `c.Attachment()` being picky with paths starting with `./`, `../` and `/` after 4.7.0 introduced echo.Filesystem support (Go1.16+) [#2123](https://github.com/labstack/echo/pull/2123)
+
+**Enhancements**
+
+* Remove some unused code [#2116](https://github.com/labstack/echo/pull/2116)
+
+
 ## v4.7.0 - 2022-03-01
 
 **Enhancements**
diff --git a/binder.go b/binder.go
index 0900ce8..5a6cf9d 100644
--- a/binder.go
+++ b/binder.go
@@ -1,6 +1,8 @@
 package echo
 
 import (
+	"encoding"
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"strconv"
@@ -52,8 +54,11 @@ import (
 		* time
 		* duration
 		* BindUnmarshaler() interface
+		* TextUnmarshaler() interface
+		* JSONUnmarshaler() interface
 		* UnixTime() - converts unix time (integer) to time.Time
-		* UnixTimeNano() - converts unix time with nano second precision (integer) to time.Time
+		* UnixTimeMilli() - converts unix time with millisecond precision (integer) to time.Time
+		* UnixTimeNano() - converts unix time with nanosecond precision (integer) to time.Time
 		* CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error`
 */
 
@@ -204,7 +209,7 @@ func (b *ValueBinder) CustomFunc(sourceParam string, customFunc func(values []st
 	return b.customFunc(sourceParam, customFunc, false)
 }
 
-// MustCustomFunc requires parameter values to exist to be bind with Func. Returns error when value does not exist.
+// MustCustomFunc requires parameter values to exist to bind with Func. Returns error when value does not exist.
 func (b *ValueBinder) MustCustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder {
 	return b.customFunc(sourceParam, customFunc, true)
 }
@@ -241,7 +246,7 @@ func (b *ValueBinder) String(sourceParam string, dest *string) *ValueBinder {
 	return b
 }
 
-// MustString requires parameter value to exist to be bind to string variable. Returns error when value does not exist
+// MustString requires parameter value to exist to bind to string variable. Returns error when value does not exist
 func (b *ValueBinder) MustString(sourceParam string, dest *string) *ValueBinder {
 	if b.failFast && b.errors != nil {
 		return b
@@ -270,7 +275,7 @@ func (b *ValueBinder) Strings(sourceParam string, dest *[]string) *ValueBinder {
 	return b
 }
 
-// MustStrings requires parameter values to exist to be bind to slice of string variables. Returns error when value does not exist
+// MustStrings requires parameter values to exist to bind to slice of string variables. Returns error when value does not exist
 func (b *ValueBinder) MustStrings(sourceParam string, dest *[]string) *ValueBinder {
 	if b.failFast && b.errors != nil {
 		return b
@@ -302,7 +307,7 @@ func (b *ValueBinder) BindUnmarshaler(sourceParam string, dest BindUnmarshaler)
 	return b
 }
 
-// MustBindUnmarshaler requires parameter value to exist to be bind to destination implementing BindUnmarshaler interface.
+// MustBindUnmarshaler requires parameter value to exist to bind to destination implementing BindUnmarshaler interface.
 // Returns error when value does not exist
 func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder {
 	if b.failFast && b.errors != nil {
@@ -321,13 +326,85 @@ func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshal
 	return b
 }
 
+// JSONUnmarshaler binds parameter to destination implementing json.Unmarshaler interface
+func (b *ValueBinder) JSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
+	if b.failFast && b.errors != nil {
+		return b
+	}
+
+	tmp := b.ValueFunc(sourceParam)
+	if tmp == "" {
+		return b
+	}
+
+	if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
+	}
+	return b
+}
+
+// MustJSONUnmarshaler requires parameter value to exist to bind to destination implementing json.Unmarshaler interface.
+// Returns error when value does not exist
+func (b *ValueBinder) MustJSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
+	if b.failFast && b.errors != nil {
+		return b
+	}
+
+	tmp := b.ValueFunc(sourceParam)
+	if tmp == "" {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
+		return b
+	}
+
+	if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
+	}
+	return b
+}
+
+// TextUnmarshaler binds parameter to destination implementing encoding.TextUnmarshaler interface
+func (b *ValueBinder) TextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
+	if b.failFast && b.errors != nil {
+		return b
+	}
+
+	tmp := b.ValueFunc(sourceParam)
+	if tmp == "" {
+		return b
+	}
+
+	if err := dest.UnmarshalText([]byte(tmp)); err != nil {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
+	}
+	return b
+}
+
+// MustTextUnmarshaler requires parameter value to exist to bind to destination implementing encoding.TextUnmarshaler interface.
+// Returns error when value does not exist
+func (b *ValueBinder) MustTextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
+	if b.failFast && b.errors != nil {
+		return b
+	}
+
+	tmp := b.ValueFunc(sourceParam)
+	if tmp == "" {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
+		return b
+	}
+
+	if err := dest.UnmarshalText([]byte(tmp)); err != nil {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
+	}
+	return b
+}
+
 // BindWithDelimiter binds parameter to destination by suitable conversion function.
 // Delimiter is used before conversion to split parameter value to separate values
 func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
 	return b.bindWithDelimiter(sourceParam, dest, delimiter, false)
 }
 
-// MustBindWithDelimiter requires parameter value to exist to be bind destination by suitable conversion function.
+// MustBindWithDelimiter requires parameter value to exist to bind destination by suitable conversion function.
 // Delimiter is used before conversion to split parameter value to separate values
 func (b *ValueBinder) MustBindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
 	return b.bindWithDelimiter(sourceParam, dest, delimiter, true)
@@ -376,7 +453,7 @@ func (b *ValueBinder) Int64(sourceParam string, dest *int64) *ValueBinder {
 	return b.intValue(sourceParam, dest, 64, false)
 }
 
-// MustInt64 requires parameter value to exist to be bind to int64 variable. Returns error when value does not exist
+// MustInt64 requires parameter value to exist to bind to int64 variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt64(sourceParam string, dest *int64) *ValueBinder {
 	return b.intValue(sourceParam, dest, 64, true)
 }
@@ -386,7 +463,7 @@ func (b *ValueBinder) Int32(sourceParam string, dest *int32) *ValueBinder {
 	return b.intValue(sourceParam, dest, 32, false)
 }
 
-// MustInt32 requires parameter value to exist to be bind to int32 variable. Returns error when value does not exist
+// MustInt32 requires parameter value to exist to bind to int32 variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt32(sourceParam string, dest *int32) *ValueBinder {
 	return b.intValue(sourceParam, dest, 32, true)
 }
@@ -396,7 +473,7 @@ func (b *ValueBinder) Int16(sourceParam string, dest *int16) *ValueBinder {
 	return b.intValue(sourceParam, dest, 16, false)
 }
 
-// MustInt16 requires parameter value to exist to be bind to int16 variable. Returns error when value does not exist
+// MustInt16 requires parameter value to exist to bind to int16 variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt16(sourceParam string, dest *int16) *ValueBinder {
 	return b.intValue(sourceParam, dest, 16, true)
 }
@@ -406,7 +483,7 @@ func (b *ValueBinder) Int8(sourceParam string, dest *int8) *ValueBinder {
 	return b.intValue(sourceParam, dest, 8, false)
 }
 
-// MustInt8 requires parameter value to exist to be bind to int8 variable. Returns error when value does not exist
+// MustInt8 requires parameter value to exist to bind to int8 variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt8(sourceParam string, dest *int8) *ValueBinder {
 	return b.intValue(sourceParam, dest, 8, true)
 }
@@ -416,7 +493,7 @@ func (b *ValueBinder) Int(sourceParam string, dest *int) *ValueBinder {
 	return b.intValue(sourceParam, dest, 0, false)
 }
 
-// MustInt requires parameter value to exist to be bind to int variable. Returns error when value does not exist
+// MustInt requires parameter value to exist to bind to int variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt(sourceParam string, dest *int) *ValueBinder {
 	return b.intValue(sourceParam, dest, 0, true)
 }
@@ -544,7 +621,7 @@ func (b *ValueBinder) Int64s(sourceParam string, dest *[]int64) *ValueBinder {
 	return b.intsValue(sourceParam, dest, false)
 }
 
-// MustInt64s requires parameter value to exist to be bind to int64 slice variable. Returns error when value does not exist
+// MustInt64s requires parameter value to exist to bind to int64 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt64s(sourceParam string, dest *[]int64) *ValueBinder {
 	return b.intsValue(sourceParam, dest, true)
 }
@@ -554,7 +631,7 @@ func (b *ValueBinder) Int32s(sourceParam string, dest *[]int32) *ValueBinder {
 	return b.intsValue(sourceParam, dest, false)
 }
 
-// MustInt32s requires parameter value to exist to be bind to int32 slice variable. Returns error when value does not exist
+// MustInt32s requires parameter value to exist to bind to int32 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt32s(sourceParam string, dest *[]int32) *ValueBinder {
 	return b.intsValue(sourceParam, dest, true)
 }
@@ -564,7 +641,7 @@ func (b *ValueBinder) Int16s(sourceParam string, dest *[]int16) *ValueBinder {
 	return b.intsValue(sourceParam, dest, false)
 }
 
-// MustInt16s requires parameter value to exist to be bind to int16 slice variable. Returns error when value does not exist
+// MustInt16s requires parameter value to exist to bind to int16 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt16s(sourceParam string, dest *[]int16) *ValueBinder {
 	return b.intsValue(sourceParam, dest, true)
 }
@@ -574,7 +651,7 @@ func (b *ValueBinder) Int8s(sourceParam string, dest *[]int8) *ValueBinder {
 	return b.intsValue(sourceParam, dest, false)
 }
 
-// MustInt8s requires parameter value to exist to be bind to int8 slice variable. Returns error when value does not exist
+// MustInt8s requires parameter value to exist to bind to int8 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt8s(sourceParam string, dest *[]int8) *ValueBinder {
 	return b.intsValue(sourceParam, dest, true)
 }
@@ -584,7 +661,7 @@ func (b *ValueBinder) Ints(sourceParam string, dest *[]int) *ValueBinder {
 	return b.intsValue(sourceParam, dest, false)
 }
 
-// MustInts requires parameter value to exist to be bind to int slice variable. Returns error when value does not exist
+// MustInts requires parameter value to exist to bind to int slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustInts(sourceParam string, dest *[]int) *ValueBinder {
 	return b.intsValue(sourceParam, dest, true)
 }
@@ -594,7 +671,7 @@ func (b *ValueBinder) Uint64(sourceParam string, dest *uint64) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 64, false)
 }
 
-// MustUint64 requires parameter value to exist to be bind to uint64 variable. Returns error when value does not exist
+// MustUint64 requires parameter value to exist to bind to uint64 variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint64(sourceParam string, dest *uint64) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 64, true)
 }
@@ -604,7 +681,7 @@ func (b *ValueBinder) Uint32(sourceParam string, dest *uint32) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 32, false)
 }
 
-// MustUint32 requires parameter value to exist to be bind to uint32 variable. Returns error when value does not exist
+// MustUint32 requires parameter value to exist to bind to uint32 variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint32(sourceParam string, dest *uint32) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 32, true)
 }
@@ -614,7 +691,7 @@ func (b *ValueBinder) Uint16(sourceParam string, dest *uint16) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 16, false)
 }
 
-// MustUint16 requires parameter value to exist to be bind to uint16 variable. Returns error when value does not exist
+// MustUint16 requires parameter value to exist to bind to uint16 variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint16(sourceParam string, dest *uint16) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 16, true)
 }
@@ -624,7 +701,7 @@ func (b *ValueBinder) Uint8(sourceParam string, dest *uint8) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 8, false)
 }
 
-// MustUint8 requires parameter value to exist to be bind to uint8 variable. Returns error when value does not exist
+// MustUint8 requires parameter value to exist to bind to uint8 variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint8(sourceParam string, dest *uint8) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 8, true)
 }
@@ -634,7 +711,7 @@ func (b *ValueBinder) Byte(sourceParam string, dest *byte) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 8, false)
 }
 
-// MustByte requires parameter value to exist to be bind to byte variable. Returns error when value does not exist
+// MustByte requires parameter value to exist to bind to byte variable. Returns error when value does not exist
 func (b *ValueBinder) MustByte(sourceParam string, dest *byte) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 8, true)
 }
@@ -644,7 +721,7 @@ func (b *ValueBinder) Uint(sourceParam string, dest *uint) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 0, false)
 }
 
-// MustUint requires parameter value to exist to be bind to uint variable. Returns error when value does not exist
+// MustUint requires parameter value to exist to bind to uint variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint(sourceParam string, dest *uint) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 0, true)
 }
@@ -772,7 +849,7 @@ func (b *ValueBinder) Uint64s(sourceParam string, dest *[]uint64) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, false)
 }
 
-// MustUint64s requires parameter value to exist to be bind to uint64 slice variable. Returns error when value does not exist
+// MustUint64s requires parameter value to exist to bind to uint64 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint64s(sourceParam string, dest *[]uint64) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, true)
 }
@@ -782,7 +859,7 @@ func (b *ValueBinder) Uint32s(sourceParam string, dest *[]uint32) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, false)
 }
 
-// MustUint32s requires parameter value to exist to be bind to uint32 slice variable. Returns error when value does not exist
+// MustUint32s requires parameter value to exist to bind to uint32 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint32s(sourceParam string, dest *[]uint32) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, true)
 }
@@ -792,7 +869,7 @@ func (b *ValueBinder) Uint16s(sourceParam string, dest *[]uint16) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, false)
 }
 
-// MustUint16s requires parameter value to exist to be bind to uint16 slice variable. Returns error when value does not exist
+// MustUint16s requires parameter value to exist to bind to uint16 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint16s(sourceParam string, dest *[]uint16) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, true)
 }
@@ -802,7 +879,7 @@ func (b *ValueBinder) Uint8s(sourceParam string, dest *[]uint8) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, false)
 }
 
-// MustUint8s requires parameter value to exist to be bind to uint8 slice variable. Returns error when value does not exist
+// MustUint8s requires parameter value to exist to bind to uint8 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint8s(sourceParam string, dest *[]uint8) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, true)
 }
@@ -812,7 +889,7 @@ func (b *ValueBinder) Uints(sourceParam string, dest *[]uint) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, false)
 }
 
-// MustUints requires parameter value to exist to be bind to uint slice variable. Returns error when value does not exist
+// MustUints requires parameter value to exist to bind to uint slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustUints(sourceParam string, dest *[]uint) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, true)
 }
@@ -822,7 +899,7 @@ func (b *ValueBinder) Bool(sourceParam string, dest *bool) *ValueBinder {
 	return b.boolValue(sourceParam, dest, false)
 }
 
-// MustBool requires parameter value to exist to be bind to bool variable. Returns error when value does not exist
+// MustBool requires parameter value to exist to bind to bool variable. Returns error when value does not exist
 func (b *ValueBinder) MustBool(sourceParam string, dest *bool) *ValueBinder {
 	return b.boolValue(sourceParam, dest, true)
 }
@@ -887,7 +964,7 @@ func (b *ValueBinder) Bools(sourceParam string, dest *[]bool) *ValueBinder {
 	return b.boolsValue(sourceParam, dest, false)
 }
 
-// MustBools requires parameter values to exist to be bind to slice of bool variables. Returns error when values does not exist
+// MustBools requires parameter values to exist to bind to slice of bool variables. Returns error when values does not exist
 func (b *ValueBinder) MustBools(sourceParam string, dest *[]bool) *ValueBinder {
 	return b.boolsValue(sourceParam, dest, true)
 }
@@ -897,7 +974,7 @@ func (b *ValueBinder) Float64(sourceParam string, dest *float64) *ValueBinder {
 	return b.floatValue(sourceParam, dest, 64, false)
 }
 
-// MustFloat64 requires parameter value to exist to be bind to float64 variable. Returns error when value does not exist
+// MustFloat64 requires parameter value to exist to bind to float64 variable. Returns error when value does not exist
 func (b *ValueBinder) MustFloat64(sourceParam string, dest *float64) *ValueBinder {
 	return b.floatValue(sourceParam, dest, 64, true)
 }
@@ -907,7 +984,7 @@ func (b *ValueBinder) Float32(sourceParam string, dest *float32) *ValueBinder {
 	return b.floatValue(sourceParam, dest, 32, false)
 }
 
-// MustFloat32 requires parameter value to exist to be bind to float32 variable. Returns error when value does not exist
+// MustFloat32 requires parameter value to exist to bind to float32 variable. Returns error when value does not exist
 func (b *ValueBinder) MustFloat32(sourceParam string, dest *float32) *ValueBinder {
 	return b.floatValue(sourceParam, dest, 32, true)
 }
@@ -992,7 +1069,7 @@ func (b *ValueBinder) Float64s(sourceParam string, dest *[]float64) *ValueBinder
 	return b.floatsValue(sourceParam, dest, false)
 }
 
-// MustFloat64s requires parameter values to exist to be bind to slice of float64 variables. Returns error when values does not exist
+// MustFloat64s requires parameter values to exist to bind to slice of float64 variables. Returns error when values does not exist
 func (b *ValueBinder) MustFloat64s(sourceParam string, dest *[]float64) *ValueBinder {
 	return b.floatsValue(sourceParam, dest, true)
 }
@@ -1002,7 +1079,7 @@ func (b *ValueBinder) Float32s(sourceParam string, dest *[]float32) *ValueBinder
 	return b.floatsValue(sourceParam, dest, false)
 }
 
-// MustFloat32s requires parameter values to exist to be bind to slice of float32 variables. Returns error when values does not exist
+// MustFloat32s requires parameter values to exist to bind to slice of float32 variables. Returns error when values does not exist
 func (b *ValueBinder) MustFloat32s(sourceParam string, dest *[]float32) *ValueBinder {
 	return b.floatsValue(sourceParam, dest, true)
 }
@@ -1012,7 +1089,7 @@ func (b *ValueBinder) Time(sourceParam string, dest *time.Time, layout string) *
 	return b.time(sourceParam, dest, layout, false)
 }
 
-// MustTime requires parameter value to exist to be bind to time.Time variable. Returns error when value does not exist
+// MustTime requires parameter value to exist to bind to time.Time variable. Returns error when value does not exist
 func (b *ValueBinder) MustTime(sourceParam string, dest *time.Time, layout string) *ValueBinder {
 	return b.time(sourceParam, dest, layout, true)
 }
@@ -1043,7 +1120,7 @@ func (b *ValueBinder) Times(sourceParam string, dest *[]time.Time, layout string
 	return b.times(sourceParam, dest, layout, false)
 }
 
-// MustTimes requires parameter values to exist to be bind to slice of time.Time variables. Returns error when values does not exist
+// MustTimes requires parameter values to exist to bind to slice of time.Time variables. Returns error when values does not exist
 func (b *ValueBinder) MustTimes(sourceParam string, dest *[]time.Time, layout string) *ValueBinder {
 	return b.times(sourceParam, dest, layout, true)
 }
@@ -1084,7 +1161,7 @@ func (b *ValueBinder) Duration(sourceParam string, dest *time.Duration) *ValueBi
 	return b.duration(sourceParam, dest, false)
 }
 
-// MustDuration requires parameter value to exist to be bind to time.Duration variable. Returns error when value does not exist
+// MustDuration requires parameter value to exist to bind to time.Duration variable. Returns error when value does not exist
 func (b *ValueBinder) MustDuration(sourceParam string, dest *time.Duration) *ValueBinder {
 	return b.duration(sourceParam, dest, true)
 }
@@ -1115,7 +1192,7 @@ func (b *ValueBinder) Durations(sourceParam string, dest *[]time.Duration) *Valu
 	return b.durationsValue(sourceParam, dest, false)
 }
 
-// MustDurations requires parameter values to exist to be bind to slice of time.Duration variables. Returns error when values does not exist
+// MustDurations requires parameter values to exist to bind to slice of time.Duration variables. Returns error when values does not exist
 func (b *ValueBinder) MustDurations(sourceParam string, dest *[]time.Duration) *ValueBinder {
 	return b.durationsValue(sourceParam, dest, true)
 }
@@ -1161,10 +1238,10 @@ func (b *ValueBinder) durations(sourceParam string, values []string, dest *[]tim
 // Note:
 //  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
 func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder {
-	return b.unixTime(sourceParam, dest, false, false)
+	return b.unixTime(sourceParam, dest, false, time.Second)
 }
 
-// MustUnixTime requires parameter value to exist to be bind to time.Duration variable  (in local Time corresponding
+// MustUnixTime requires parameter value to exist to bind to time.Duration variable (in local time corresponding
 // to the given Unix time). Returns error when value does not exist.
 //
 // Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
@@ -1172,10 +1249,31 @@ func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder
 // Note:
 //  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
 func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder {
-	return b.unixTime(sourceParam, dest, true, false)
+	return b.unixTime(sourceParam, dest, true, time.Second)
+}
+
+// UnixTimeMilli binds parameter to time.Time variable (in local time corresponding to the given Unix time in millisecond precision).
+//
+// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
+//
+// Note:
+//  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
+	return b.unixTime(sourceParam, dest, false, time.Millisecond)
 }
 
-// UnixTimeNano binds parameter to time.Time variable (in local Time corresponding to the given Unix time in nano second precision).
+// MustUnixTimeMilli requires parameter value to exist to bind to time.Duration variable  (in local time corresponding
+// to the given Unix time in millisecond precision). Returns error when value does not exist.
+//
+// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
+//
+// Note:
+//  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
+	return b.unixTime(sourceParam, dest, true, time.Millisecond)
+}
+
+// UnixTimeNano binds parameter to time.Time variable (in local time corresponding to the given Unix time in nanosecond precision).
 //
 // Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
 // Example:          1000000000 binds to 1970-01-01T00:00:01.000000000+00:00
@@ -1185,10 +1283,10 @@ func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBi
 //  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
 //  * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
 func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
-	return b.unixTime(sourceParam, dest, false, true)
+	return b.unixTime(sourceParam, dest, false, time.Nanosecond)
 }
 
-// MustUnixTimeNano requires parameter value to exist to be bind to time.Duration variable  (in local Time corresponding
+// MustUnixTimeNano requires parameter value to exist to bind to time.Duration variable  (in local Time corresponding
 // to the given Unix time value in nano second precision). Returns error when value does not exist.
 //
 // Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
@@ -1199,10 +1297,10 @@ func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBi
 //  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
 //  * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
 func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
-	return b.unixTime(sourceParam, dest, true, true)
+	return b.unixTime(sourceParam, dest, true, time.Nanosecond)
 }
 
-func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, isNano bool) *ValueBinder {
+func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, precision time.Duration) *ValueBinder {
 	if b.failFast && b.errors != nil {
 		return b
 	}
@@ -1221,10 +1319,13 @@ func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExi
 		return b
 	}
 
-	if isNano {
-		*dest = time.Unix(0, n)
-	} else {
+	switch precision {
+	case time.Second:
 		*dest = time.Unix(n, 0)
+	case time.Millisecond:
+		*dest = time.Unix(n/1e3, (n%1e3)*1e6) // TODO: time.UnixMilli(n) exists since Go1.17 switch to that when min version allows
+	case time.Nanosecond:
+		*dest = time.Unix(0, n)
 	}
 	return b
 }
diff --git a/echo.go b/echo.go
index 143f9ff..8829619 100644
--- a/echo.go
+++ b/echo.go
@@ -246,7 +246,7 @@ const (
 
 const (
 	// Version of Echo
-	Version = "4.7.0"
+	Version = "4.7.2"
 	website = "https://echo.labstack.com"
 	// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
 	banner = `
@@ -732,7 +732,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
 	return s.Serve(e.Listener)
 }
 
-func (e *Echo) configureServer(s *http.Server) (err error) {
+func (e *Echo) configureServer(s *http.Server) error {
 	// Setup
 	e.colorer.SetOutput(e.Logger.Output())
 	s.ErrorLog = e.StdLogger
@@ -747,10 +747,11 @@ func (e *Echo) configureServer(s *http.Server) (err error) {
 
 	if s.TLSConfig == nil {
 		if e.Listener == nil {
-			e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
+			l, err := newListener(s.Addr, e.ListenerNetwork)
 			if err != nil {
 				return err
 			}
+			e.Listener = l
 		}
 		if !e.HidePort {
 			e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
@@ -791,7 +792,7 @@ func (e *Echo) TLSListenerAddr() net.Addr {
 }
 
 // StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext).
-func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
+func (e *Echo) StartH2CServer(address string, h2s *http2.Server) error {
 	e.startupMutex.Lock()
 	// Setup
 	s := e.Server
@@ -808,11 +809,12 @@ func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
 	}
 
 	if e.Listener == nil {
-		e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
+		l, err := newListener(s.Addr, e.ListenerNetwork)
 		if err != nil {
 			e.startupMutex.Unlock()
 			return err
 		}
+		e.Listener = l
 	}
 	if !e.HidePort {
 		e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
diff --git a/middleware/extractor.go b/middleware/extractor.go
index a57ed4e..afdfd81 100644
--- a/middleware/extractor.go
+++ b/middleware/extractor.go
@@ -168,8 +168,8 @@ func valuesFromCookie(name string) ValuesExtractor {
 // valuesFromForm returns a function that extracts values from the form field.
 func valuesFromForm(name string) ValuesExtractor {
 	return func(c echo.Context) ([]string, error) {
-		if parseErr := c.Request().ParseForm(); parseErr != nil {
-			return nil, fmt.Errorf("valuesFromForm parse form failed: %w", parseErr)
+		if c.Request().Form == nil {
+			_ = c.Request().ParseMultipartForm(32 << 20) // same what `c.Request().FormValue(name)` does
 		}
 		values := c.Request().Form[name]
 		if len(values) == 0 {
diff --git a/middleware/recover.go b/middleware/recover.go
index a621a9e..7b61285 100644
--- a/middleware/recover.go
+++ b/middleware/recover.go
@@ -2,6 +2,7 @@ package middleware
 
 import (
 	"fmt"
+	"net/http"
 	"runtime"
 
 	"github.com/labstack/echo/v4"
@@ -77,6 +78,9 @@ func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
 
 			defer func() {
 				if r := recover(); r != nil {
+					if r == http.ErrAbortHandler {
+						panic(r)
+					}
 					err, ok := r.(error)
 					if !ok {
 						err = fmt.Errorf("%v", r)
diff --git a/middleware/timeout.go b/middleware/timeout.go
index 768ef8d..4e8836c 100644
--- a/middleware/timeout.go
+++ b/middleware/timeout.go
@@ -2,10 +2,10 @@ package middleware
 
 import (
 	"context"
+	"github.com/labstack/echo/v4"
 	"net/http"
+	"sync"
 	"time"
-
-	"github.com/labstack/echo/v4"
 )
 
 // ---------------------------------------------------------------------------------------------------------------
@@ -55,29 +55,27 @@ import (
 //	})
 //
 
-type (
-	// TimeoutConfig defines the config for Timeout middleware.
-	TimeoutConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// ErrorMessage is written to response on timeout in addition to http.StatusServiceUnavailable (503) status code
-		// It can be used to define a custom timeout error message
-		ErrorMessage string
-
-		// OnTimeoutRouteErrorHandler is an error handler that is executed for error that was returned from wrapped route after
-		// request timeouted and we already had sent the error code (503) and message response to the client.
-		// NB: do not write headers/body inside this handler. The response has already been sent to the client and response writer
-		// will not accept anything no more. If you want to know what actual route middleware timeouted use `c.Path()`
-		OnTimeoutRouteErrorHandler func(err error, c echo.Context)
-
-		// Timeout configures a timeout for the middleware, defaults to 0 for no timeout
-		// NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
-		// the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
-		// difference over 500microseconds (0.5millisecond) response seems to be reliable
-		Timeout time.Duration
-	}
-)
+// TimeoutConfig defines the config for Timeout middleware.
+type TimeoutConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
+
+	// ErrorMessage is written to response on timeout in addition to http.StatusServiceUnavailable (503) status code
+	// It can be used to define a custom timeout error message
+	ErrorMessage string
+
+	// OnTimeoutRouteErrorHandler is an error handler that is executed for error that was returned from wrapped route after
+	// request timeouted and we already had sent the error code (503) and message response to the client.
+	// NB: do not write headers/body inside this handler. The response has already been sent to the client and response writer
+	// will not accept anything no more. If you want to know what actual route middleware timeouted use `c.Path()`
+	OnTimeoutRouteErrorHandler func(err error, c echo.Context)
+
+	// Timeout configures a timeout for the middleware, defaults to 0 for no timeout
+	// NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
+	// the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
+	// difference over 500microseconds (0.5millisecond) response seems to be reliable
+	Timeout time.Duration
+}
 
 var (
 	// DefaultTimeoutConfig is the default Timeout middleware config.
@@ -94,10 +92,17 @@ func Timeout() echo.MiddlewareFunc {
 	return TimeoutWithConfig(DefaultTimeoutConfig)
 }
 
-// TimeoutWithConfig returns a Timeout middleware with config.
-// See: `Timeout()`.
+// TimeoutWithConfig returns a Timeout middleware with config or panics on invalid configuration.
 func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
-	// Defaults
+	mw, err := config.ToMiddleware()
+	if err != nil {
+		panic(err)
+	}
+	return mw
+}
+
+// ToMiddleware converts Config to middleware or returns an error for invalid configuration
+func (config TimeoutConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
 	if config.Skipper == nil {
 		config.Skipper = DefaultTimeoutConfig.Skipper
 	}
@@ -108,26 +113,29 @@ func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
 				return next(c)
 			}
 
+			errChan := make(chan error, 1)
 			handlerWrapper := echoHandlerFuncWrapper{
+				writer:     &ignorableWriter{ResponseWriter: c.Response().Writer},
 				ctx:        c,
 				handler:    next,
-				errChan:    make(chan error, 1),
+				errChan:    errChan,
 				errHandler: config.OnTimeoutRouteErrorHandler,
 			}
 			handler := http.TimeoutHandler(handlerWrapper, config.Timeout, config.ErrorMessage)
-			handler.ServeHTTP(c.Response().Writer, c.Request())
+			handler.ServeHTTP(handlerWrapper.writer, c.Request())
 
 			select {
-			case err := <-handlerWrapper.errChan:
+			case err := <-errChan:
 				return err
 			default:
 				return nil
 			}
 		}
-	}
+	}, nil
 }
 
 type echoHandlerFuncWrapper struct {
+	writer     *ignorableWriter
 	ctx        echo.Context
 	handler    echo.HandlerFunc
 	errHandler func(err error, c echo.Context)
@@ -160,23 +168,53 @@ func (t echoHandlerFuncWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Reques
 		}
 		return // on timeout we can not send handler error to client because `http.TimeoutHandler` has already sent headers
 	}
-	// we restore original writer only for cases we did not timeout. On timeout we have already sent response to client
-	// and should not anymore send additional headers/data
-	// so on timeout writer stays what http.TimeoutHandler uses and prevents writing headers/body
 	if err != nil {
-		// Error must be written into Writer created in `http.TimeoutHandler` so to get Response into `commited` state.
-		// So call global error handler to write error to the client. This is needed or `http.TimeoutHandler` will send
-		// status code by itself and after that our tries to write status code will not work anymore and/or create errors in
-		// log about `superfluous response.WriteHeader call from`
-		t.ctx.Error(err)
-		// we pass error from handler to middlewares up in handler chain to act on it if needed. But this means that
-		// global error handler is probably be called twice as `t.ctx.Error` already does that.
-
-		// NB: later call of the global error handler or middlewares will not take any effect, as echo.Response will be
-		// already marked as `committed` because we called global error handler above.
-		t.ctx.Response().Writer = originalWriter // make sure we restore before we signal original coroutine about the error
+		// This is needed as `http.TimeoutHandler` will write status code by itself on error and after that our tries to write
+		// status code will not work anymore as Echo.Response thinks it has been already "committed" and further writes
+		// create errors in log about `superfluous response.WriteHeader call from`
+		t.writer.Ignore(true)
+		t.ctx.Response().Writer = originalWriter // make sure we restore writer before we signal original coroutine about the error
+		// we pass error from handler to middlewares up in handler chain to act on it if needed.
 		t.errChan <- err
 		return
 	}
+	// we restore original writer only for cases we did not timeout. On timeout we have already sent response to client
+	// and should not anymore send additional headers/data
+	// so on timeout writer stays what http.TimeoutHandler uses and prevents writing headers/body
 	t.ctx.Response().Writer = originalWriter
 }
+
+// ignorableWriter is ResponseWriter implementations that allows us to mark writer to ignore further write calls. This
+// is handy in cases when you do not have direct control of code being executed (3rd party middleware) but want to make
+// sure that external code will not be able to write response to the client.
+// Writer is coroutine safe for writes.
+type ignorableWriter struct {
+	http.ResponseWriter
+
+	lock         sync.Mutex
+	ignoreWrites bool
+}
+
+func (w *ignorableWriter) Ignore(ignore bool) {
+	w.lock.Lock()
+	w.ignoreWrites = ignore
+	w.lock.Unlock()
+}
+
+func (w *ignorableWriter) WriteHeader(code int) {
+	w.lock.Lock()
+	defer w.lock.Unlock()
+	if w.ignoreWrites {
+		return
+	}
+	w.ResponseWriter.WriteHeader(code)
+}
+
+func (w *ignorableWriter) Write(b []byte) (int, error) {
+	w.lock.Lock()
+	defer w.lock.Unlock()
+	if w.ignoreWrites {
+		return len(b), nil
+	}
+	return w.ResponseWriter.Write(b)
+}


Test patch:
diff --git a/binder_test.go b/binder_test.go
index 0349677..910bbfc 100644
--- a/binder_test.go
+++ b/binder_test.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"github.com/stretchr/testify/assert"
 	"io"
+	"math/big"
 	"net/http"
 	"net/http/httptest"
 	"strconv"
@@ -2187,6 +2188,188 @@ func TestValueBinder_BindUnmarshaler(t *testing.T) {
 	}
 }
 
+func TestValueBinder_JSONUnmarshaler(t *testing.T) {
+	example := big.NewInt(999)
+
+	var testCases = []struct {
+		name            string
+		givenFailFast   bool
+		givenBindErrors []error
+		whenURL         string
+		whenMust        bool
+		expectValue     big.Int
+		expectError     string
+	}{
+		{
+			name:        "ok, binds value",
+			whenURL:     "/search?param=999&param=998",
+			expectValue: *example,
+		},
+		{
+			name:        "ok, params values empty, value is not changed",
+			whenURL:     "/search?nope=1",
+			expectValue: big.Int{},
+		},
+		{
+			name:          "nok, previous errors fail fast without binding value",
+			givenFailFast: true,
+			whenURL:       "/search?param=1&param=100",
+			expectValue:   big.Int{},
+			expectError:   "previous error",
+		},
+		{
+			name:        "nok, conversion fails, value is not changed",
+			whenURL:     "/search?param=nope&param=xxx",
+			expectValue: big.Int{},
+			expectError: "code=400, message=failed to bind field value to json.Unmarshaler interface, internal=math/big: cannot unmarshal \"nope\" into a *big.Int, field=param",
+		},
+		{
+			name:        "ok (must), binds value",
+			whenMust:    true,
+			whenURL:     "/search?param=999&param=998",
+			expectValue: *example,
+		},
+		{
+			name:        "ok (must), params values empty, returns error, value is not changed",
+			whenMust:    true,
+			whenURL:     "/search?nope=1",
+			expectValue: big.Int{},
+			expectError: "code=400, message=required field value is empty, field=param",
+		},
+		{
+			name:          "nok (must), previous errors fail fast without binding value",
+			givenFailFast: true,
+			whenMust:      true,
+			whenURL:       "/search?param=1&param=xxx",
+			expectValue:   big.Int{},
+			expectError:   "previous error",
+		},
+		{
+			name:        "nok (must), conversion fails, value is not changed",
+			whenMust:    true,
+			whenURL:     "/search?param=nope&param=xxx",
+			expectValue: big.Int{},
+			expectError: "code=400, message=failed to bind field value to json.Unmarshaler interface, internal=math/big: cannot unmarshal \"nope\" into a *big.Int, field=param",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			c := createTestContext(tc.whenURL, nil, nil)
+			b := QueryParamsBinder(c).FailFast(tc.givenFailFast)
+			if tc.givenFailFast {
+				b.errors = []error{errors.New("previous error")}
+			}
+
+			var dest big.Int
+			var err error
+			if tc.whenMust {
+				err = b.MustJSONUnmarshaler("param", &dest).BindError()
+			} else {
+				err = b.JSONUnmarshaler("param", &dest).BindError()
+			}
+
+			assert.Equal(t, tc.expectValue, dest)
+			if tc.expectError != "" {
+				assert.EqualError(t, err, tc.expectError)
+			} else {
+				assert.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestValueBinder_TextUnmarshaler(t *testing.T) {
+	example := big.NewInt(999)
+
+	var testCases = []struct {
+		name            string
+		givenFailFast   bool
+		givenBindErrors []error
+		whenURL         string
+		whenMust        bool
+		expectValue     big.Int
+		expectError     string
+	}{
+		{
+			name:        "ok, binds value",
+			whenURL:     "/search?param=999&param=998",
+			expectValue: *example,
+		},
+		{
+			name:        "ok, params values empty, value is not changed",
+			whenURL:     "/search?nope=1",
+			expectValue: big.Int{},
+		},
+		{
+			name:          "nok, previous errors fail fast without binding value",
+			givenFailFast: true,
+			whenURL:       "/search?param=1&param=100",
+			expectValue:   big.Int{},
+			expectError:   "previous error",
+		},
+		{
+			name:        "nok, conversion fails, value is not changed",
+			whenURL:     "/search?param=nope&param=xxx",
+			expectValue: big.Int{},
+			expectError: "code=400, message=failed to bind field value to encoding.TextUnmarshaler interface, internal=math/big: cannot unmarshal \"nope\" into a *big.Int, field=param",
+		},
+		{
+			name:        "ok (must), binds value",
+			whenMust:    true,
+			whenURL:     "/search?param=999&param=998",
+			expectValue: *example,
+		},
+		{
+			name:        "ok (must), params values empty, returns error, value is not changed",
+			whenMust:    true,
+			whenURL:     "/search?nope=1",
+			expectValue: big.Int{},
+			expectError: "code=400, message=required field value is empty, field=param",
+		},
+		{
+			name:          "nok (must), previous errors fail fast without binding value",
+			givenFailFast: true,
+			whenMust:      true,
+			whenURL:       "/search?param=1&param=xxx",
+			expectValue:   big.Int{},
+			expectError:   "previous error",
+		},
+		{
+			name:        "nok (must), conversion fails, value is not changed",
+			whenMust:    true,
+			whenURL:     "/search?param=nope&param=xxx",
+			expectValue: big.Int{},
+			expectError: "code=400, message=failed to bind field value to encoding.TextUnmarshaler interface, internal=math/big: cannot unmarshal \"nope\" into a *big.Int, field=param",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			c := createTestContext(tc.whenURL, nil, nil)
+			b := QueryParamsBinder(c).FailFast(tc.givenFailFast)
+			if tc.givenFailFast {
+				b.errors = []error{errors.New("previous error")}
+			}
+
+			var dest big.Int
+			var err error
+			if tc.whenMust {
+				err = b.MustTextUnmarshaler("param", &dest).BindError()
+			} else {
+				err = b.TextUnmarshaler("param", &dest).BindError()
+			}
+
+			assert.Equal(t, tc.expectValue, dest)
+			if tc.expectError != "" {
+				assert.EqualError(t, err, tc.expectError)
+			} else {
+				assert.NoError(t, err)
+			}
+		})
+	}
+}
+
 func TestValueBinder_BindWithDelimiter_types(t *testing.T) {
 	var testCases = []struct {
 		name    string
@@ -2529,6 +2712,97 @@ func TestValueBinder_UnixTime(t *testing.T) {
 	}
 }
 
+func TestValueBinder_UnixTimeMilli(t *testing.T) {
+	exampleTime, _ := time.Parse(time.RFC3339Nano, "2022-03-13T15:13:30.140000000+00:00") // => 1647184410140
+	var testCases = []struct {
+		name            string
+		givenFailFast   bool
+		givenBindErrors []error
+		whenURL         string
+		whenMust        bool
+		expectValue     time.Time
+		expectError     string
+	}{
+		{
+			name:        "ok, binds value, unix time in milliseconds",
+			whenURL:     "/search?param=1647184410140&param=1647184410199",
+			expectValue: exampleTime,
+		},
+		{
+			name:        "ok, params values empty, value is not changed",
+			whenURL:     "/search?nope=1",
+			expectValue: time.Time{},
+		},
+		{
+			name:          "nok, previous errors fail fast without binding value",
+			givenFailFast: true,
+			whenURL:       "/search?param=1&param=100",
+			expectValue:   time.Time{},
+			expectError:   "previous error",
+		},
+		{
+			name:        "nok, conversion fails, value is not changed",
+			whenURL:     "/search?param=nope&param=100",
+			expectValue: time.Time{},
+			expectError: "code=400, message=failed to bind field value to Time, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param",
+		},
+		{
+			name:        "ok (must), binds value",
+			whenMust:    true,
+			whenURL:     "/search?param=1647184410140&param=1647184410199",
+			expectValue: exampleTime,
+		},
+		{
+			name:        "ok (must), params values empty, returns error, value is not changed",
+			whenMust:    true,
+			whenURL:     "/search?nope=1",
+			expectValue: time.Time{},
+			expectError: "code=400, message=required field value is empty, field=param",
+		},
+		{
+			name:          "nok (must), previous errors fail fast without binding value",
+			givenFailFast: true,
+			whenMust:      true,
+			whenURL:       "/search?param=1&param=100",
+			expectValue:   time.Time{},
+			expectError:   "previous error",
+		},
+		{
+			name:        "nok (must), conversion fails, value is not changed",
+			whenMust:    true,
+			whenURL:     "/search?param=nope&param=100",
+			expectValue: time.Time{},
+			expectError: "code=400, message=failed to bind field value to Time, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			c := createTestContext(tc.whenURL, nil, nil)
+			b := QueryParamsBinder(c).FailFast(tc.givenFailFast)
+			if tc.givenFailFast {
+				b.errors = []error{errors.New("previous error")}
+			}
+
+			dest := time.Time{}
+			var err error
+			if tc.whenMust {
+				err = b.MustUnixTimeMilli("param", &dest).BindError()
+			} else {
+				err = b.UnixTimeMilli("param", &dest).BindError()
+			}
+
+			assert.Equal(t, tc.expectValue.UnixNano(), dest.UnixNano())
+			assert.Equal(t, tc.expectValue.In(time.UTC), dest.In(time.UTC))
+			if tc.expectError != "" {
+				assert.EqualError(t, err, tc.expectError)
+			} else {
+				assert.NoError(t, err)
+			}
+		})
+	}
+}
+
 func TestValueBinder_UnixTimeNano(t *testing.T) {
 	exampleTime, _ := time.Parse(time.RFC3339, "2020-12-28T18:36:43.000000000+00:00")         // => 1609180603
 	exampleTimeNano, _ := time.Parse(time.RFC3339Nano, "2020-12-28T18:36:43.123456789+00:00") // => 1609180603123456789
diff --git a/middleware/extractor_test.go b/middleware/extractor_test.go
index ae4b30a..2e898f5 100644
--- a/middleware/extractor_test.go
+++ b/middleware/extractor_test.go
@@ -1,9 +1,11 @@
 package middleware
 
 import (
+	"bytes"
 	"fmt"
 	"github.com/labstack/echo/v4"
 	"github.com/stretchr/testify/assert"
+	"mime/multipart"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -499,6 +501,25 @@ func TestValuesFromForm(t *testing.T) {
 		return req
 	}
 
+	exampleMultiPartFormRequest := func(mod func(w *multipart.Writer)) *http.Request {
+		var b bytes.Buffer
+		w := multipart.NewWriter(&b)
+		w.WriteField("name", "Jon Snow")
+		w.WriteField("emails[]", "jon@labstack.com")
+		if mod != nil {
+			mod(w)
+		}
+
+		fw, _ := w.CreateFormFile("upload", "my.file")
+		fw.Write([]byte(`<div>hi</div>`))
+		w.Close()
+
+		req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(b.String()))
+		req.Header.Add(echo.HeaderContentType, w.FormDataContentType())
+
+		return req
+	}
+
 	var testCases = []struct {
 		name         string
 		givenRequest *http.Request
@@ -520,6 +541,14 @@ func TestValuesFromForm(t *testing.T) {
 			whenName:     "emails[]",
 			expectValues: []string{"jon@labstack.com", "snow@labstack.com"},
 		},
+		{
+			name: "ok, POST multipart/form, multiple value",
+			givenRequest: exampleMultiPartFormRequest(func(w *multipart.Writer) {
+				w.WriteField("emails[]", "snow@labstack.com")
+			}),
+			whenName:     "emails[]",
+			expectValues: []string{"jon@labstack.com", "snow@labstack.com"},
+		},
 		{
 			name:         "ok, GET form, single value",
 			givenRequest: exampleGetFormRequest(nil),
@@ -540,16 +569,6 @@ func TestValuesFromForm(t *testing.T) {
 			whenName:     "nope",
 			expectError:  errFormExtractorValueMissing.Error(),
 		},
-		{
-			name: "nok, POST form, form parsing error",
-			givenRequest: func() *http.Request {
-				req := httptest.NewRequest(http.MethodPost, "/", nil)
-				req.Body = nil
-				return req
-			}(),
-			whenName:    "name",
-			expectError: "valuesFromForm parse form failed: missing form body",
-		},
 		{
 			name: "ok, cut values over extractorLimit",
 			givenRequest: examplePostFormRequest(func(v *url.Values) {
diff --git a/middleware/recover_test.go b/middleware/recover_test.go
index 9ac4fee..b27f3b4 100644
--- a/middleware/recover_test.go
+++ b/middleware/recover_test.go
@@ -28,6 +28,35 @@ func TestRecover(t *testing.T) {
 	assert.Contains(t, buf.String(), "PANIC RECOVER")
 }
 
+func TestRecoverErrAbortHandler(t *testing.T) {
+	e := echo.New()
+	buf := new(bytes.Buffer)
+	e.Logger.SetOutput(buf)
+	req := httptest.NewRequest(http.MethodGet, "/", nil)
+	rec := httptest.NewRecorder()
+	c := e.NewContext(req, rec)
+	h := Recover()(echo.HandlerFunc(func(c echo.Context) error {
+		panic(http.ErrAbortHandler)
+	}))
+	defer func() {
+		r := recover()
+		if r == nil {
+			assert.Fail(t, "expecting `http.ErrAbortHandler`, got `nil`")
+		} else {
+			if err, ok := r.(error); ok {
+				assert.ErrorIs(t, err, http.ErrAbortHandler)
+			} else {
+				assert.Fail(t, "not of error type")
+			}
+		}
+	}()
+
+	h(c)
+
+	assert.Equal(t, http.StatusInternalServerError, rec.Code)
+	assert.NotContains(t, buf.String(), "PANIC RECOVER")
+}
+
 func TestRecoverWithConfig_LogLevel(t *testing.T) {
 	tests := []struct {
 		logLevel  log.Lvl
diff --git a/middleware/timeout_test.go b/middleware/timeout_test.go
index aa6402b..7fb802a 100644
--- a/middleware/timeout_test.go
+++ b/middleware/timeout_test.go
@@ -74,13 +74,18 @@ func TestTimeoutErrorOutInHandler(t *testing.T) {
 	e := echo.New()
 	c := e.NewContext(req, rec)
 
+	rec.Code = 1 // we want to be sure that even 200 will not be sent
 	err := m(func(c echo.Context) error {
+		// this error must not be written to the client response. Middlewares upstream of timeout middleware must be able
+		// to handle returned error and this can be done only then handler has not yet committed (written status code)
+		// the response.
 		return echo.NewHTTPError(http.StatusTeapot, "err")
 	})(c)
 
 	assert.Error(t, err)
-	assert.Equal(t, http.StatusTeapot, rec.Code)
-	assert.Equal(t, "{\"message\":\"err\"}\n", rec.Body.String())
+	assert.EqualError(t, err, "code=418, message=err")
+	assert.Equal(t, 1, rec.Code)
+	assert.Equal(t, "", rec.Body.String())
 }
 
 func TestTimeoutSuccessfulRequest(t *testing.T) {