Repository: labstack/echo
Pull request #2550 (2023-11-27 15:50:29)

Fix Real IP logic
Hello.
This fix for realIP logic.
We should check for trusting not real IP, but RemoteIP, who sends the request.
For example, we have a client - 1.1.1.1 and LB - 8.8.8.8.
LB are trusting, all requests sended by it have X-Real-Ip header with client IP and we should extract it from headers.
We should not extract RealIP from requests sended from another hosts (not our LB).
Current implementation checking client IP for trusting, but it's incorrect.

aldas: Is cause by https://github.com/labstack/echo/issues/1834
aldas: Maybe something like that would be better

In case `X-Real-Ip` header is present we check if we can trust `Request.RemoteAddr` and If we can -we will use `X-Real-Ip` value.  Also we parse `X-Real-Ip` value just to be sure it is valid IP.


```go
// ExtractIPFromRealIPHeader extracts IP address using X-Real-Ip header only when we trust Request.RemoteAddr IP.
// Use this if you put proxy which uses this header.
func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
	checker := newIPChecker(options)
	return func(req *http.Request) string {
		directIP := extractIP(req)
		realIP := req.Header.Get(HeaderXRealIP)
		if realIP != "" {
			if dIP := net.ParseIP(directIP); dIP != nil && checker.trust(dIP) {
				realIP = strings.TrimPrefix(realIP, "[")
				realIP = strings.TrimSuffix(realIP, "]")
				if rIP := net.ParseIP(realIP); rIP != nil {
					return realIP
				}
			}
		}
		return directIP
	}
}
```
cl-bvl: Hello, thanks.
The tests are fixed. Now it's failing on master code and passes on fixed version.

Patch:
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 440f0ec..9ae5dbd 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -14,17 +14,17 @@ permissions:
 
 env:
   # run static analysis only with the latest Go version
-  LATEST_GO_VERSION: "1.21"
+  LATEST_GO_VERSION: "1.22"
 
 jobs:
   check:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout Code
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Set up Go ${{ matrix.go }}
-        uses: actions/setup-go@v4
+        uses: actions/setup-go@v5
         with:
           go-version: ${{ env.LATEST_GO_VERSION }}
           check-latest: true
diff --git a/.github/workflows/echo.yml b/.github/workflows/echo.yml
index c240dd0..cb3dc44 100644
--- a/.github/workflows/echo.yml
+++ b/.github/workflows/echo.yml
@@ -14,7 +14,7 @@ permissions:
 
 env:
   # run coverage and benchmarks only with the latest Go version
-  LATEST_GO_VERSION: "1.21"
+  LATEST_GO_VERSION: "1.22"
 
 jobs:
   test:
@@ -25,15 +25,15 @@ jobs:
         # Echo tests with last four major releases (unless there are pressing vulnerabilities)
         # As we depend on `golang.org/x/` libraries which only support last 2 Go releases we could have situations when
         # we derive from last four major releases promise.
-        go: ["1.18", "1.19", "1.20", "1.21"]
+        go: ["1.19", "1.20", "1.21", "1.22"]
     name: ${{ matrix.os }} @ Go ${{ matrix.go }}
     runs-on: ${{ matrix.os }}
     steps:
       - name: Checkout Code
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Set up Go ${{ matrix.go }}
-        uses: actions/setup-go@v4
+        uses: actions/setup-go@v5
         with:
           go-version: ${{ matrix.go }}
 
@@ -53,18 +53,18 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout Code (Previous)
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           ref: ${{ github.base_ref }}
           path: previous
 
       - name: Checkout Code (New)
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           path: new
 
       - name: Set up Go ${{ matrix.go }}
-        uses: actions/setup-go@v4
+        uses: actions/setup-go@v5
         with:
           go-version: ${{ env.LATEST_GO_VERSION }}
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8490ab2..cc17e28 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
 # Changelog
 
+## v4.11.4 - 2023-12-20
+
+**Security**
+
+* Upgrade golang.org/x/crypto to v0.17.0 to fix vulnerability [issue](https://pkg.go.dev/vuln/GO-2023-2402) [#2562](https://github.com/labstack/echo/pull/2562)
+
+**Enhancements**
+
+* Update deps and mark Go version to 1.18 as this is what golang.org/x/* use [#2563](https://github.com/labstack/echo/pull/2563)
+* Request logger: add example for Slog https://pkg.go.dev/log/slog [#2543](https://github.com/labstack/echo/pull/2543)
+
+
 ## v4.11.3 - 2023-11-07
 
 **Security**
diff --git a/Makefile b/Makefile
index 6aff6a8..f9e5afb 100644
--- a/Makefile
+++ b/Makefile
@@ -31,6 +31,6 @@ benchmark: ## Run benchmarks
 help: ## Display this help screen
 	@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
 
-goversion ?= "1.17"
-test_version: ## Run tests inside Docker with given version (defaults to 1.17 oldest supported). Example: make test_version goversion=1.17
+goversion ?= "1.19"
+test_version: ## Run tests inside Docker with given version (defaults to 1.19 oldest supported). Example: make test_version goversion=1.19
 	@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"
diff --git a/README.md b/README.md
index 18accea..351ba3c 100644
--- a/README.md
+++ b/README.md
@@ -9,20 +9,18 @@
 [![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)
 [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
 
-## Supported Go versions
+## Echo
 
-Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with
-older versions.
+High performance, extensible, minimalist Go web framework.
 
-As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules).
-Therefore a Go version capable of understanding /vN suffixed imports is required:
+* [Official website](https://echo.labstack.com)
+* [Quick start](https://echo.labstack.com/docs/quick-start)
+* [Middlewares](https://echo.labstack.com/docs/category/middleware)
 
-Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended
-way of using Echo going forward.
+Help and questions: [Github Discussions](https://github.com/labstack/echo/discussions)
 
-For older versions, please use the latest v3 tag.
 
-## Feature Overview
+### Feature Overview
 
 - Optimized HTTP router which smartly prioritize routes
 - Build robust and scalable RESTful APIs
@@ -38,6 +36,18 @@ For older versions, please use the latest v3 tag.
 - Automatic TLS via Let’s Encrypt
 - HTTP/2 support
 
+## Sponsors
+
+<div>
+  <a href="https://encore.dev" style="display: inline-flex; align-items: center; gap: 10px">
+    <img src="https://user-images.githubusercontent.com/78424526/214602214-52e0483a-b5fc-4d4c-b03e-0b7b23e012df.svg" height="28px" alt="encore icon"></img>
+  <b>Encore – the platform for building Go-based cloud backends</b>
+    </a>
+</div>
+<br/>
+
+Click [here](https://github.com/sponsors/labstack) for more information on sponsorship.
+
 ## Benchmarks
 
 Date: 2020/11/11<br>
@@ -57,6 +67,7 @@ The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
 // go get github.com/labstack/echo/{version}
 go get github.com/labstack/echo/v4
 ```
+Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with older versions.
 
 ### Example
 
@@ -117,10 +128,6 @@ of middlewares in this list.
 
 Please send a PR to add your own library here.
 
-## Help
-
-- [Forum](https://github.com/labstack/echo/discussions)
-
 ## Contribute
 
 **Use issues for everything**
diff --git a/bind.go b/bind.go
index 374a2ae..5e29be8 100644
--- a/bind.go
+++ b/bind.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
@@ -11,23 +14,21 @@ import (
 	"strings"
 )
 
-type (
-	// Binder is the interface that wraps the Bind method.
-	Binder interface {
-		Bind(i interface{}, c Context) error
-	}
+// Binder is the interface that wraps the Bind method.
+type Binder interface {
+	Bind(i interface{}, c Context) error
+}
 
-	// DefaultBinder is the default implementation of the Binder interface.
-	DefaultBinder struct{}
+// DefaultBinder is the default implementation of the Binder interface.
+type DefaultBinder struct{}
 
-	// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
-	// Types that don't implement this, but do implement encoding.TextUnmarshaler
-	// will use that interface instead.
-	BindUnmarshaler interface {
-		// UnmarshalParam decodes and assigns a value from an form or query param.
-		UnmarshalParam(param string) error
-	}
-)
+// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
+// Types that don't implement this, but do implement encoding.TextUnmarshaler
+// will use that interface instead.
+type BindUnmarshaler interface {
+	// UnmarshalParam decodes and assigns a value from an form or query param.
+	UnmarshalParam(param string) error
+}
 
 // BindPathParams binds path params to bindable object
 func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
@@ -131,10 +132,29 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
 	typ := reflect.TypeOf(destination).Elem()
 	val := reflect.ValueOf(destination).Elem()
 
-	// Map
-	if typ.Kind() == reflect.Map {
+	// Support binding to limited Map destinations:
+	// - map[string][]string,
+	// - map[string]string <-- (binds first value from data slice)
+	// - map[string]interface{}
+	// You are better off binding to struct but there are user who want this map feature. Source of data for these cases are:
+	// params,query,header,form as these sources produce string values, most of the time slice of strings, actually.
+	if typ.Kind() == reflect.Map && typ.Key().Kind() == reflect.String {
+		k := typ.Elem().Kind()
+		isElemInterface := k == reflect.Interface
+		isElemString := k == reflect.String
+		isElemSliceOfStrings := k == reflect.Slice && typ.Elem().Elem().Kind() == reflect.String
+		if !(isElemSliceOfStrings || isElemString || isElemInterface) {
+			return nil
+		}
+		if val.IsNil() {
+			val.Set(reflect.MakeMap(typ))
+		}
 		for k, v := range data {
-			val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
+			if isElemString {
+				val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
+			} else {
+				val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
+			}
 		}
 		return nil
 	}
diff --git a/binder.go b/binder.go
index 8e7b814..ebabeaf 100644
--- a/binder.go
+++ b/binder.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/context.go b/context.go
index 6a18116..a5177e8 100644
--- a/context.go
+++ b/context.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
@@ -13,204 +16,202 @@ import (
 	"sync"
 )
 
-type (
-	// Context represents the context of the current HTTP request. It holds request and
-	// response objects, path, path parameters, data and registered handler.
-	Context interface {
-		// Request returns `*http.Request`.
-		Request() *http.Request
+// Context represents the context of the current HTTP request. It holds request and
+// response objects, path, path parameters, data and registered handler.
+type Context interface {
+	// Request returns `*http.Request`.
+	Request() *http.Request
 
-		// SetRequest sets `*http.Request`.
-		SetRequest(r *http.Request)
+	// SetRequest sets `*http.Request`.
+	SetRequest(r *http.Request)
 
-		// SetResponse sets `*Response`.
-		SetResponse(r *Response)
+	// SetResponse sets `*Response`.
+	SetResponse(r *Response)
 
-		// Response returns `*Response`.
-		Response() *Response
+	// Response returns `*Response`.
+	Response() *Response
 
-		// IsTLS returns true if HTTP connection is TLS otherwise false.
-		IsTLS() bool
+	// IsTLS returns true if HTTP connection is TLS otherwise false.
+	IsTLS() bool
 
-		// IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
-		IsWebSocket() bool
+	// IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
+	IsWebSocket() bool
 
-		// Scheme returns the HTTP protocol scheme, `http` or `https`.
-		Scheme() string
+	// Scheme returns the HTTP protocol scheme, `http` or `https`.
+	Scheme() string
 
-		// RealIP returns the client's network address based on `X-Forwarded-For`
-		// or `X-Real-IP` request header.
-		// The behavior can be configured using `Echo#IPExtractor`.
-		RealIP() string
+	// RealIP returns the client's network address based on `X-Forwarded-For`
+	// or `X-Real-IP` request header.
+	// The behavior can be configured using `Echo#IPExtractor`.
+	RealIP() string
 
-		// Path returns the registered path for the handler.
-		Path() string
+	// Path returns the registered path for the handler.
+	Path() string
 
-		// SetPath sets the registered path for the handler.
-		SetPath(p string)
+	// SetPath sets the registered path for the handler.
+	SetPath(p string)
 
-		// Param returns path parameter by name.
-		Param(name string) string
+	// Param returns path parameter by name.
+	Param(name string) string
 
-		// ParamNames returns path parameter names.
-		ParamNames() []string
+	// ParamNames returns path parameter names.
+	ParamNames() []string
 
-		// SetParamNames sets path parameter names.
-		SetParamNames(names ...string)
+	// SetParamNames sets path parameter names.
+	SetParamNames(names ...string)
 
-		// ParamValues returns path parameter values.
-		ParamValues() []string
+	// ParamValues returns path parameter values.
+	ParamValues() []string
 
-		// SetParamValues sets path parameter values.
-		SetParamValues(values ...string)
+	// SetParamValues sets path parameter values.
+	SetParamValues(values ...string)
 
-		// QueryParam returns the query param for the provided name.
-		QueryParam(name string) string
+	// QueryParam returns the query param for the provided name.
+	QueryParam(name string) string
 
-		// QueryParams returns the query parameters as `url.Values`.
-		QueryParams() url.Values
+	// QueryParams returns the query parameters as `url.Values`.
+	QueryParams() url.Values
 
-		// QueryString returns the URL query string.
-		QueryString() string
+	// QueryString returns the URL query string.
+	QueryString() string
 
-		// FormValue returns the form field value for the provided name.
-		FormValue(name string) string
+	// FormValue returns the form field value for the provided name.
+	FormValue(name string) string
 
-		// FormParams returns the form parameters as `url.Values`.
-		FormParams() (url.Values, error)
+	// FormParams returns the form parameters as `url.Values`.
+	FormParams() (url.Values, error)
 
-		// FormFile returns the multipart form file for the provided name.
-		FormFile(name string) (*multipart.FileHeader, error)
+	// FormFile returns the multipart form file for the provided name.
+	FormFile(name string) (*multipart.FileHeader, error)
 
-		// MultipartForm returns the multipart form.
-		MultipartForm() (*multipart.Form, error)
+	// MultipartForm returns the multipart form.
+	MultipartForm() (*multipart.Form, error)
 
-		// Cookie returns the named cookie provided in the request.
-		Cookie(name string) (*http.Cookie, error)
+	// Cookie returns the named cookie provided in the request.
+	Cookie(name string) (*http.Cookie, error)
 
-		// SetCookie adds a `Set-Cookie` header in HTTP response.
-		SetCookie(cookie *http.Cookie)
+	// SetCookie adds a `Set-Cookie` header in HTTP response.
+	SetCookie(cookie *http.Cookie)
 
-		// Cookies returns the HTTP cookies sent with the request.
-		Cookies() []*http.Cookie
+	// Cookies returns the HTTP cookies sent with the request.
+	Cookies() []*http.Cookie
 
-		// Get retrieves data from the context.
-		Get(key string) interface{}
+	// Get retrieves data from the context.
+	Get(key string) interface{}
 
-		// Set saves data in the context.
-		Set(key string, val interface{})
+	// Set saves data in the context.
+	Set(key string, val interface{})
 
-		// Bind binds path params, query params and the request body into provided type `i`. The default binder
-		// binds body based on Content-Type header.
-		Bind(i interface{}) error
+	// Bind binds path params, query params and the request body into provided type `i`. The default binder
+	// binds body based on Content-Type header.
+	Bind(i interface{}) error
 
-		// Validate validates provided `i`. It is usually called after `Context#Bind()`.
-		// Validator must be registered using `Echo#Validator`.
-		Validate(i interface{}) error
+	// Validate validates provided `i`. It is usually called after `Context#Bind()`.
+	// Validator must be registered using `Echo#Validator`.
+	Validate(i interface{}) error
 
-		// Render renders a template with data and sends a text/html response with status
-		// code. Renderer must be registered using `Echo.Renderer`.
-		Render(code int, name string, data interface{}) error
+	// Render renders a template with data and sends a text/html response with status
+	// code. Renderer must be registered using `Echo.Renderer`.
+	Render(code int, name string, data interface{}) error
 
-		// HTML sends an HTTP response with status code.
-		HTML(code int, html string) error
+	// HTML sends an HTTP response with status code.
+	HTML(code int, html string) error
 
-		// HTMLBlob sends an HTTP blob response with status code.
-		HTMLBlob(code int, b []byte) error
+	// HTMLBlob sends an HTTP blob response with status code.
+	HTMLBlob(code int, b []byte) error
 
-		// String sends a string response with status code.
-		String(code int, s string) error
+	// String sends a string response with status code.
+	String(code int, s string) error
 
-		// JSON sends a JSON response with status code.
-		JSON(code int, i interface{}) error
+	// JSON sends a JSON response with status code.
+	JSON(code int, i interface{}) error
 
-		// JSONPretty sends a pretty-print JSON with status code.
-		JSONPretty(code int, i interface{}, indent string) error
+	// JSONPretty sends a pretty-print JSON with status code.
+	JSONPretty(code int, i interface{}, indent string) error
 
-		// JSONBlob sends a JSON blob response with status code.
-		JSONBlob(code int, b []byte) error
+	// JSONBlob sends a JSON blob response with status code.
+	JSONBlob(code int, b []byte) error
 
-		// JSONP sends a JSONP response with status code. It uses `callback` to construct
-		// the JSONP payload.
-		JSONP(code int, callback string, i interface{}) error
+	// JSONP sends a JSONP response with status code. It uses `callback` to construct
+	// the JSONP payload.
+	JSONP(code int, callback string, i interface{}) error
 
-		// JSONPBlob sends a JSONP blob response with status code. It uses `callback`
-		// to construct the JSONP payload.
-		JSONPBlob(code int, callback string, b []byte) error
+	// JSONPBlob sends a JSONP blob response with status code. It uses `callback`
+	// to construct the JSONP payload.
+	JSONPBlob(code int, callback string, b []byte) error
 
-		// XML sends an XML response with status code.
-		XML(code int, i interface{}) error
+	// XML sends an XML response with status code.
+	XML(code int, i interface{}) error
 
-		// XMLPretty sends a pretty-print XML with status code.
-		XMLPretty(code int, i interface{}, indent string) error
+	// XMLPretty sends a pretty-print XML with status code.
+	XMLPretty(code int, i interface{}, indent string) error
 
-		// XMLBlob sends an XML blob response with status code.
-		XMLBlob(code int, b []byte) error
+	// XMLBlob sends an XML blob response with status code.
+	XMLBlob(code int, b []byte) error
 
-		// Blob sends a blob response with status code and content type.
-		Blob(code int, contentType string, b []byte) error
+	// Blob sends a blob response with status code and content type.
+	Blob(code int, contentType string, b []byte) error
 
-		// Stream sends a streaming response with status code and content type.
-		Stream(code int, contentType string, r io.Reader) error
+	// Stream sends a streaming response with status code and content type.
+	Stream(code int, contentType string, r io.Reader) error
 
-		// File sends a response with the content of the file.
-		File(file string) error
+	// File sends a response with the content of the file.
+	File(file string) error
 
-		// Attachment sends a response as attachment, prompting client to save the
-		// file.
-		Attachment(file string, name string) error
+	// Attachment sends a response as attachment, prompting client to save the
+	// file.
+	Attachment(file string, name string) error
 
-		// Inline sends a response as inline, opening the file in the browser.
-		Inline(file string, name string) error
+	// Inline sends a response as inline, opening the file in the browser.
+	Inline(file string, name string) error
 
-		// NoContent sends a response with no body and a status code.
-		NoContent(code int) error
+	// NoContent sends a response with no body and a status code.
+	NoContent(code int) error
 
-		// Redirect redirects the request to a provided URL with status code.
-		Redirect(code int, url string) error
+	// Redirect redirects the request to a provided URL with status code.
+	Redirect(code int, url string) error
 
-		// Error invokes the registered global HTTP error handler. Generally used by middleware.
-		// A side-effect of calling global error handler is that now Response has been committed (sent to the client) and
-		// middlewares up in chain can not change Response status code or Response body anymore.
-		//
-		// Avoid using this method in handlers as no middleware will be able to effectively handle errors after that.
-		Error(err error)
+	// Error invokes the registered global HTTP error handler. Generally used by middleware.
+	// A side-effect of calling global error handler is that now Response has been committed (sent to the client) and
+	// middlewares up in chain can not change Response status code or Response body anymore.
+	//
+	// Avoid using this method in handlers as no middleware will be able to effectively handle errors after that.
+	Error(err error)
 
-		// Handler returns the matched handler by router.
-		Handler() HandlerFunc
+	// Handler returns the matched handler by router.
+	Handler() HandlerFunc
 
-		// SetHandler sets the matched handler by router.
-		SetHandler(h HandlerFunc)
+	// SetHandler sets the matched handler by router.
+	SetHandler(h HandlerFunc)
 
-		// Logger returns the `Logger` instance.
-		Logger() Logger
+	// Logger returns the `Logger` instance.
+	Logger() Logger
 
-		// SetLogger Set the logger
-		SetLogger(l Logger)
+	// SetLogger Set the logger
+	SetLogger(l Logger)
 
-		// Echo returns the `Echo` instance.
-		Echo() *Echo
+	// Echo returns the `Echo` instance.
+	Echo() *Echo
 
-		// Reset resets the context after request completes. It must be called along
-		// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
-		// See `Echo#ServeHTTP()`
-		Reset(r *http.Request, w http.ResponseWriter)
-	}
+	// Reset resets the context after request completes. It must be called along
+	// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
+	// See `Echo#ServeHTTP()`
+	Reset(r *http.Request, w http.ResponseWriter)
+}
 
-	context struct {
-		request  *http.Request
-		response *Response
-		path     string
-		pnames   []string
-		pvalues  []string
-		query    url.Values
-		handler  HandlerFunc
-		store    Map
-		echo     *Echo
-		logger   Logger
-		lock     sync.RWMutex
-	}
-)
+type context struct {
+	request  *http.Request
+	response *Response
+	path     string
+	pnames   []string
+	pvalues  []string
+	query    url.Values
+	handler  HandlerFunc
+	store    Map
+	echo     *Echo
+	logger   Logger
+	lock     sync.RWMutex
+}
 
 const (
 	// ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain.
@@ -335,7 +336,7 @@ func (c *context) SetParamNames(names ...string) {
 
 	if len(c.pvalues) < l {
 		// Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them,
-		// probably those values will be overriden in a Context#SetParamValues
+		// probably those values will be overridden in a Context#SetParamValues
 		newPvalues := make([]string, l)
 		copy(newPvalues, c.pvalues)
 		c.pvalues = newPvalues
@@ -489,7 +490,7 @@ func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error
 }
 
 func (c *context) json(code int, i interface{}, indent string) error {
-	c.writeContentType(MIMEApplicationJSONCharsetUTF8)
+	c.writeContentType(MIMEApplicationJSON)
 	c.response.Status = code
 	return c.echo.JSONSerializer.Serialize(c, i, indent)
 }
@@ -507,7 +508,7 @@ func (c *context) JSONPretty(code int, i interface{}, indent string) (err error)
 }
 
 func (c *context) JSONBlob(code int, b []byte) (err error) {
-	return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b)
+	return c.Blob(code, MIMEApplicationJSON, b)
 }
 
 func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
diff --git a/context_fs.go b/context_fs.go
index 1038f89..1c25baf 100644
--- a/context_fs.go
+++ b/context_fs.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/echo.go b/echo.go
index 0ac6449..6e4ed9a 100644
--- a/echo.go
+++ b/echo.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 /*
 Package echo implements high performance, minimalist Go web framework.
 
@@ -60,97 +63,95 @@ import (
 	"golang.org/x/net/http2/h2c"
 )
 
-type (
-	// Echo is the top-level framework instance.
-	//
-	// Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these
-	// fields from handlers/middlewares and changing field values at the same time leads to data-races.
-	// Adding new routes after the server has been started is also not safe!
-	Echo struct {
-		filesystem
-		common
-		// startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
-		// listener address info (on which interface/port was listener binded) without having data races.
-		startupMutex sync.RWMutex
-		colorer      *color.Color
-
-		// premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns
-		// an error the router is not executed and the request will end up in the global error handler.
-		premiddleware []MiddlewareFunc
-		middleware    []MiddlewareFunc
-		maxParam      *int
-		router        *Router
-		routers       map[string]*Router
-		pool          sync.Pool
-
-		StdLogger        *stdLog.Logger
-		Server           *http.Server
-		TLSServer        *http.Server
-		Listener         net.Listener
-		TLSListener      net.Listener
-		AutoTLSManager   autocert.Manager
-		DisableHTTP2     bool
-		Debug            bool
-		HideBanner       bool
-		HidePort         bool
-		HTTPErrorHandler HTTPErrorHandler
-		Binder           Binder
-		JSONSerializer   JSONSerializer
-		Validator        Validator
-		Renderer         Renderer
-		Logger           Logger
-		IPExtractor      IPExtractor
-		ListenerNetwork  string
-
-		// OnAddRouteHandler is called when Echo adds new route to specific host router.
-		OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc)
-	}
-
-	// Route contains a handler and information for matching against requests.
-	Route struct {
-		Method string `json:"method"`
-		Path   string `json:"path"`
-		Name   string `json:"name"`
-	}
-
-	// HTTPError represents an error that occurred while handling a request.
-	HTTPError struct {
-		Code     int         `json:"-"`
-		Message  interface{} `json:"message"`
-		Internal error       `json:"-"` // Stores the error returned by an external dependency
-	}
-
-	// MiddlewareFunc defines a function to process middleware.
-	MiddlewareFunc func(next HandlerFunc) HandlerFunc
-
-	// HandlerFunc defines a function to serve HTTP requests.
-	HandlerFunc func(c Context) error
-
-	// HTTPErrorHandler is a centralized HTTP error handler.
-	HTTPErrorHandler func(err error, c Context)
-
-	// Validator is the interface that wraps the Validate function.
-	Validator interface {
-		Validate(i interface{}) error
-	}
-
-	// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
-	JSONSerializer interface {
-		Serialize(c Context, i interface{}, indent string) error
-		Deserialize(c Context, i interface{}) error
-	}
-
-	// Renderer is the interface that wraps the Render function.
-	Renderer interface {
-		Render(io.Writer, string, interface{}, Context) error
-	}
-
-	// Map defines a generic map of type `map[string]interface{}`.
-	Map map[string]interface{}
-
-	// Common struct for Echo & Group.
-	common struct{}
-)
+// Echo is the top-level framework instance.
+//
+// Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these
+// fields from handlers/middlewares and changing field values at the same time leads to data-races.
+// Adding new routes after the server has been started is also not safe!
+type Echo struct {
+	filesystem
+	common
+	// startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
+	// listener address info (on which interface/port was listener bound) without having data races.
+	startupMutex sync.RWMutex
+	colorer      *color.Color
+
+	// premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns
+	// an error the router is not executed and the request will end up in the global error handler.
+	premiddleware []MiddlewareFunc
+	middleware    []MiddlewareFunc
+	maxParam      *int
+	router        *Router
+	routers       map[string]*Router
+	pool          sync.Pool
+
+	StdLogger        *stdLog.Logger
+	Server           *http.Server
+	TLSServer        *http.Server
+	Listener         net.Listener
+	TLSListener      net.Listener
+	AutoTLSManager   autocert.Manager
+	DisableHTTP2     bool
+	Debug            bool
+	HideBanner       bool
+	HidePort         bool
+	HTTPErrorHandler HTTPErrorHandler
+	Binder           Binder
+	JSONSerializer   JSONSerializer
+	Validator        Validator
+	Renderer         Renderer
+	Logger           Logger
+	IPExtractor      IPExtractor
+	ListenerNetwork  string
+
+	// OnAddRouteHandler is called when Echo adds new route to specific host router.
+	OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc)
+}
+
+// Route contains a handler and information for matching against requests.
+type Route struct {
+	Method string `json:"method"`
+	Path   string `json:"path"`
+	Name   string `json:"name"`
+}
+
+// HTTPError represents an error that occurred while handling a request.
+type HTTPError struct {
+	Code     int         `json:"-"`
+	Message  interface{} `json:"message"`
+	Internal error       `json:"-"` // Stores the error returned by an external dependency
+}
+
+// MiddlewareFunc defines a function to process middleware.
+type MiddlewareFunc func(next HandlerFunc) HandlerFunc
+
+// HandlerFunc defines a function to serve HTTP requests.
+type HandlerFunc func(c Context) error
+
+// HTTPErrorHandler is a centralized HTTP error handler.
+type HTTPErrorHandler func(err error, c Context)
+
+// Validator is the interface that wraps the Validate function.
+type Validator interface {
+	Validate(i interface{}) error
+}
+
+// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
+type JSONSerializer interface {
+	Serialize(c Context, i interface{}, indent string) error
+	Deserialize(c Context, i interface{}) error
+}
+
+// Renderer is the interface that wraps the Render function.
+type Renderer interface {
+	Render(io.Writer, string, interface{}, Context) error
+}
+
+// Map defines a generic map of type `map[string]interface{}`.
+type Map map[string]interface{}
+
+// Common struct for Echo & Group.
+type common struct{}
 
 // HTTP methods
 // NOTE: Deprecated, please use the stdlib constants directly instead.
@@ -169,7 +170,12 @@ const (
 
 // MIME types
 const (
-	MIMEApplicationJSON                  = "application/json"
+	// MIMEApplicationJSON JavaScript Object Notation (JSON) https://www.rfc-editor.org/rfc/rfc8259
+	MIMEApplicationJSON = "application/json"
+	// Deprecated: Please use MIMEApplicationJSON instead. JSON should be encoded using UTF-8 by default.
+	// No "charset" parameter is defined for this registration.
+	// Adding one really has no effect on compliant recipients.
+	// See RFC 8259, section 8.1. https://datatracker.ietf.org/doc/html/rfc8259#section-8.1
 	MIMEApplicationJSONCharsetUTF8       = MIMEApplicationJSON + "; " + charsetUTF8
 	MIMEApplicationJavaScript            = "application/javascript"
 	MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
@@ -259,7 +265,7 @@ const (
 
 const (
 	// Version of Echo
-	Version = "4.11.3"
+	Version = "4.11.4"
 	website = "https://echo.labstack.com"
 	// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
 	banner = `
@@ -274,21 +280,19 @@ ____________________________________O/_______
 `
 )
 
-var (
-	methods = [...]string{
-		http.MethodConnect,
-		http.MethodDelete,
-		http.MethodGet,
-		http.MethodHead,
-		http.MethodOptions,
-		http.MethodPatch,
-		http.MethodPost,
-		PROPFIND,
-		http.MethodPut,
-		http.MethodTrace,
-		REPORT,
-	}
-)
+var methods = [...]string{
+	http.MethodConnect,
+	http.MethodDelete,
+	http.MethodGet,
+	http.MethodHead,
+	http.MethodOptions,
+	http.MethodPatch,
+	http.MethodPost,
+	PROPFIND,
+	http.MethodPut,
+	http.MethodTrace,
+	REPORT,
+}
 
 // Errors
 var (
@@ -341,22 +345,23 @@ var (
 	ErrInvalidListenerNetwork = errors.New("invalid listener network")
 )
 
-// Error handlers
-var (
-	NotFoundHandler = func(c Context) error {
-		return ErrNotFound
-	}
+// NotFoundHandler is the handler that router uses in case there was no matching route found. Returns an error that results
+// HTTP 404 status code.
+var NotFoundHandler = func(c Context) error {
+	return ErrNotFound
+}
 
-	MethodNotAllowedHandler = func(c Context) error {
-		// See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed)
-		// response and MAY do so in any other response. For disabled resources an empty Allow header may be returned
-		routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string)
-		if ok && routerAllowMethods != "" {
-			c.Response().Header().Set(HeaderAllow, routerAllowMethods)
-		}
-		return ErrMethodNotAllowed
+// MethodNotAllowedHandler is the handler thar router uses in case there was no matching route found but there was
+// another matching routes for that requested URL. Returns an error that results HTTP 405 Method Not Allowed status code.
+var MethodNotAllowedHandler = func(c Context) error {
+	// See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed)
+	// response and MAY do so in any other response. For disabled resources an empty Allow header may be returned
+	routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string)
+	if ok && routerAllowMethods != "" {
+		c.Response().Header().Set(HeaderAllow, routerAllowMethods)
 	}
-)
+	return ErrMethodNotAllowed
+}
 
 // New creates an instance of Echo.
 func New() (e *Echo) {
@@ -414,7 +419,7 @@ func (e *Echo) Routers() map[string]*Router {
 //
 // NOTE: In case errors happens in middleware call-chain that is returning from handler (which did not return an error).
 // When handler has already sent response (ala c.JSON()) and there is error in middleware that is returning from
-// handler. Then the error that global error handler received will be ignored because we have already "commited" the
+// handler. Then the error that global error handler received will be ignored because we have already "committed" the
 // response and status code header has been sent to the client.
 func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
 
diff --git a/echo_fs.go b/echo_fs.go
index 9f83a03..a7b231f 100644
--- a/echo_fs.go
+++ b/echo_fs.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/go.mod b/go.mod
index 367dcb8..89a0e86 100644
--- a/go.mod
+++ b/go.mod
@@ -1,24 +1,24 @@
 module github.com/labstack/echo/v4
 
-go 1.17
+go 1.18
 
 require (
 	github.com/golang-jwt/jwt v3.2.2+incompatible
-	github.com/labstack/gommon v0.4.0
+	github.com/labstack/gommon v0.4.2
 	github.com/stretchr/testify v1.8.4
 	github.com/valyala/fasttemplate v1.2.2
-	golang.org/x/crypto v0.14.0
-	golang.org/x/net v0.17.0
-	golang.org/x/time v0.3.0
+	golang.org/x/crypto v0.21.0
+	golang.org/x/net v0.22.0
+	golang.org/x/time v0.5.0
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
-	github.com/mattn/go-isatty v0.0.19 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
-	golang.org/x/sys v0.13.0 // indirect
-	golang.org/x/text v0.13.0 // indirect
+	golang.org/x/sys v0.18.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 5b8ba6b..397a22d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,85 +1,35 @@
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
-github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
-github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
-github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
+github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
-github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 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=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
 github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
+golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
-golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
-golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/group.go b/group.go
index 749a5ca..eca25c9 100644
--- a/group.go
+++ b/group.go
@@ -1,21 +1,22 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
 	"net/http"
 )
 
-type (
-	// Group is a set of sub-routes for a specified route. It can be used for inner
-	// routes that share a common middleware or functionality that should be separate
-	// from the parent echo instance while still inheriting from it.
-	Group struct {
-		common
-		host       string
-		prefix     string
-		middleware []MiddlewareFunc
-		echo       *Echo
-	}
-)
+// Group is a set of sub-routes for a specified route. It can be used for inner
+// routes that share a common middleware or functionality that should be separate
+// from the parent echo instance while still inheriting from it.
+type Group struct {
+	common
+	host       string
+	prefix     string
+	middleware []MiddlewareFunc
+	echo       *Echo
+}
 
 // Use implements `Echo#Use()` for sub-routes within the Group.
 func (g *Group) Use(middleware ...MiddlewareFunc) {
diff --git a/group_fs.go b/group_fs.go
index aedc4c6..c1b7ec2 100644
--- a/group_fs.go
+++ b/group_fs.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/ip.go b/ip.go
index 1bcd756..6aed8d6 100644
--- a/ip.go
+++ b/ip.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
@@ -64,7 +67,7 @@ XFF:  "x"                   "x, a"                  "x, a, b"
 ```
 
 In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is
-configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructre".
+configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructure".
 In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`.
 
 In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`.
@@ -225,15 +228,21 @@ func extractIP(req *http.Request) string {
 func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
 	checker := newIPChecker(options)
 	return func(req *http.Request) string {
+		directIP := extractIP(req)
 		realIP := req.Header.Get(HeaderXRealIP)
-		if realIP != "" {
+		if realIP == "" {
+			return directIP
+		}
+
+		if checker.trust(net.ParseIP(directIP)) {
 			realIP = strings.TrimPrefix(realIP, "[")
 			realIP = strings.TrimSuffix(realIP, "]")
-			if ip := net.ParseIP(realIP); ip != nil && checker.trust(ip) {
+			if rIP := net.ParseIP(realIP); rIP != nil {
 				return realIP
 			}
 		}
-		return extractIP(req)
+
+		return directIP
 	}
 }
 
diff --git a/json.go b/json.go
index 16b2d05..6da0aaf 100644
--- a/json.go
+++ b/json.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/log.go b/log.go
index 3f8de59..0acd9ff 100644
--- a/log.go
+++ b/log.go
@@ -1,41 +1,41 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
-	"io"
-
 	"github.com/labstack/gommon/log"
+	"io"
 )
 
-type (
-	// Logger defines the logging interface.
-	Logger interface {
-		Output() io.Writer
-		SetOutput(w io.Writer)
-		Prefix() string
-		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)
-		Debug(i ...interface{})
-		Debugf(format string, args ...interface{})
-		Debugj(j log.JSON)
-		Info(i ...interface{})
-		Infof(format string, args ...interface{})
-		Infoj(j log.JSON)
-		Warn(i ...interface{})
-		Warnf(format string, args ...interface{})
-		Warnj(j log.JSON)
-		Error(i ...interface{})
-		Errorf(format string, args ...interface{})
-		Errorj(j log.JSON)
-		Fatal(i ...interface{})
-		Fatalj(j log.JSON)
-		Fatalf(format string, args ...interface{})
-		Panic(i ...interface{})
-		Panicj(j log.JSON)
-		Panicf(format string, args ...interface{})
-	}
-)
+// Logger defines the logging interface.
+type Logger interface {
+	Output() io.Writer
+	SetOutput(w io.Writer)
+	Prefix() string
+	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)
+	Debug(i ...interface{})
+	Debugf(format string, args ...interface{})
+	Debugj(j log.JSON)
+	Info(i ...interface{})
+	Infof(format string, args ...interface{})
+	Infoj(j log.JSON)
+	Warn(i ...interface{})
+	Warnf(format string, args ...interface{})
+	Warnj(j log.JSON)
+	Error(i ...interface{})
+	Errorf(format string, args ...interface{})
+	Errorj(j log.JSON)
+	Fatal(i ...interface{})
+	Fatalj(j log.JSON)
+	Fatalf(format string, args ...interface{})
+	Panic(i ...interface{})
+	Panicj(j log.JSON)
+	Panicf(format string, args ...interface{})
+}
diff --git a/middleware/basic_auth.go b/middleware/basic_auth.go
index f9e8caa..9285f29 100644
--- a/middleware/basic_auth.go
+++ b/middleware/basic_auth.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -9,37 +12,35 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// BasicAuthConfig defines the config for BasicAuth middleware.
-	BasicAuthConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
+// BasicAuthConfig defines the config for BasicAuth middleware.
+type BasicAuthConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
 
-		// Validator is a function to validate BasicAuth credentials.
-		// Required.
-		Validator BasicAuthValidator
+	// Validator is a function to validate BasicAuth credentials.
+	// Required.
+	Validator BasicAuthValidator
 
-		// Realm is a string to define realm attribute of BasicAuth.
-		// Default value "Restricted".
-		Realm string
-	}
+	// Realm is a string to define realm attribute of BasicAuth.
+	// Default value "Restricted".
+	Realm string
+}
 
-	// BasicAuthValidator defines a function to validate BasicAuth credentials.
-	BasicAuthValidator func(string, string, echo.Context) (bool, error)
-)
+// BasicAuthValidator defines a function to validate BasicAuth credentials.
+// The function should return a boolean indicating whether the credentials are valid,
+// and an error if any error occurs during the validation process.
+type BasicAuthValidator func(string, string, echo.Context) (bool, error)
 
 const (
 	basic        = "basic"
 	defaultRealm = "Restricted"
 )
 
-var (
-	// DefaultBasicAuthConfig is the default BasicAuth middleware config.
-	DefaultBasicAuthConfig = BasicAuthConfig{
-		Skipper: DefaultSkipper,
-		Realm:   defaultRealm,
-	}
-)
+// DefaultBasicAuthConfig is the default BasicAuth middleware config.
+var DefaultBasicAuthConfig = BasicAuthConfig{
+	Skipper: DefaultSkipper,
+	Realm:   defaultRealm,
+}
 
 // BasicAuth returns an BasicAuth middleware.
 //
diff --git a/middleware/body_dump.go b/middleware/body_dump.go
index fa7891b..b06f762 100644
--- a/middleware/body_dump.go
+++ b/middleware/body_dump.go
@@ -1,8 +1,12 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
 	"bufio"
 	"bytes"
+	"errors"
 	"io"
 	"net"
 	"net/http"
@@ -10,32 +14,28 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// BodyDumpConfig defines the config for BodyDump middleware.
-	BodyDumpConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
+// BodyDumpConfig defines the config for BodyDump middleware.
+type BodyDumpConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
 
-		// Handler receives request and response payload.
-		// Required.
-		Handler BodyDumpHandler
-	}
+	// Handler receives request and response payload.
+	// Required.
+	Handler BodyDumpHandler
+}
 
-	// BodyDumpHandler receives the request and response payload.
-	BodyDumpHandler func(echo.Context, []byte, []byte)
+// BodyDumpHandler receives the request and response payload.
+type BodyDumpHandler func(echo.Context, []byte, []byte)
 
-	bodyDumpResponseWriter struct {
-		io.Writer
-		http.ResponseWriter
-	}
-)
+type bodyDumpResponseWriter struct {
+	io.Writer
+	http.ResponseWriter
+}
 
-var (
-	// DefaultBodyDumpConfig is the default BodyDump middleware config.
-	DefaultBodyDumpConfig = BodyDumpConfig{
-		Skipper: DefaultSkipper,
-	}
-)
+// DefaultBodyDumpConfig is the default BodyDump middleware config.
+var DefaultBodyDumpConfig = BodyDumpConfig{
+	Skipper: DefaultSkipper,
+}
 
 // BodyDump returns a BodyDump middleware.
 //
@@ -98,9 +98,16 @@ func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
 }
 
 func (w *bodyDumpResponseWriter) Flush() {
-	w.ResponseWriter.(http.Flusher).Flush()
+	err := responseControllerFlush(w.ResponseWriter)
+	if err != nil && errors.Is(err, http.ErrNotSupported) {
+		panic(errors.New("response writer flushing is not supported"))
+	}
 }
 
 func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
-	return w.ResponseWriter.(http.Hijacker).Hijack()
+	return responseControllerHijack(w.ResponseWriter)
+}
+
+func (w *bodyDumpResponseWriter) Unwrap() http.ResponseWriter {
+	return w.ResponseWriter
 }
diff --git a/middleware/body_limit.go b/middleware/body_limit.go
index 99e3ac5..7d3c665 100644
--- a/middleware/body_limit.go
+++ b/middleware/body_limit.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -9,31 +12,27 @@ import (
 	"github.com/labstack/gommon/bytes"
 )
 
-type (
-	// BodyLimitConfig defines the config for BodyLimit middleware.
-	BodyLimitConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
+// BodyLimitConfig defines the config for BodyLimit middleware.
+type BodyLimitConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
 
-		// Maximum allowed size for a request body, it can be specified
-		// as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
-		Limit string `yaml:"limit"`
-		limit int64
-	}
+	// Maximum allowed size for a request body, it can be specified
+	// as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
+	Limit string `yaml:"limit"`
+	limit int64
+}
 
-	limitedReader struct {
-		BodyLimitConfig
-		reader io.ReadCloser
-		read   int64
-	}
-)
+type limitedReader struct {
+	BodyLimitConfig
+	reader io.ReadCloser
+	read   int64
+}
 
-var (
-	// DefaultBodyLimitConfig is the default BodyLimit middleware config.
-	DefaultBodyLimitConfig = BodyLimitConfig{
-		Skipper: DefaultSkipper,
-	}
-)
+// DefaultBodyLimitConfig is the default BodyLimit middleware config.
+var DefaultBodyLimitConfig = BodyLimitConfig{
+	Skipper: DefaultSkipper,
+}
 
 // BodyLimit returns a BodyLimit middleware.
 //
diff --git a/middleware/compress.go b/middleware/compress.go
index 3e9bd32..557bdc8 100644
--- a/middleware/compress.go
+++ b/middleware/compress.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -13,54 +16,50 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// GzipConfig defines the config for Gzip middleware.
-	GzipConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// Gzip compression level.
-		// Optional. Default value -1.
-		Level int `yaml:"level"`
-
-		// Length threshold before gzip compression is applied.
-		// Optional. Default value 0.
-		//
-		// Most of the time you will not need to change the default. Compressing
-		// a short response might increase the transmitted data because of the
-		// gzip format overhead. Compressing the response will also consume CPU
-		// and time on the server and the client (for decompressing). Depending on
-		// your use case such a threshold might be useful.
-		//
-		// See also:
-		// https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits
-		MinLength int
-	}
+// GzipConfig defines the config for Gzip middleware.
+type GzipConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
+
+	// Gzip compression level.
+	// Optional. Default value -1.
+	Level int `yaml:"level"`
+
+	// Length threshold before gzip compression is applied.
+	// Optional. Default value 0.
+	//
+	// Most of the time you will not need to change the default. Compressing
+	// a short response might increase the transmitted data because of the
+	// gzip format overhead. Compressing the response will also consume CPU
+	// and time on the server and the client (for decompressing). Depending on
+	// your use case such a threshold might be useful.
+	//
+	// See also:
+	// https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits
+	MinLength int
+}
 
-	gzipResponseWriter struct {
-		io.Writer
-		http.ResponseWriter
-		wroteHeader       bool
-		wroteBody         bool
-		minLength         int
-		minLengthExceeded bool
-		buffer            *bytes.Buffer
-		code              int
-	}
-)
+type gzipResponseWriter struct {
+	io.Writer
+	http.ResponseWriter
+	wroteHeader       bool
+	wroteBody         bool
+	minLength         int
+	minLengthExceeded bool
+	buffer            *bytes.Buffer
+	code              int
+}
 
 const (
 	gzipScheme = "gzip"
 )
 
-var (
-	// DefaultGzipConfig is the default Gzip middleware config.
-	DefaultGzipConfig = GzipConfig{
-		Skipper:   DefaultSkipper,
-		Level:     -1,
-		MinLength: 0,
-	}
-)
+// DefaultGzipConfig is the default Gzip middleware config.
+var DefaultGzipConfig = GzipConfig{
+	Skipper:   DefaultSkipper,
+	Level:     -1,
+	MinLength: 0,
+}
 
 // Gzip returns a middleware which compresses HTTP response using gzip compression
 // scheme.
@@ -191,13 +190,15 @@ func (w *gzipResponseWriter) Flush() {
 	}
 
 	w.Writer.(*gzip.Writer).Flush()
-	if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
-		flusher.Flush()
-	}
+	_ = responseControllerFlush(w.ResponseWriter)
+}
+
+func (w *gzipResponseWriter) Unwrap() http.ResponseWriter {
+	return w.ResponseWriter
 }
 
 func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
-	return w.ResponseWriter.(http.Hijacker).Hijack()
+	return responseControllerHijack(w.ResponseWriter)
 }
 
 func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error {
diff --git a/middleware/context_timeout.go b/middleware/context_timeout.go
index 1937693..e67173f 100644
--- a/middleware/context_timeout.go
+++ b/middleware/context_timeout.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/cors.go b/middleware/cors.go
index 7ace2f2..7af6a76 100644
--- a/middleware/cors.go
+++ b/middleware/cors.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -9,113 +12,109 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// CORSConfig defines the config for CORS middleware.
-	CORSConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// AllowOrigins determines the value of the Access-Control-Allow-Origin
-		// response header.  This header defines a list of origins that may access the
-		// resource.  The wildcard characters '*' and '?' are supported and are
-		// converted to regex fragments '.*' and '.' accordingly.
-		//
-		// Security: use extreme caution when handling the origin, and carefully
-		// validate any logic. Remember that attackers may register hostile domain names.
-		// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
-		//
-		// Optional. Default value []string{"*"}.
-		//
-		// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
-		AllowOrigins []string `yaml:"allow_origins"`
-
-		// AllowOriginFunc is a custom function to validate the origin. It takes the
-		// origin as an argument and returns true if allowed or false otherwise. If
-		// an error is returned, it is returned by the handler. If this option is
-		// set, AllowOrigins is ignored.
-		//
-		// Security: use extreme caution when handling the origin, and carefully
-		// validate any logic. Remember that attackers may register hostile domain names.
-		// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
-		//
-		// Optional.
-		AllowOriginFunc func(origin string) (bool, error) `yaml:"-"`
-
-		// AllowMethods determines the value of the Access-Control-Allow-Methods
-		// response header.  This header specified the list of methods allowed when
-		// accessing the resource.  This is used in response to a preflight request.
-		//
-		// Optional. Default value DefaultCORSConfig.AllowMethods.
-		// If `allowMethods` is left empty, this middleware will fill for preflight
-		// request `Access-Control-Allow-Methods` header value
-		// from `Allow` header that echo.Router set into context.
-		//
-		// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
-		AllowMethods []string `yaml:"allow_methods"`
-
-		// AllowHeaders determines the value of the Access-Control-Allow-Headers
-		// response header.  This header is used in response to a preflight request to
-		// indicate which HTTP headers can be used when making the actual request.
-		//
-		// Optional. Default value []string{}.
-		//
-		// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
-		AllowHeaders []string `yaml:"allow_headers"`
-
-		// AllowCredentials determines the value of the
-		// Access-Control-Allow-Credentials response header.  This header indicates
-		// whether or not the response to the request can be exposed when the
-		// credentials mode (Request.credentials) is true. When used as part of a
-		// response to a preflight request, this indicates whether or not the actual
-		// request can be made using credentials.  See also
-		// [MDN: Access-Control-Allow-Credentials].
-		//
-		// Optional. Default value false, in which case the header is not set.
-		//
-		// Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`.
-		// See "Exploiting CORS misconfigurations for Bitcoins and bounties",
-		// https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
-		//
-		// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
-		AllowCredentials bool `yaml:"allow_credentials"`
-
-		// UnsafeWildcardOriginWithAllowCredentials UNSAFE/INSECURE: allows wildcard '*' origin to be used with AllowCredentials
-		// flag. In that case we consider any origin allowed and send it back to the client with `Access-Control-Allow-Origin` header.
-		//
-		// This is INSECURE and potentially leads to [cross-origin](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties)
-		// attacks. See: https://github.com/labstack/echo/issues/2400 for discussion on the subject.
-		//
-		// Optional. Default value is false.
-		UnsafeWildcardOriginWithAllowCredentials bool `yaml:"unsafe_wildcard_origin_with_allow_credentials"`
-
-		// ExposeHeaders determines the value of Access-Control-Expose-Headers, which
-		// defines a list of headers that clients are allowed to access.
-		//
-		// Optional. Default value []string{}, in which case the header is not set.
-		//
-		// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Header
-		ExposeHeaders []string `yaml:"expose_headers"`
-
-		// MaxAge determines the value of the Access-Control-Max-Age response header.
-		// This header indicates how long (in seconds) the results of a preflight
-		// request can be cached.
-		// The header is set only if MaxAge != 0, negative value sends "0" which instructs browsers not to cache that response.
-		//
-		// Optional. Default value 0 - meaning header is not sent.
-		//
-		// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
-		MaxAge int `yaml:"max_age"`
-	}
-)
+// CORSConfig defines the config for CORS middleware.
+type CORSConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
 
-var (
-	// DefaultCORSConfig is the default CORS middleware config.
-	DefaultCORSConfig = CORSConfig{
-		Skipper:      DefaultSkipper,
-		AllowOrigins: []string{"*"},
-		AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
-	}
-)
+	// AllowOrigins determines the value of the Access-Control-Allow-Origin
+	// response header.  This header defines a list of origins that may access the
+	// resource.  The wildcard characters '*' and '?' are supported and are
+	// converted to regex fragments '.*' and '.' accordingly.
+	//
+	// Security: use extreme caution when handling the origin, and carefully
+	// validate any logic. Remember that attackers may register hostile domain names.
+	// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
+	//
+	// Optional. Default value []string{"*"}.
+	//
+	// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
+	AllowOrigins []string `yaml:"allow_origins"`
+
+	// AllowOriginFunc is a custom function to validate the origin. It takes the
+	// origin as an argument and returns true if allowed or false otherwise. If
+	// an error is returned, it is returned by the handler. If this option is
+	// set, AllowOrigins is ignored.
+	//
+	// Security: use extreme caution when handling the origin, and carefully
+	// validate any logic. Remember that attackers may register hostile domain names.
+	// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
+	//
+	// Optional.
+	AllowOriginFunc func(origin string) (bool, error) `yaml:"-"`
+
+	// AllowMethods determines the value of the Access-Control-Allow-Methods
+	// response header.  This header specified the list of methods allowed when
+	// accessing the resource.  This is used in response to a preflight request.
+	//
+	// Optional. Default value DefaultCORSConfig.AllowMethods.
+	// If `allowMethods` is left empty, this middleware will fill for preflight
+	// request `Access-Control-Allow-Methods` header value
+	// from `Allow` header that echo.Router set into context.
+	//
+	// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
+	AllowMethods []string `yaml:"allow_methods"`
+
+	// AllowHeaders determines the value of the Access-Control-Allow-Headers
+	// response header.  This header is used in response to a preflight request to
+	// indicate which HTTP headers can be used when making the actual request.
+	//
+	// Optional. Default value []string{}.
+	//
+	// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
+	AllowHeaders []string `yaml:"allow_headers"`
+
+	// AllowCredentials determines the value of the
+	// Access-Control-Allow-Credentials response header.  This header indicates
+	// whether or not the response to the request can be exposed when the
+	// credentials mode (Request.credentials) is true. When used as part of a
+	// response to a preflight request, this indicates whether or not the actual
+	// request can be made using credentials.  See also
+	// [MDN: Access-Control-Allow-Credentials].
+	//
+	// Optional. Default value false, in which case the header is not set.
+	//
+	// Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`.
+	// See "Exploiting CORS misconfigurations for Bitcoins and bounties",
+	// https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
+	//
+	// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
+	AllowCredentials bool `yaml:"allow_credentials"`
+
+	// UnsafeWildcardOriginWithAllowCredentials UNSAFE/INSECURE: allows wildcard '*' origin to be used with AllowCredentials
+	// flag. In that case we consider any origin allowed and send it back to the client with `Access-Control-Allow-Origin` header.
+	//
+	// This is INSECURE and potentially leads to [cross-origin](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties)
+	// attacks. See: https://github.com/labstack/echo/issues/2400 for discussion on the subject.
+	//
+	// Optional. Default value is false.
+	UnsafeWildcardOriginWithAllowCredentials bool `yaml:"unsafe_wildcard_origin_with_allow_credentials"`
+
+	// ExposeHeaders determines the value of Access-Control-Expose-Headers, which
+	// defines a list of headers that clients are allowed to access.
+	//
+	// Optional. Default value []string{}, in which case the header is not set.
+	//
+	// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Header
+	ExposeHeaders []string `yaml:"expose_headers"`
+
+	// MaxAge determines the value of the Access-Control-Max-Age response header.
+	// This header indicates how long (in seconds) the results of a preflight
+	// request can be cached.
+	// The header is set only if MaxAge != 0, negative value sends "0" which instructs browsers not to cache that response.
+	//
+	// Optional. Default value 0 - meaning header is not sent.
+	//
+	// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
+	MaxAge int `yaml:"max_age"`
+}
+
+// DefaultCORSConfig is the default CORS middleware config.
+var DefaultCORSConfig = CORSConfig{
+	Skipper:      DefaultSkipper,
+	AllowOrigins: []string{"*"},
+	AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
+}
 
 // CORS returns a Cross-Origin Resource Sharing (CORS) middleware.
 // See also [MDN: Cross-Origin Resource Sharing (CORS)].
diff --git a/middleware/csrf.go b/middleware/csrf.go
index adf1221..92f4019 100644
--- a/middleware/csrf.go
+++ b/middleware/csrf.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -8,82 +11,78 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// CSRFConfig defines the config for CSRF middleware.
-	CSRFConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// TokenLength is the length of the generated token.
-		TokenLength uint8 `yaml:"token_length"`
-		// Optional. Default value 32.
-
-		// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
-		// to extract token from the request.
-		// Optional. Default value "header:X-CSRF-Token".
-		// Possible values:
-		// - "header:<name>" or "header:<name>:<cut-prefix>"
-		// - "query:<name>"
-		// - "form:<name>"
-		// Multiple sources example:
-		// - "header:X-CSRF-Token,query:csrf"
-		TokenLookup string `yaml:"token_lookup"`
-
-		// Context key to store generated CSRF token into context.
-		// Optional. Default value "csrf".
-		ContextKey string `yaml:"context_key"`
-
-		// Name of the CSRF cookie. This cookie will store CSRF token.
-		// Optional. Default value "csrf".
-		CookieName string `yaml:"cookie_name"`
-
-		// Domain of the CSRF cookie.
-		// Optional. Default value none.
-		CookieDomain string `yaml:"cookie_domain"`
-
-		// Path of the CSRF cookie.
-		// Optional. Default value none.
-		CookiePath string `yaml:"cookie_path"`
-
-		// Max age (in seconds) of the CSRF cookie.
-		// Optional. Default value 86400 (24hr).
-		CookieMaxAge int `yaml:"cookie_max_age"`
-
-		// Indicates if CSRF cookie is secure.
-		// Optional. Default value false.
-		CookieSecure bool `yaml:"cookie_secure"`
-
-		// Indicates if CSRF cookie is HTTP only.
-		// Optional. Default value false.
-		CookieHTTPOnly bool `yaml:"cookie_http_only"`
-
-		// Indicates SameSite mode of the CSRF cookie.
-		// Optional. Default value SameSiteDefaultMode.
-		CookieSameSite http.SameSite `yaml:"cookie_same_site"`
-
-		// ErrorHandler defines a function which is executed for returning custom errors.
-		ErrorHandler CSRFErrorHandler
-	}
+// CSRFConfig defines the config for CSRF middleware.
+type CSRFConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
+
+	// TokenLength is the length of the generated token.
+	TokenLength uint8 `yaml:"token_length"`
+	// Optional. Default value 32.
+
+	// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
+	// to extract token from the request.
+	// Optional. Default value "header:X-CSRF-Token".
+	// Possible values:
+	// - "header:<name>" or "header:<name>:<cut-prefix>"
+	// - "query:<name>"
+	// - "form:<name>"
+	// Multiple sources example:
+	// - "header:X-CSRF-Token,query:csrf"
+	TokenLookup string `yaml:"token_lookup"`
+
+	// Context key to store generated CSRF token into context.
+	// Optional. Default value "csrf".
+	ContextKey string `yaml:"context_key"`
+
+	// Name of the CSRF cookie. This cookie will store CSRF token.
+	// Optional. Default value "csrf".
+	CookieName string `yaml:"cookie_name"`
+
+	// Domain of the CSRF cookie.
+	// Optional. Default value none.
+	CookieDomain string `yaml:"cookie_domain"`
+
+	// Path of the CSRF cookie.
+	// Optional. Default value none.
+	CookiePath string `yaml:"cookie_path"`
+
+	// Max age (in seconds) of the CSRF cookie.
+	// Optional. Default value 86400 (24hr).
+	CookieMaxAge int `yaml:"cookie_max_age"`
+
+	// Indicates if CSRF cookie is secure.
+	// Optional. Default value false.
+	CookieSecure bool `yaml:"cookie_secure"`
+
+	// Indicates if CSRF cookie is HTTP only.
+	// Optional. Default value false.
+	CookieHTTPOnly bool `yaml:"cookie_http_only"`
+
+	// Indicates SameSite mode of the CSRF cookie.
+	// Optional. Default value SameSiteDefaultMode.
+	CookieSameSite http.SameSite `yaml:"cookie_same_site"`
+
+	// ErrorHandler defines a function which is executed for returning custom errors.
+	ErrorHandler CSRFErrorHandler
+}
 
-	// CSRFErrorHandler is a function which is executed for creating custom errors.
-	CSRFErrorHandler func(err error, c echo.Context) error
-)
+// CSRFErrorHandler is a function which is executed for creating custom errors.
+type CSRFErrorHandler func(err error, c echo.Context) error
 
 // ErrCSRFInvalid is returned when CSRF check fails
 var ErrCSRFInvalid = echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
 
-var (
-	// DefaultCSRFConfig is the default CSRF middleware config.
-	DefaultCSRFConfig = CSRFConfig{
-		Skipper:        DefaultSkipper,
-		TokenLength:    32,
-		TokenLookup:    "header:" + echo.HeaderXCSRFToken,
-		ContextKey:     "csrf",
-		CookieName:     "_csrf",
-		CookieMaxAge:   86400,
-		CookieSameSite: http.SameSiteDefaultMode,
-	}
-)
+// DefaultCSRFConfig is the default CSRF middleware config.
+var DefaultCSRFConfig = CSRFConfig{
+	Skipper:        DefaultSkipper,
+	TokenLength:    32,
+	TokenLookup:    "header:" + echo.HeaderXCSRFToken,
+	ContextKey:     "csrf",
+	CookieName:     "_csrf",
+	CookieMaxAge:   86400,
+	CookieSameSite: http.SameSiteDefaultMode,
+}
 
 // CSRF returns a Cross-Site Request Forgery (CSRF) middleware.
 // See: https://en.wikipedia.org/wiki/Cross-site_request_forgery
diff --git a/middleware/decompress.go b/middleware/decompress.go
index a73c973..0c56176 100644
--- a/middleware/decompress.go
+++ b/middleware/decompress.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -9,16 +12,14 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// DecompressConfig defines the config for Decompress middleware.
-	DecompressConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
+// DecompressConfig defines the config for Decompress middleware.
+type DecompressConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
 
-		// GzipDecompressPool defines an interface to provide the sync.Pool used to create/store Gzip readers
-		GzipDecompressPool Decompressor
-	}
-)
+	// GzipDecompressPool defines an interface to provide the sync.Pool used to create/store Gzip readers
+	GzipDecompressPool Decompressor
+}
 
 // GZIPEncoding content-encoding header if set to "gzip", decompress body contents.
 const GZIPEncoding string = "gzip"
@@ -28,13 +29,11 @@ type Decompressor interface {
 	gzipDecompressPool() sync.Pool
 }
 
-var (
-	//DefaultDecompressConfig defines the config for decompress middleware
-	DefaultDecompressConfig = DecompressConfig{
-		Skipper:            DefaultSkipper,
-		GzipDecompressPool: &DefaultGzipDecompressPool{},
-	}
-)
+// DefaultDecompressConfig defines the config for decompress middleware
+var DefaultDecompressConfig = DecompressConfig{
+	Skipper:            DefaultSkipper,
+	GzipDecompressPool: &DefaultGzipDecompressPool{},
+}
 
 // DefaultGzipDecompressPool is the default implementation of Decompressor interface
 type DefaultGzipDecompressPool struct {
diff --git a/middleware/extractor.go b/middleware/extractor.go
index 5d9cee6..3f27414 100644
--- a/middleware/extractor.go
+++ b/middleware/extractor.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/jwt.go b/middleware/jwt.go
index bc318c9..a6bf16f 100644
--- a/middleware/jwt.go
+++ b/middleware/jwt.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 //go:build go1.15
 // +build go1.15
 
@@ -12,139 +15,135 @@ import (
 	"reflect"
 )
 
-type (
-	// JWTConfig defines the config for JWT middleware.
-	JWTConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// BeforeFunc defines a function which is executed just before the middleware.
-		BeforeFunc BeforeFunc
-
-		// SuccessHandler defines a function which is executed for a valid token before middleware chain continues with next
-		// middleware or handler.
-		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
-
-		// ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context.
-		ErrorHandlerWithContext JWTErrorHandlerWithContext
-
-		// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandlerWithContext decides to
-		// ignore the error (by returning `nil`).
-		// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
-		// In that case you can use ErrorHandlerWithContext to set a default public JWT token value in the request context
-		// and continue. Some logic down the remaining execution chain needs to check that (public) token value then.
-		ContinueOnIgnoredError bool
-
-		// Signing key to validate token.
-		// This is one of the three options to provide a token validation key.
-		// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
-		// Required if neither user-defined KeyFunc nor SigningKeys is provided.
-		SigningKey interface{}
-
-		// Map of signing keys to validate token with kid field usage.
-		// This is one of the three options to provide a token validation key.
-		// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
-		// Required if neither user-defined KeyFunc nor SigningKey is provided.
-		SigningKeys map[string]interface{}
-
-		// Signing method used to check the token's signing algorithm.
-		// Optional. Default value HS256.
-		SigningMethod string
-
-		// Context key to store user information from the token into context.
-		// Optional. Default value "user".
-		ContextKey string
-
-		// Claims are extendable claims data defining token content. Used by default ParseTokenFunc implementation.
-		// Not used if custom ParseTokenFunc is set.
-		// Optional. Default value jwt.MapClaims
-		Claims jwt.Claims
-
-		// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
-		// to extract token from the request.
-		// Optional. Default value "header:Authorization".
-		// Possible values:
-		// - "header:<name>" or "header:<name>:<cut-prefix>"
-		// 			`<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
-		//			value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
-		//			want to cut is `<auth-scheme> ` note the space at the end.
-		//			In case of JWT tokens `Authorization: Bearer <token>` prefix we cut is `Bearer `.
-		// If prefix is left empty the whole value is returned.
-		// - "query:<name>"
-		// - "param:<name>"
-		// - "cookie:<name>"
-		// - "form:<name>"
-		// Multiple sources example:
-		// - "header:Authorization,cookie:myowncookie"
-		TokenLookup string
-
-		// TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context.
-		// This is one of the two options to provide a token extractor.
-		// The order of precedence is user-defined TokenLookupFuncs, and TokenLookup.
-		// You can also provide both if you want.
-		TokenLookupFuncs []ValuesExtractor
-
-		// AuthScheme to be used in the Authorization header.
-		// Optional. Default value "Bearer".
-		AuthScheme string
-
-		// KeyFunc defines a user-defined function that supplies the public key for a token validation.
-		// The function shall take care of verifying the signing algorithm and selecting the proper key.
-		// A user-defined KeyFunc can be useful if tokens are issued by an external party.
-		// Used by default ParseTokenFunc implementation.
-		//
-		// When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored.
-		// This is one of the three options to provide a token validation key.
-		// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
-		// Required if neither SigningKeys nor SigningKey is provided.
-		// Not used if custom ParseTokenFunc is set.
-		// Default to an internal implementation verifying the signing algorithm and selecting the proper key.
-		KeyFunc jwt.Keyfunc
-
-		// ParseTokenFunc defines a user-defined function that parses token from given auth. Returns an error when token
-		// parsing fails or parsed token is invalid.
-		// Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library
-		ParseTokenFunc func(auth string, c echo.Context) (interface{}, error)
-	}
+// JWTConfig defines the config for JWT middleware.
+type JWTConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
+
+	// BeforeFunc defines a function which is executed just before the middleware.
+	BeforeFunc BeforeFunc
+
+	// SuccessHandler defines a function which is executed for a valid token before middleware chain continues with next
+	// middleware or handler.
+	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
+
+	// ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context.
+	ErrorHandlerWithContext JWTErrorHandlerWithContext
+
+	// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandlerWithContext decides to
+	// ignore the error (by returning `nil`).
+	// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
+	// In that case you can use ErrorHandlerWithContext to set a default public JWT token value in the request context
+	// and continue. Some logic down the remaining execution chain needs to check that (public) token value then.
+	ContinueOnIgnoredError bool
+
+	// Signing key to validate token.
+	// This is one of the three options to provide a token validation key.
+	// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
+	// Required if neither user-defined KeyFunc nor SigningKeys is provided.
+	SigningKey interface{}
+
+	// Map of signing keys to validate token with kid field usage.
+	// This is one of the three options to provide a token validation key.
+	// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
+	// Required if neither user-defined KeyFunc nor SigningKey is provided.
+	SigningKeys map[string]interface{}
+
+	// Signing method used to check the token's signing algorithm.
+	// Optional. Default value HS256.
+	SigningMethod string
+
+	// Context key to store user information from the token into context.
+	// Optional. Default value "user".
+	ContextKey string
+
+	// Claims are extendable claims data defining token content. Used by default ParseTokenFunc implementation.
+	// Not used if custom ParseTokenFunc is set.
+	// Optional. Default value jwt.MapClaims
+	Claims jwt.Claims
+
+	// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
+	// to extract token from the request.
+	// Optional. Default value "header:Authorization".
+	// Possible values:
+	// - "header:<name>" or "header:<name>:<cut-prefix>"
+	// 			`<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
+	//			value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
+	//			want to cut is `<auth-scheme> ` note the space at the end.
+	//			In case of JWT tokens `Authorization: Bearer <token>` prefix we cut is `Bearer `.
+	// If prefix is left empty the whole value is returned.
+	// - "query:<name>"
+	// - "param:<name>"
+	// - "cookie:<name>"
+	// - "form:<name>"
+	// Multiple sources example:
+	// - "header:Authorization,cookie:myowncookie"
+	TokenLookup string
+
+	// TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context.
+	// This is one of the two options to provide a token extractor.
+	// The order of precedence is user-defined TokenLookupFuncs, and TokenLookup.
+	// You can also provide both if you want.
+	TokenLookupFuncs []ValuesExtractor
+
+	// AuthScheme to be used in the Authorization header.
+	// Optional. Default value "Bearer".
+	AuthScheme string
+
+	// KeyFunc defines a user-defined function that supplies the public key for a token validation.
+	// The function shall take care of verifying the signing algorithm and selecting the proper key.
+	// A user-defined KeyFunc can be useful if tokens are issued by an external party.
+	// Used by default ParseTokenFunc implementation.
+	//
+	// When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored.
+	// This is one of the three options to provide a token validation key.
+	// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
+	// Required if neither SigningKeys nor SigningKey is provided.
+	// Not used if custom ParseTokenFunc is set.
+	// Default to an internal implementation verifying the signing algorithm and selecting the proper key.
+	KeyFunc jwt.Keyfunc
+
+	// ParseTokenFunc defines a user-defined function that parses token from given auth. Returns an error when token
+	// parsing fails or parsed token is invalid.
+	// Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library
+	ParseTokenFunc func(auth string, c echo.Context) (interface{}, error)
+}
 
-	// JWTSuccessHandler defines a function which is executed for a valid token.
-	JWTSuccessHandler func(c echo.Context)
+// JWTSuccessHandler defines a function which is executed for a valid token.
+type JWTSuccessHandler func(c echo.Context)
 
-	// JWTErrorHandler defines a function which is executed for an invalid token.
-	JWTErrorHandler func(err error) error
+// JWTErrorHandler defines a function which is executed for an invalid token.
+type JWTErrorHandler func(err error) error
 
-	// JWTErrorHandlerWithContext is almost identical to JWTErrorHandler, but it's passed the current context.
-	JWTErrorHandlerWithContext func(err error, c echo.Context) error
-)
+// JWTErrorHandlerWithContext is almost identical to JWTErrorHandler, but it's passed the current context.
+type JWTErrorHandlerWithContext func(err error, c echo.Context) error
 
 // Algorithms
 const (
 	AlgorithmHS256 = "HS256"
 )
 
-// Errors
-var (
-	ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
-	ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt")
-)
-
-var (
-	// DefaultJWTConfig is the default JWT auth middleware config.
-	DefaultJWTConfig = JWTConfig{
-		Skipper:          DefaultSkipper,
-		SigningMethod:    AlgorithmHS256,
-		ContextKey:       "user",
-		TokenLookup:      "header:" + echo.HeaderAuthorization,
-		TokenLookupFuncs: nil,
-		AuthScheme:       "Bearer",
-		Claims:           jwt.MapClaims{},
-		KeyFunc:          nil,
-	}
-)
+// ErrJWTMissing is error that is returned when no JWToken was extracted from the request.
+var ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
+
+// ErrJWTInvalid is error that is returned when middleware could not parse JWT correctly.
+var ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt")
+
+// DefaultJWTConfig is the default JWT auth middleware config.
+var DefaultJWTConfig = JWTConfig{
+	Skipper:          DefaultSkipper,
+	SigningMethod:    AlgorithmHS256,
+	ContextKey:       "user",
+	TokenLookup:      "header:" + echo.HeaderAuthorization,
+	TokenLookupFuncs: nil,
+	AuthScheme:       "Bearer",
+	Claims:           jwt.MapClaims{},
+	KeyFunc:          nil,
+}
 
 // JWT returns a JSON Web Token (JWT) auth middleware.
 //
diff --git a/middleware/key_auth.go b/middleware/key_auth.go
index f6fcc5d..79bee20 100644
--- a/middleware/key_auth.go
+++ b/middleware/key_auth.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -6,69 +9,65 @@ import (
 	"net/http"
 )
 
-type (
-	// KeyAuthConfig defines the config for KeyAuth middleware.
-	KeyAuthConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// KeyLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
-		// to extract key from the request.
-		// Optional. Default value "header:Authorization".
-		// Possible values:
-		// - "header:<name>" or "header:<name>:<cut-prefix>"
-		// 			`<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
-		//			value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
-		//			want to cut is `<auth-scheme> ` note the space at the end.
-		//			In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `.
-		// - "query:<name>"
-		// - "form:<name>"
-		// - "cookie:<name>"
-		// Multiple sources example:
-		// - "header:Authorization,header:X-Api-Key"
-		KeyLookup string
-
-		// AuthScheme to be used in the Authorization header.
-		// Optional. Default value "Bearer".
-		AuthScheme string
-
-		// Validator is a function to validate key.
-		// Required.
-		Validator KeyAuthValidator
-
-		// ErrorHandler defines a function which is executed for an invalid key.
-		// It may be used to define a custom error.
-		ErrorHandler KeyAuthErrorHandler
-
-		// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to
-		// ignore the error (by returning `nil`).
-		// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
-		// In that case you can use ErrorHandler to set a default public key auth value in the request context
-		// and continue. Some logic down the remaining execution chain needs to check that (public) key auth value then.
-		ContinueOnIgnoredError bool
-	}
+// KeyAuthConfig defines the config for KeyAuth middleware.
+type KeyAuthConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
+
+	// KeyLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
+	// to extract key from the request.
+	// Optional. Default value "header:Authorization".
+	// Possible values:
+	// - "header:<name>" or "header:<name>:<cut-prefix>"
+	// 			`<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
+	//			value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
+	//			want to cut is `<auth-scheme> ` note the space at the end.
+	//			In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `.
+	// - "query:<name>"
+	// - "form:<name>"
+	// - "cookie:<name>"
+	// Multiple sources example:
+	// - "header:Authorization,header:X-Api-Key"
+	KeyLookup string
+
+	// AuthScheme to be used in the Authorization header.
+	// Optional. Default value "Bearer".
+	AuthScheme string
+
+	// Validator is a function to validate key.
+	// Required.
+	Validator KeyAuthValidator
+
+	// ErrorHandler defines a function which is executed for an invalid key.
+	// It may be used to define a custom error.
+	ErrorHandler KeyAuthErrorHandler
+
+	// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to
+	// ignore the error (by returning `nil`).
+	// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
+	// In that case you can use ErrorHandler to set a default public key auth value in the request context
+	// and continue. Some logic down the remaining execution chain needs to check that (public) key auth value then.
+	ContinueOnIgnoredError bool
+}
 
-	// KeyAuthValidator defines a function to validate KeyAuth credentials.
-	KeyAuthValidator func(auth string, c echo.Context) (bool, error)
+// KeyAuthValidator defines a function to validate KeyAuth credentials.
+type KeyAuthValidator func(auth string, c echo.Context) (bool, error)
 
-	// KeyAuthErrorHandler defines a function which is executed for an invalid key.
-	KeyAuthErrorHandler func(err error, c echo.Context) error
-)
-
-var (
-	// DefaultKeyAuthConfig is the default KeyAuth middleware config.
-	DefaultKeyAuthConfig = KeyAuthConfig{
-		Skipper:    DefaultSkipper,
-		KeyLookup:  "header:" + echo.HeaderAuthorization,
-		AuthScheme: "Bearer",
-	}
-)
+// KeyAuthErrorHandler defines a function which is executed for an invalid key.
+type KeyAuthErrorHandler func(err error, c echo.Context) error
 
 // ErrKeyAuthMissing is error type when KeyAuth middleware is unable to extract value from lookups
 type ErrKeyAuthMissing struct {
 	Err error
 }
 
+// DefaultKeyAuthConfig is the default KeyAuth middleware config.
+var DefaultKeyAuthConfig = KeyAuthConfig{
+	Skipper:    DefaultSkipper,
+	KeyLookup:  "header:" + echo.HeaderAuthorization,
+	AuthScheme: "Bearer",
+}
+
 // Error returns errors text
 func (e *ErrKeyAuthMissing) Error() string {
 	return e.Err.Error()
diff --git a/middleware/logger.go b/middleware/logger.go
index 7958d87..910fce8 100644
--- a/middleware/logger.go
+++ b/middleware/logger.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -14,77 +17,73 @@ import (
 	"github.com/valyala/fasttemplate"
 )
 
-type (
-	// LoggerConfig defines the config for Logger middleware.
-	LoggerConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// Tags to construct the logger format.
-		//
-		// - time_unix
-		// - time_unix_milli
-		// - time_unix_micro
-		// - time_unix_nano
-		// - time_rfc3339
-		// - time_rfc3339_nano
-		// - time_custom
-		// - id (Request ID)
-		// - remote_ip
-		// - uri
-		// - host
-		// - method
-		// - path
-		// - route
-		// - protocol
-		// - referer
-		// - user_agent
-		// - status
-		// - error
-		// - latency (In nanoseconds)
-		// - latency_human (Human readable)
-		// - bytes_in (Bytes received)
-		// - bytes_out (Bytes sent)
-		// - header:<NAME>
-		// - query:<NAME>
-		// - form:<NAME>
-		// - custom (see CustomTagFunc field)
-		//
-		// Example "${remote_ip} ${status}"
-		//
-		// Optional. Default value DefaultLoggerConfig.Format.
-		Format string `yaml:"format"`
-
-		// Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
-		CustomTimeFormat string `yaml:"custom_time_format"`
-
-		// CustomTagFunc is function called for `${custom}` tag to output user implemented text by writing it to buf.
-		// Make sure that outputted text creates valid JSON string with other logged tags.
-		// Optional.
-		CustomTagFunc func(c echo.Context, buf *bytes.Buffer) (int, error)
-
-		// Output is a writer where logs in JSON format are written.
-		// Optional. Default value os.Stdout.
-		Output io.Writer
-
-		template *fasttemplate.Template
-		colorer  *color.Color
-		pool     *sync.Pool
-	}
-)
+// LoggerConfig defines the config for Logger middleware.
+type LoggerConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
+
+	// Tags to construct the logger format.
+	//
+	// - time_unix
+	// - time_unix_milli
+	// - time_unix_micro
+	// - time_unix_nano
+	// - time_rfc3339
+	// - time_rfc3339_nano
+	// - time_custom
+	// - id (Request ID)
+	// - remote_ip
+	// - uri
+	// - host
+	// - method
+	// - path
+	// - route
+	// - protocol
+	// - referer
+	// - user_agent
+	// - status
+	// - error
+	// - latency (In nanoseconds)
+	// - latency_human (Human readable)
+	// - bytes_in (Bytes received)
+	// - bytes_out (Bytes sent)
+	// - header:<NAME>
+	// - query:<NAME>
+	// - form:<NAME>
+	// - custom (see CustomTagFunc field)
+	//
+	// Example "${remote_ip} ${status}"
+	//
+	// Optional. Default value DefaultLoggerConfig.Format.
+	Format string `yaml:"format"`
+
+	// Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
+	CustomTimeFormat string `yaml:"custom_time_format"`
+
+	// CustomTagFunc is function called for `${custom}` tag to output user implemented text by writing it to buf.
+	// Make sure that outputted text creates valid JSON string with other logged tags.
+	// Optional.
+	CustomTagFunc func(c echo.Context, buf *bytes.Buffer) (int, error)
+
+	// Output is a writer where logs in JSON format are written.
+	// Optional. Default value os.Stdout.
+	Output io.Writer
+
+	template *fasttemplate.Template
+	colorer  *color.Color
+	pool     *sync.Pool
+}
 
-var (
-	// DefaultLoggerConfig is the default Logger middleware config.
-	DefaultLoggerConfig = LoggerConfig{
-		Skipper: DefaultSkipper,
-		Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
-			`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
-			`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
-			`,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
-		CustomTimeFormat: "2006-01-02 15:04:05.00000",
-		colorer:          color.New(),
-	}
-)
+// DefaultLoggerConfig is the default Logger middleware config.
+var DefaultLoggerConfig = LoggerConfig{
+	Skipper: DefaultSkipper,
+	Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
+		`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
+		`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
+		`,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
+	CustomTimeFormat: "2006-01-02 15:04:05.00000",
+	colorer:          color.New(),
+}
 
 // Logger returns a middleware that logs HTTP requests.
 func Logger() echo.MiddlewareFunc {
diff --git a/middleware/method_override.go b/middleware/method_override.go
index 92b14d2..3991e10 100644
--- a/middleware/method_override.go
+++ b/middleware/method_override.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -6,28 +9,24 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// MethodOverrideConfig defines the config for MethodOverride middleware.
-	MethodOverrideConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
+// MethodOverrideConfig defines the config for MethodOverride middleware.
+type MethodOverrideConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
 
-		// Getter is a function that gets overridden method from the request.
-		// Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride).
-		Getter MethodOverrideGetter
-	}
+	// Getter is a function that gets overridden method from the request.
+	// Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride).
+	Getter MethodOverrideGetter
+}
 
-	// MethodOverrideGetter is a function that gets overridden method from the request
-	MethodOverrideGetter func(echo.Context) string
-)
+// MethodOverrideGetter is a function that gets overridden method from the request
+type MethodOverrideGetter func(echo.Context) string
 
-var (
-	// DefaultMethodOverrideConfig is the default MethodOverride middleware config.
-	DefaultMethodOverrideConfig = MethodOverrideConfig{
-		Skipper: DefaultSkipper,
-		Getter:  MethodFromHeader(echo.HeaderXHTTPMethodOverride),
-	}
-)
+// DefaultMethodOverrideConfig is the default MethodOverride middleware config.
+var DefaultMethodOverrideConfig = MethodOverrideConfig{
+	Skipper: DefaultSkipper,
+	Getter:  MethodFromHeader(echo.HeaderXHTTPMethodOverride),
+}
 
 // MethodOverride returns a MethodOverride middleware.
 // MethodOverride  middleware checks for the overridden method from the request and
diff --git a/middleware/middleware.go b/middleware/middleware.go
index 664f71f..6f33cc5 100644
--- a/middleware/middleware.go
+++ b/middleware/middleware.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -9,14 +12,12 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// Skipper defines a function to skip middleware. Returning true skips processing
-	// the middleware.
-	Skipper func(c echo.Context) bool
+// Skipper defines a function to skip middleware. Returning true skips processing
+// the middleware.
+type Skipper func(c echo.Context) bool
 
-	// BeforeFunc defines a function which is executed just before the middleware.
-	BeforeFunc func(c echo.Context)
-)
+// BeforeFunc defines a function which is executed just before the middleware.
+type BeforeFunc func(c echo.Context)
 
 func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
 	groups := pattern.FindAllStringSubmatch(input, -1)
@@ -53,7 +54,7 @@ func rewriteURL(rewriteRegex map[*regexp.Regexp]string, req *http.Request) error
 		return nil
 	}
 
-	// Depending how HTTP request is sent RequestURI could contain Scheme://Host/path or be just /path.
+	// Depending on how HTTP request is sent RequestURI could contain Scheme://Host/path or be just /path.
 	// We only want to use path part for rewriting and therefore trim prefix if it exists
 	rawURI := req.RequestURI
 	if rawURI != "" && rawURI[0] != '/' {
diff --git a/middleware/proxy.go b/middleware/proxy.go
index 16b00d6..f6b302a 100644
--- a/middleware/proxy.go
+++ b/middleware/proxy.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -19,117 +22,113 @@ import (
 
 // TODO: Handle TLS proxy
 
-type (
-	// ProxyConfig defines the config for Proxy middleware.
-	ProxyConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// Balancer defines a load balancing technique.
-		// Required.
-		Balancer ProxyBalancer
-
-		// RetryCount defines the number of times a failed proxied request should be retried
-		// using the next available ProxyTarget. Defaults to 0, meaning requests are never retried.
-		RetryCount int
-
-		// RetryFilter defines a function used to determine if a failed request to a
-		// ProxyTarget should be retried. The RetryFilter will only be called when the number
-		// of previous retries is less than RetryCount. If the function returns true, the
-		// request will be retried. The provided error indicates the reason for the request
-		// failure. When the ProxyTarget is unavailable, the error will be an instance of
-		// echo.HTTPError with a Code of http.StatusBadGateway. In all other cases, the error
-		// will indicate an internal error in the Proxy middleware. When a RetryFilter is not
-		// specified, all requests that fail with http.StatusBadGateway will be retried. A custom
-		// RetryFilter can be provided to only retry specific requests. Note that RetryFilter is
-		// only called when the request to the target fails, or an internal error in the Proxy
-		// middleware has occurred. Successful requests that return a non-200 response code cannot
-		// be retried.
-		RetryFilter func(c echo.Context, e error) bool
-
-		// ErrorHandler defines a function which can be used to return custom errors from
-		// the Proxy middleware. ErrorHandler is only invoked when there has been
-		// either an internal error in the Proxy middleware or the ProxyTarget is
-		// unavailable. Due to the way requests are proxied, ErrorHandler is not invoked
-		// when a ProxyTarget returns a non-200 response. In these cases, the response
-		// is already written so errors cannot be modified. ErrorHandler is only
-		// invoked after all retry attempts have been exhausted.
-		ErrorHandler func(c echo.Context, err error) error
-
-		// Rewrite defines URL path rewrite rules. The values captured in asterisk can be
-		// retrieved by index e.g. $1, $2 and so on.
-		// Examples:
-		// "/old":              "/new",
-		// "/api/*":            "/$1",
-		// "/js/*":             "/public/javascripts/$1",
-		// "/users/*/orders/*": "/user/$1/order/$2",
-		Rewrite map[string]string
-
-		// RegexRewrite defines rewrite rules using regexp.Rexexp with captures
-		// Every capture group in the values can be retrieved by index e.g. $1, $2 and so on.
-		// Example:
-		// "^/old/[0.9]+/":     "/new",
-		// "^/api/.+?/(.*)":    "/v2/$1",
-		RegexRewrite map[*regexp.Regexp]string
-
-		// Context key to store selected ProxyTarget into context.
-		// Optional. Default value "target".
-		ContextKey string
-
-		// To customize the transport to remote.
-		// Examples: If custom TLS certificates are required.
-		Transport http.RoundTripper
-
-		// ModifyResponse defines function to modify response from ProxyTarget.
-		ModifyResponse func(*http.Response) error
-	}
+// ProxyConfig defines the config for Proxy middleware.
+type ProxyConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
+
+	// Balancer defines a load balancing technique.
+	// Required.
+	Balancer ProxyBalancer
+
+	// RetryCount defines the number of times a failed proxied request should be retried
+	// using the next available ProxyTarget. Defaults to 0, meaning requests are never retried.
+	RetryCount int
+
+	// RetryFilter defines a function used to determine if a failed request to a
+	// ProxyTarget should be retried. The RetryFilter will only be called when the number
+	// of previous retries is less than RetryCount. If the function returns true, the
+	// request will be retried. The provided error indicates the reason for the request
+	// failure. When the ProxyTarget is unavailable, the error will be an instance of
+	// echo.HTTPError with a Code of http.StatusBadGateway. In all other cases, the error
+	// will indicate an internal error in the Proxy middleware. When a RetryFilter is not
+	// specified, all requests that fail with http.StatusBadGateway will be retried. A custom
+	// RetryFilter can be provided to only retry specific requests. Note that RetryFilter is
+	// only called when the request to the target fails, or an internal error in the Proxy
+	// middleware has occurred. Successful requests that return a non-200 response code cannot
+	// be retried.
+	RetryFilter func(c echo.Context, e error) bool
+
+	// ErrorHandler defines a function which can be used to return custom errors from
+	// the Proxy middleware. ErrorHandler is only invoked when there has been
+	// either an internal error in the Proxy middleware or the ProxyTarget is
+	// unavailable. Due to the way requests are proxied, ErrorHandler is not invoked
+	// when a ProxyTarget returns a non-200 response. In these cases, the response
+	// is already written so errors cannot be modified. ErrorHandler is only
+	// invoked after all retry attempts have been exhausted.
+	ErrorHandler func(c echo.Context, err error) error
+
+	// Rewrite defines URL path rewrite rules. The values captured in asterisk can be
+	// retrieved by index e.g. $1, $2 and so on.
+	// Examples:
+	// "/old":              "/new",
+	// "/api/*":            "/$1",
+	// "/js/*":             "/public/javascripts/$1",
+	// "/users/*/orders/*": "/user/$1/order/$2",
+	Rewrite map[string]string
+
+	// RegexRewrite defines rewrite rules using regexp.Rexexp with captures
+	// Every capture group in the values can be retrieved by index e.g. $1, $2 and so on.
+	// Example:
+	// "^/old/[0.9]+/":     "/new",
+	// "^/api/.+?/(.*)":    "/v2/$1",
+	RegexRewrite map[*regexp.Regexp]string
+
+	// Context key to store selected ProxyTarget into context.
+	// Optional. Default value "target".
+	ContextKey string
+
+	// To customize the transport to remote.
+	// Examples: If custom TLS certificates are required.
+	Transport http.RoundTripper
+
+	// ModifyResponse defines function to modify response from ProxyTarget.
+	ModifyResponse func(*http.Response) error
+}
 
-	// ProxyTarget defines the upstream target.
-	ProxyTarget struct {
-		Name string
-		URL  *url.URL
-		Meta echo.Map
-	}
+// ProxyTarget defines the upstream target.
+type ProxyTarget struct {
+	Name string
+	URL  *url.URL
+	Meta echo.Map
+}
 
-	// ProxyBalancer defines an interface to implement a load balancing technique.
-	ProxyBalancer interface {
-		AddTarget(*ProxyTarget) bool
-		RemoveTarget(string) bool
-		Next(echo.Context) *ProxyTarget
-	}
+// ProxyBalancer defines an interface to implement a load balancing technique.
+type ProxyBalancer interface {
+	AddTarget(*ProxyTarget) bool
+	RemoveTarget(string) bool
+	Next(echo.Context) *ProxyTarget
+}
 
-	// TargetProvider defines an interface that gives the opportunity for balancer
-	// to return custom errors when selecting target.
-	TargetProvider interface {
-		NextTarget(echo.Context) (*ProxyTarget, error)
-	}
+// TargetProvider defines an interface that gives the opportunity for balancer
+// to return custom errors when selecting target.
+type TargetProvider interface {
+	NextTarget(echo.Context) (*ProxyTarget, error)
+}
 
-	commonBalancer struct {
-		targets []*ProxyTarget
-		mutex   sync.Mutex
-	}
+type commonBalancer struct {
+	targets []*ProxyTarget
+	mutex   sync.Mutex
+}
 
-	// RandomBalancer implements a random load balancing technique.
-	randomBalancer struct {
-		commonBalancer
-		random *rand.Rand
-	}
+// RandomBalancer implements a random load balancing technique.
+type randomBalancer struct {
+	commonBalancer
+	random *rand.Rand
+}
 
-	// RoundRobinBalancer implements a round-robin load balancing technique.
-	roundRobinBalancer struct {
-		commonBalancer
-		// tracking the index on `targets` slice for the next `*ProxyTarget` to be used
-		i int
-	}
-)
+// RoundRobinBalancer implements a round-robin load balancing technique.
+type roundRobinBalancer struct {
+	commonBalancer
+	// tracking the index on `targets` slice for the next `*ProxyTarget` to be used
+	i int
+}
 
-var (
-	// DefaultProxyConfig is the default Proxy middleware config.
-	DefaultProxyConfig = ProxyConfig{
-		Skipper:    DefaultSkipper,
-		ContextKey: "target",
-	}
-)
+// DefaultProxyConfig is the default Proxy middleware config.
+var DefaultProxyConfig = ProxyConfig{
+	Skipper:    DefaultSkipper,
+	ContextKey: "target",
+}
 
 func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
diff --git a/middleware/rate_limiter.go b/middleware/rate_limiter.go
index 1d24df5..d4724fd 100644
--- a/middleware/rate_limiter.go
+++ b/middleware/rate_limiter.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -9,39 +12,34 @@ import (
 	"golang.org/x/time/rate"
 )
 
-type (
-	// RateLimiterStore is the interface to be implemented by custom stores.
-	RateLimiterStore interface {
-		// Stores for the rate limiter have to implement the Allow method
-		Allow(identifier string) (bool, error)
-	}
-)
+// RateLimiterStore is the interface to be implemented by custom stores.
+type RateLimiterStore interface {
+	// Stores for the rate limiter have to implement the Allow method
+	Allow(identifier string) (bool, error)
+}
 
-type (
-	// RateLimiterConfig defines the configuration for the rate limiter
-	RateLimiterConfig struct {
-		Skipper    Skipper
-		BeforeFunc BeforeFunc
-		// IdentifierExtractor uses echo.Context to extract the identifier for a visitor
-		IdentifierExtractor Extractor
-		// Store defines a store for the rate limiter
-		Store RateLimiterStore
-		// ErrorHandler provides a handler to be called when IdentifierExtractor returns an error
-		ErrorHandler func(context echo.Context, err error) error
-		// DenyHandler provides a handler to be called when RateLimiter denies access
-		DenyHandler func(context echo.Context, identifier string, err error) error
-	}
-	// Extractor is used to extract data from echo.Context
-	Extractor func(context echo.Context) (string, error)
-)
+// RateLimiterConfig defines the configuration for the rate limiter
+type RateLimiterConfig struct {
+	Skipper    Skipper
+	BeforeFunc BeforeFunc
+	// IdentifierExtractor uses echo.Context to extract the identifier for a visitor
+	IdentifierExtractor Extractor
+	// Store defines a store for the rate limiter
+	Store RateLimiterStore
+	// ErrorHandler provides a handler to be called when IdentifierExtractor returns an error
+	ErrorHandler func(context echo.Context, err error) error
+	// DenyHandler provides a handler to be called when RateLimiter denies access
+	DenyHandler func(context echo.Context, identifier string, err error) error
+}
 
-// errors
-var (
-	// ErrRateLimitExceeded denotes an error raised when rate limit is exceeded
-	ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded")
-	// ErrExtractorError denotes an error raised when extractor function is unsuccessful
-	ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier")
-)
+// Extractor is used to extract data from echo.Context
+type Extractor func(context echo.Context) (string, error)
+
+// ErrRateLimitExceeded denotes an error raised when rate limit is exceeded
+var ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded")
+
+// ErrExtractorError denotes an error raised when extractor function is unsuccessful
+var ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier")
 
 // DefaultRateLimiterConfig defines default values for RateLimiterConfig
 var DefaultRateLimiterConfig = RateLimiterConfig{
@@ -150,25 +148,24 @@ func RateLimiterWithConfig(config RateLimiterConfig) echo.MiddlewareFunc {
 	}
 }
 
-type (
-	// RateLimiterMemoryStore is the built-in store implementation for RateLimiter
-	RateLimiterMemoryStore struct {
-		visitors map[string]*Visitor
-		mutex    sync.Mutex
-		rate     rate.Limit // for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
+// RateLimiterMemoryStore is the built-in store implementation for RateLimiter
+type RateLimiterMemoryStore struct {
+	visitors map[string]*Visitor
+	mutex    sync.Mutex
+	rate     rate.Limit // for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
 
-		burst       int
-		expiresIn   time.Duration
-		lastCleanup time.Time
+	burst       int
+	expiresIn   time.Duration
+	lastCleanup time.Time
 
-		timeNow func() time.Time
-	}
-	// Visitor signifies a unique user's limiter details
-	Visitor struct {
-		*rate.Limiter
-		lastSeen time.Time
-	}
-)
+	timeNow func() time.Time
+}
+
+// Visitor signifies a unique user's limiter details
+type Visitor struct {
+	*rate.Limiter
+	lastSeen time.Time
+}
 
 /*
 NewRateLimiterMemoryStore returns an instance of RateLimiterMemoryStore with
diff --git a/middleware/recover.go b/middleware/recover.go
index 0466cfe..e6a5940 100644
--- a/middleware/recover.go
+++ b/middleware/recover.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -9,56 +12,52 @@ import (
 	"github.com/labstack/gommon/log"
 )
 
-type (
+// LogErrorFunc defines a function for custom logging in the middleware.
+type LogErrorFunc func(c echo.Context, err error, stack []byte) error
+
+// RecoverConfig defines the config for Recover middleware.
+type RecoverConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
+
+	// Size of the stack to be printed.
+	// Optional. Default value 4KB.
+	StackSize int `yaml:"stack_size"`
+
+	// DisableStackAll disables formatting stack traces of all other goroutines
+	// into buffer after the trace for the current goroutine.
+	// Optional. Default value false.
+	DisableStackAll bool `yaml:"disable_stack_all"`
+
+	// DisablePrintStack disables printing stack trace.
+	// Optional. Default value as false.
+	DisablePrintStack bool `yaml:"disable_print_stack"`
+
+	// LogLevel is log level to printing stack trace.
+	// Optional. Default value 0 (Print).
+	LogLevel log.Lvl
+
 	// LogErrorFunc defines a function for custom logging in the middleware.
-	LogErrorFunc func(c echo.Context, err error, stack []byte) error
-
-	// RecoverConfig defines the config for Recover middleware.
-	RecoverConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// Size of the stack to be printed.
-		// Optional. Default value 4KB.
-		StackSize int `yaml:"stack_size"`
-
-		// DisableStackAll disables formatting stack traces of all other goroutines
-		// into buffer after the trace for the current goroutine.
-		// Optional. Default value false.
-		DisableStackAll bool `yaml:"disable_stack_all"`
-
-		// DisablePrintStack disables printing stack trace.
-		// Optional. Default value as false.
-		DisablePrintStack bool `yaml:"disable_print_stack"`
-
-		// LogLevel is log level to printing stack trace.
-		// Optional. Default value 0 (Print).
-		LogLevel log.Lvl
-
-		// LogErrorFunc defines a function for custom logging in the middleware.
-		// If it's set you don't need to provide LogLevel for config.
-		// If this function returns nil, the centralized HTTPErrorHandler will not be called.
-		LogErrorFunc LogErrorFunc
-
-		// DisableErrorHandler disables the call to centralized HTTPErrorHandler.
-		// The recovered error is then passed back to upstream middleware, instead of swallowing the error.
-		// Optional. Default value false.
-		DisableErrorHandler bool `yaml:"disable_error_handler"`
-	}
-)
+	// If it's set you don't need to provide LogLevel for config.
+	// If this function returns nil, the centralized HTTPErrorHandler will not be called.
+	LogErrorFunc LogErrorFunc
+
+	// DisableErrorHandler disables the call to centralized HTTPErrorHandler.
+	// The recovered error is then passed back to upstream middleware, instead of swallowing the error.
+	// Optional. Default value false.
+	DisableErrorHandler bool `yaml:"disable_error_handler"`
+}
 
-var (
-	// DefaultRecoverConfig is the default Recover middleware config.
-	DefaultRecoverConfig = RecoverConfig{
-		Skipper:             DefaultSkipper,
-		StackSize:           4 << 10, // 4 KB
-		DisableStackAll:     false,
-		DisablePrintStack:   false,
-		LogLevel:            0,
-		LogErrorFunc:        nil,
-		DisableErrorHandler: false,
-	}
-)
+// DefaultRecoverConfig is the default Recover middleware config.
+var DefaultRecoverConfig = RecoverConfig{
+	Skipper:             DefaultSkipper,
+	StackSize:           4 << 10, // 4 KB
+	DisableStackAll:     false,
+	DisablePrintStack:   false,
+	LogLevel:            0,
+	LogErrorFunc:        nil,
+	DisableErrorHandler: false,
+}
 
 // Recover returns a middleware which recovers from panics anywhere in the chain
 // and handles the control to the centralized HTTPErrorHandler.
diff --git a/middleware/redirect.go b/middleware/redirect.go
index 13877db..b772ac1 100644
--- a/middleware/redirect.go
+++ b/middleware/redirect.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/request_id.go b/middleware/request_id.go
index e29c8f5..14bd4fd 100644
--- a/middleware/request_id.go
+++ b/middleware/request_id.go
@@ -1,35 +1,34 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// RequestIDConfig defines the config for RequestID middleware.
-	RequestIDConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
+// RequestIDConfig defines the config for RequestID middleware.
+type RequestIDConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
 
-		// Generator defines a function to generate an ID.
-		// Optional. Defaults to generator for random string of length 32.
-		Generator func() string
+	// Generator defines a function to generate an ID.
+	// Optional. Defaults to generator for random string of length 32.
+	Generator func() string
 
-		// RequestIDHandler defines a function which is executed for a request id.
-		RequestIDHandler func(echo.Context, string)
+	// RequestIDHandler defines a function which is executed for a request id.
+	RequestIDHandler func(echo.Context, string)
 
-		// TargetHeader defines what header to look for to populate the id
-		TargetHeader string
-	}
-)
+	// TargetHeader defines what header to look for to populate the id
+	TargetHeader string
+}
 
-var (
-	// DefaultRequestIDConfig is the default RequestID middleware config.
-	DefaultRequestIDConfig = RequestIDConfig{
-		Skipper:      DefaultSkipper,
-		Generator:    generator,
-		TargetHeader: echo.HeaderXRequestID,
-	}
-)
+// DefaultRequestIDConfig is the default RequestID middleware config.
+var DefaultRequestIDConfig = RequestIDConfig{
+	Skipper:      DefaultSkipper,
+	Generator:    generator,
+	TargetHeader: echo.HeaderXRequestID,
+}
 
 // RequestID returns a X-Request-ID middleware.
 func RequestID() echo.MiddlewareFunc {
diff --git a/middleware/request_logger.go b/middleware/request_logger.go
index f82f6b6..7c18200 100644
--- a/middleware/request_logger.go
+++ b/middleware/request_logger.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/responsecontroller_1.19.go b/middleware/responsecontroller_1.19.go
new file mode 100644
index 0000000..ddf6b64
--- /dev/null
+++ b/middleware/responsecontroller_1.19.go
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
+//go:build !go1.20
+
+package middleware
+
+import (
+	"bufio"
+	"fmt"
+	"net"
+	"net/http"
+)
+
+// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
+func responseControllerFlush(rw http.ResponseWriter) error {
+	for {
+		switch t := rw.(type) {
+		case interface{ FlushError() error }:
+			return t.FlushError()
+		case http.Flusher:
+			t.Flush()
+			return nil
+		case interface{ Unwrap() http.ResponseWriter }:
+			rw = t.Unwrap()
+		default:
+			return fmt.Errorf("%w", http.ErrNotSupported)
+		}
+	}
+}
+
+// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
+func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
+	for {
+		switch t := rw.(type) {
+		case http.Hijacker:
+			return t.Hijack()
+		case interface{ Unwrap() http.ResponseWriter }:
+			rw = t.Unwrap()
+		default:
+			return nil, nil, fmt.Errorf("%w", http.ErrNotSupported)
+		}
+	}
+}
diff --git a/middleware/responsecontroller_1.20.go b/middleware/responsecontroller_1.20.go
new file mode 100644
index 0000000..bc03059
--- /dev/null
+++ b/middleware/responsecontroller_1.20.go
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
+//go:build go1.20
+
+package middleware
+
+import (
+	"bufio"
+	"net"
+	"net/http"
+)
+
+func responseControllerFlush(rw http.ResponseWriter) error {
+	return http.NewResponseController(rw).Flush()
+}
+
+func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
+	return http.NewResponseController(rw).Hijack()
+}
diff --git a/middleware/rewrite.go b/middleware/rewrite.go
index 2090eac..4c19cc1 100644
--- a/middleware/rewrite.go
+++ b/middleware/rewrite.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -6,37 +9,33 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// RewriteConfig defines the config for Rewrite middleware.
-	RewriteConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
+// RewriteConfig defines the config for Rewrite middleware.
+type RewriteConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
 
-		// Rules defines the URL path rewrite rules. The values captured in asterisk can be
-		// retrieved by index e.g. $1, $2 and so on.
-		// Example:
-		// "/old":              "/new",
-		// "/api/*":            "/$1",
-		// "/js/*":             "/public/javascripts/$1",
-		// "/users/*/orders/*": "/user/$1/order/$2",
-		// Required.
-		Rules map[string]string `yaml:"rules"`
+	// Rules defines the URL path rewrite rules. The values captured in asterisk can be
+	// retrieved by index e.g. $1, $2 and so on.
+	// Example:
+	// "/old":              "/new",
+	// "/api/*":            "/$1",
+	// "/js/*":             "/public/javascripts/$1",
+	// "/users/*/orders/*": "/user/$1/order/$2",
+	// Required.
+	Rules map[string]string `yaml:"rules"`
 
-		// RegexRules defines the URL path rewrite rules using regexp.Rexexp with captures
-		// Every capture group in the values can be retrieved by index e.g. $1, $2 and so on.
-		// Example:
-		// "^/old/[0.9]+/":     "/new",
-		// "^/api/.+?/(.*)":     "/v2/$1",
-		RegexRules map[*regexp.Regexp]string `yaml:"-"`
-	}
-)
+	// RegexRules defines the URL path rewrite rules using regexp.Rexexp with captures
+	// Every capture group in the values can be retrieved by index e.g. $1, $2 and so on.
+	// Example:
+	// "^/old/[0.9]+/":     "/new",
+	// "^/api/.+?/(.*)":     "/v2/$1",
+	RegexRules map[*regexp.Regexp]string `yaml:"-"`
+}
 
-var (
-	// DefaultRewriteConfig is the default Rewrite middleware config.
-	DefaultRewriteConfig = RewriteConfig{
-		Skipper: DefaultSkipper,
-	}
-)
+// DefaultRewriteConfig is the default Rewrite middleware config.
+var DefaultRewriteConfig = RewriteConfig{
+	Skipper: DefaultSkipper,
+}
 
 // Rewrite returns a Rewrite middleware.
 //
diff --git a/middleware/secure.go b/middleware/secure.go
index 6c40517..c904abf 100644
--- a/middleware/secure.go
+++ b/middleware/secure.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -6,84 +9,80 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// SecureConfig defines the config for Secure middleware.
-	SecureConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// XSSProtection provides protection against cross-site scripting attack (XSS)
-		// by setting the `X-XSS-Protection` header.
-		// Optional. Default value "1; mode=block".
-		XSSProtection string `yaml:"xss_protection"`
-
-		// ContentTypeNosniff provides protection against overriding Content-Type
-		// header by setting the `X-Content-Type-Options` header.
-		// Optional. Default value "nosniff".
-		ContentTypeNosniff string `yaml:"content_type_nosniff"`
-
-		// XFrameOptions can be used to indicate whether or not a browser should
-		// be allowed to render a page in a <frame>, <iframe> or <object> .
-		// Sites can use this to avoid clickjacking attacks, by ensuring that their
-		// content is not embedded into other sites.provides protection against
-		// clickjacking.
-		// Optional. Default value "SAMEORIGIN".
-		// Possible values:
-		// - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
-		// - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so.
-		// - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin.
-		XFrameOptions string `yaml:"x_frame_options"`
-
-		// HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how
-		// long (in seconds) browsers should remember that this site is only to
-		// be accessed using HTTPS. This reduces your exposure to some SSL-stripping
-		// man-in-the-middle (MITM) attacks.
-		// Optional. Default value 0.
-		HSTSMaxAge int `yaml:"hsts_max_age"`
-
-		// HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security`
-		// header, excluding all subdomains from security policy. It has no effect
-		// unless HSTSMaxAge is set to a non-zero value.
-		// Optional. Default value false.
-		HSTSExcludeSubdomains bool `yaml:"hsts_exclude_subdomains"`
-
-		// ContentSecurityPolicy sets the `Content-Security-Policy` header providing
-		// security against cross-site scripting (XSS), clickjacking and other code
-		// injection attacks resulting from execution of malicious content in the
-		// trusted web page context.
-		// Optional. Default value "".
-		ContentSecurityPolicy string `yaml:"content_security_policy"`
-
-		// CSPReportOnly would use the `Content-Security-Policy-Report-Only` header instead
-		// of the `Content-Security-Policy` header. This allows iterative updates of the
-		// content security policy by only reporting the violations that would
-		// have occurred instead of blocking the resource.
-		// Optional. Default value false.
-		CSPReportOnly bool `yaml:"csp_report_only"`
-
-		// HSTSPreloadEnabled will add the preload tag in the `Strict Transport Security`
-		// header, which enables the domain to be included in the HSTS preload list
-		// maintained by Chrome (and used by Firefox and Safari): https://hstspreload.org/
-		// Optional.  Default value false.
-		HSTSPreloadEnabled bool `yaml:"hsts_preload_enabled"`
-
-		// ReferrerPolicy sets the `Referrer-Policy` header providing security against
-		// leaking potentially sensitive request paths to third parties.
-		// Optional. Default value "".
-		ReferrerPolicy string `yaml:"referrer_policy"`
-	}
-)
+// SecureConfig defines the config for Secure middleware.
+type SecureConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
+
+	// XSSProtection provides protection against cross-site scripting attack (XSS)
+	// by setting the `X-XSS-Protection` header.
+	// Optional. Default value "1; mode=block".
+	XSSProtection string `yaml:"xss_protection"`
+
+	// ContentTypeNosniff provides protection against overriding Content-Type
+	// header by setting the `X-Content-Type-Options` header.
+	// Optional. Default value "nosniff".
+	ContentTypeNosniff string `yaml:"content_type_nosniff"`
+
+	// XFrameOptions can be used to indicate whether or not a browser should
+	// be allowed to render a page in a <frame>, <iframe> or <object> .
+	// Sites can use this to avoid clickjacking attacks, by ensuring that their
+	// content is not embedded into other sites.provides protection against
+	// clickjacking.
+	// Optional. Default value "SAMEORIGIN".
+	// Possible values:
+	// - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
+	// - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so.
+	// - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin.
+	XFrameOptions string `yaml:"x_frame_options"`
+
+	// HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how
+	// long (in seconds) browsers should remember that this site is only to
+	// be accessed using HTTPS. This reduces your exposure to some SSL-stripping
+	// man-in-the-middle (MITM) attacks.
+	// Optional. Default value 0.
+	HSTSMaxAge int `yaml:"hsts_max_age"`
+
+	// HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security`
+	// header, excluding all subdomains from security policy. It has no effect
+	// unless HSTSMaxAge is set to a non-zero value.
+	// Optional. Default value false.
+	HSTSExcludeSubdomains bool `yaml:"hsts_exclude_subdomains"`
+
+	// ContentSecurityPolicy sets the `Content-Security-Policy` header providing
+	// security against cross-site scripting (XSS), clickjacking and other code
+	// injection attacks resulting from execution of malicious content in the
+	// trusted web page context.
+	// Optional. Default value "".
+	ContentSecurityPolicy string `yaml:"content_security_policy"`
+
+	// CSPReportOnly would use the `Content-Security-Policy-Report-Only` header instead
+	// of the `Content-Security-Policy` header. This allows iterative updates of the
+	// content security policy by only reporting the violations that would
+	// have occurred instead of blocking the resource.
+	// Optional. Default value false.
+	CSPReportOnly bool `yaml:"csp_report_only"`
+
+	// HSTSPreloadEnabled will add the preload tag in the `Strict Transport Security`
+	// header, which enables the domain to be included in the HSTS preload list
+	// maintained by Chrome (and used by Firefox and Safari): https://hstspreload.org/
+	// Optional.  Default value false.
+	HSTSPreloadEnabled bool `yaml:"hsts_preload_enabled"`
+
+	// ReferrerPolicy sets the `Referrer-Policy` header providing security against
+	// leaking potentially sensitive request paths to third parties.
+	// Optional. Default value "".
+	ReferrerPolicy string `yaml:"referrer_policy"`
+}
 
-var (
-	// DefaultSecureConfig is the default Secure middleware config.
-	DefaultSecureConfig = SecureConfig{
-		Skipper:            DefaultSkipper,
-		XSSProtection:      "1; mode=block",
-		ContentTypeNosniff: "nosniff",
-		XFrameOptions:      "SAMEORIGIN",
-		HSTSPreloadEnabled: false,
-	}
-)
+// DefaultSecureConfig is the default Secure middleware config.
+var DefaultSecureConfig = SecureConfig{
+	Skipper:            DefaultSkipper,
+	XSSProtection:      "1; mode=block",
+	ContentTypeNosniff: "nosniff",
+	XFrameOptions:      "SAMEORIGIN",
+	HSTSPreloadEnabled: false,
+}
 
 // Secure returns a Secure middleware.
 // Secure middleware provides protection against cross-site scripting (XSS) attack,
diff --git a/middleware/slash.go b/middleware/slash.go
index a3bf807..53b3052 100644
--- a/middleware/slash.go
+++ b/middleware/slash.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -6,24 +9,20 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-type (
-	// TrailingSlashConfig defines the config for TrailingSlash middleware.
-	TrailingSlashConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
+// TrailingSlashConfig defines the config for TrailingSlash middleware.
+type TrailingSlashConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
 
-		// Status code to be used when redirecting the request.
-		// Optional, but when provided the request is redirected using this code.
-		RedirectCode int `yaml:"redirect_code"`
-	}
-)
+	// Status code to be used when redirecting the request.
+	// Optional, but when provided the request is redirected using this code.
+	RedirectCode int `yaml:"redirect_code"`
+}
 
-var (
-	// DefaultTrailingSlashConfig is the default TrailingSlash middleware config.
-	DefaultTrailingSlashConfig = TrailingSlashConfig{
-		Skipper: DefaultSkipper,
-	}
-)
+// DefaultTrailingSlashConfig is the default TrailingSlash middleware config.
+var DefaultTrailingSlashConfig = TrailingSlashConfig{
+	Skipper: DefaultSkipper,
+}
 
 // AddTrailingSlash returns a root level (before router) middleware which adds a
 // trailing slash to the request `URL#Path`.
diff --git a/middleware/static.go b/middleware/static.go
index 24a5f59..1016f1b 100644
--- a/middleware/static.go
+++ b/middleware/static.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -14,40 +17,38 @@ import (
 	"github.com/labstack/gommon/bytes"
 )
 
-type (
-	// StaticConfig defines the config for Static middleware.
-	StaticConfig struct {
-		// Skipper defines a function to skip middleware.
-		Skipper Skipper
-
-		// Root directory from where the static content is served.
-		// Required.
-		Root string `yaml:"root"`
-
-		// Index file for serving a directory.
-		// Optional. Default value "index.html".
-		Index string `yaml:"index"`
-
-		// Enable HTML5 mode by forwarding all not-found requests to root so that
-		// SPA (single-page application) can handle the routing.
-		// Optional. Default value false.
-		HTML5 bool `yaml:"html5"`
-
-		// Enable directory browsing.
-		// Optional. Default value false.
-		Browse bool `yaml:"browse"`
-
-		// Enable ignoring of the base of the URL path.
-		// Example: when assigning a static middleware to a non root path group,
-		// the filesystem path is not doubled
-		// Optional. Default value false.
-		IgnoreBase bool `yaml:"ignoreBase"`
-
-		// Filesystem provides access to the static content.
-		// Optional. Defaults to http.Dir(config.Root)
-		Filesystem http.FileSystem `yaml:"-"`
-	}
-)
+// StaticConfig defines the config for Static middleware.
+type StaticConfig struct {
+	// Skipper defines a function to skip middleware.
+	Skipper Skipper
+
+	// Root directory from where the static content is served.
+	// Required.
+	Root string `yaml:"root"`
+
+	// Index file for serving a directory.
+	// Optional. Default value "index.html".
+	Index string `yaml:"index"`
+
+	// Enable HTML5 mode by forwarding all not-found requests to root so that
+	// SPA (single-page application) can handle the routing.
+	// Optional. Default value false.
+	HTML5 bool `yaml:"html5"`
+
+	// Enable directory browsing.
+	// Optional. Default value false.
+	Browse bool `yaml:"browse"`
+
+	// Enable ignoring of the base of the URL path.
+	// Example: when assigning a static middleware to a non root path group,
+	// the filesystem path is not doubled
+	// Optional. Default value false.
+	IgnoreBase bool `yaml:"ignoreBase"`
+
+	// Filesystem provides access to the static content.
+	// Optional. Defaults to http.Dir(config.Root)
+	Filesystem http.FileSystem `yaml:"-"`
+}
 
 const html = `
 <!DOCTYPE html>
@@ -121,13 +122,11 @@ const html = `
 </html>
 `
 
-var (
-	// DefaultStaticConfig is the default Static middleware config.
-	DefaultStaticConfig = StaticConfig{
-		Skipper: DefaultSkipper,
-		Index:   "index.html",
-	}
-)
+// DefaultStaticConfig is the default Static middleware config.
+var DefaultStaticConfig = StaticConfig{
+	Skipper: DefaultSkipper,
+	Index:   "index.html",
+}
 
 // Static returns a Static middleware to serves static content from the provided
 // root directory.
diff --git a/middleware/static_other.go b/middleware/static_other.go
index 0337b22..35dbfb3 100644
--- a/middleware/static_other.go
+++ b/middleware/static_other.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 //go:build !windows
 
 package middleware
diff --git a/middleware/static_windows.go b/middleware/static_windows.go
index 0ab1198..e294020 100644
--- a/middleware/static_windows.go
+++ b/middleware/static_windows.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/timeout.go b/middleware/timeout.go
index 4e8836c..c2aebef 100644
--- a/middleware/timeout.go
+++ b/middleware/timeout.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -77,14 +80,12 @@ type TimeoutConfig struct {
 	Timeout time.Duration
 }
 
-var (
-	// DefaultTimeoutConfig is the default Timeout middleware config.
-	DefaultTimeoutConfig = TimeoutConfig{
-		Skipper:      DefaultSkipper,
-		Timeout:      0,
-		ErrorMessage: "",
-	}
-)
+// DefaultTimeoutConfig is the default Timeout middleware config.
+var DefaultTimeoutConfig = TimeoutConfig{
+	Skipper:      DefaultSkipper,
+	Timeout:      0,
+	ErrorMessage: "",
+}
 
 // Timeout returns a middleware which returns error (503 Service Unavailable error) to client immediately when handler
 // call runs for longer than its time limit. NB: timeout does not stop handler execution.
diff --git a/middleware/util.go b/middleware/util.go
index 4d2d172..09428eb 100644
--- a/middleware/util.go
+++ b/middleware/util.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/response.go b/response.go
index d9c9aa6..a795ce3 100644
--- a/response.go
+++ b/response.go
@@ -1,25 +1,27 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
 	"bufio"
+	"errors"
 	"net"
 	"net/http"
 )
 
-type (
-	// Response wraps an http.ResponseWriter and implements its interface to be used
-	// by an HTTP handler to construct an HTTP response.
-	// See: https://golang.org/pkg/net/http/#ResponseWriter
-	Response struct {
-		echo        *Echo
-		beforeFuncs []func()
-		afterFuncs  []func()
-		Writer      http.ResponseWriter
-		Status      int
-		Size        int64
-		Committed   bool
-	}
-)
+// Response wraps an http.ResponseWriter and implements its interface to be used
+// by an HTTP handler to construct an HTTP response.
+// See: https://golang.org/pkg/net/http/#ResponseWriter
+type Response struct {
+	echo        *Echo
+	beforeFuncs []func()
+	afterFuncs  []func()
+	Writer      http.ResponseWriter
+	Status      int
+	Size        int64
+	Committed   bool
+}
 
 // NewResponse creates a new instance of Response.
 func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
@@ -84,14 +86,17 @@ func (r *Response) Write(b []byte) (n int, err error) {
 // buffered data to the client.
 // See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
 func (r *Response) Flush() {
-	r.Writer.(http.Flusher).Flush()
+	err := responseControllerFlush(r.Writer)
+	if err != nil && errors.Is(err, http.ErrNotSupported) {
+		panic(errors.New("response writer flushing is not supported"))
+	}
 }
 
 // Hijack implements the http.Hijacker interface to allow an HTTP handler to
 // take over the connection.
 // See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
 func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
-	return r.Writer.(http.Hijacker).Hijack()
+	return responseControllerHijack(r.Writer)
 }
 
 // Unwrap returns the original http.ResponseWriter.
diff --git a/responsecontroller_1.19.go b/responsecontroller_1.19.go
new file mode 100644
index 0000000..782dab3
--- /dev/null
+++ b/responsecontroller_1.19.go
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
+//go:build !go1.20
+
+package echo
+
+import (
+	"bufio"
+	"fmt"
+	"net"
+	"net/http"
+)
+
+// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
+func responseControllerFlush(rw http.ResponseWriter) error {
+	for {
+		switch t := rw.(type) {
+		case interface{ FlushError() error }:
+			return t.FlushError()
+		case http.Flusher:
+			t.Flush()
+			return nil
+		case interface{ Unwrap() http.ResponseWriter }:
+			rw = t.Unwrap()
+		default:
+			return fmt.Errorf("%w", http.ErrNotSupported)
+		}
+	}
+}
+
+// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
+func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
+	for {
+		switch t := rw.(type) {
+		case http.Hijacker:
+			return t.Hijack()
+		case interface{ Unwrap() http.ResponseWriter }:
+			rw = t.Unwrap()
+		default:
+			return nil, nil, fmt.Errorf("%w", http.ErrNotSupported)
+		}
+	}
+}
diff --git a/responsecontroller_1.20.go b/responsecontroller_1.20.go
new file mode 100644
index 0000000..6d77c07
--- /dev/null
+++ b/responsecontroller_1.20.go
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
+//go:build go1.20
+
+package echo
+
+import (
+	"bufio"
+	"net"
+	"net/http"
+)
+
+func responseControllerFlush(rw http.ResponseWriter) error {
+	return http.NewResponseController(rw).Flush()
+}
+
+func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
+	return http.NewResponseController(rw).Hijack()
+}
diff --git a/router.go b/router.go
index ee6f3fa..a3e258e 100644
--- a/router.go
+++ b/router.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
@@ -6,56 +9,58 @@ import (
 	"net/http"
 )
 
-type (
-	// Router is the registry of all registered routes for an `Echo` instance for
-	// request matching and URL path parameter parsing.
-	Router struct {
-		tree   *node
-		routes map[string]*Route
-		echo   *Echo
-	}
-	node struct {
-		kind           kind
-		label          byte
-		prefix         string
-		parent         *node
-		staticChildren children
-		originalPath   string
-		methods        *routeMethods
-		paramChild     *node
-		anyChild       *node
-		paramsCount    int
-		// isLeaf indicates that node does not have child routes
-		isLeaf bool
-		// isHandler indicates that node has at least one handler registered to it
-		isHandler bool
-
-		// notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
-		notFoundHandler *routeMethod
-	}
-	kind        uint8
-	children    []*node
-	routeMethod struct {
-		ppath   string
-		pnames  []string
-		handler HandlerFunc
-	}
-	routeMethods struct {
-		connect     *routeMethod
-		delete      *routeMethod
-		get         *routeMethod
-		head        *routeMethod
-		options     *routeMethod
-		patch       *routeMethod
-		post        *routeMethod
-		propfind    *routeMethod
-		put         *routeMethod
-		trace       *routeMethod
-		report      *routeMethod
-		anyOther    map[string]*routeMethod
-		allowHeader string
-	}
-)
+// Router is the registry of all registered routes for an `Echo` instance for
+// request matching and URL path parameter parsing.
+type Router struct {
+	tree   *node
+	routes map[string]*Route
+	echo   *Echo
+}
+
+type node struct {
+	kind           kind
+	label          byte
+	prefix         string
+	parent         *node
+	staticChildren children
+	originalPath   string
+	methods        *routeMethods
+	paramChild     *node
+	anyChild       *node
+	paramsCount    int
+	// isLeaf indicates that node does not have child routes
+	isLeaf bool
+	// isHandler indicates that node has at least one handler registered to it
+	isHandler bool
+
+	// notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
+	notFoundHandler *routeMethod
+}
+
+type kind uint8
+type children []*node
+
+type routeMethod struct {
+	ppath   string
+	pnames  []string
+	handler HandlerFunc
+}
+
+type routeMethods struct {
+	connect     *routeMethod
+	delete      *routeMethod
+	get         *routeMethod
+	head        *routeMethod
+	options     *routeMethod
+	patch       *routeMethod
+	post        *routeMethod
+	propfind    *routeMethod
+	put         *routeMethod
+	trace       *routeMethod
+	report      *routeMethod
+	anyOther    map[string]*routeMethod
+	allowHeader string
+}
 
 const (
 	staticKind kind = iota


Test patch:
diff --git a/bind_test.go b/bind_test.go
index c35283d..05f8ef4 100644
--- a/bind_test.go
+++ b/bind_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
@@ -19,91 +22,91 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-type (
-	bindTestStruct struct {
-		I           int
-		PtrI        *int
-		I8          int8
-		PtrI8       *int8
-		I16         int16
-		PtrI16      *int16
-		I32         int32
-		PtrI32      *int32
-		I64         int64
-		PtrI64      *int64
-		UI          uint
-		PtrUI       *uint
-		UI8         uint8
-		PtrUI8      *uint8
-		UI16        uint16
-		PtrUI16     *uint16
-		UI32        uint32
-		PtrUI32     *uint32
-		UI64        uint64
-		PtrUI64     *uint64
-		B           bool
-		PtrB        *bool
-		F32         float32
-		PtrF32      *float32
-		F64         float64
-		PtrF64      *float64
-		S           string
-		PtrS        *string
-		cantSet     string
-		DoesntExist string
-		GoT         time.Time
-		GoTptr      *time.Time
-		T           Timestamp
-		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
-	Struct      struct {
-		Foo string
-	}
-	Bar struct {
-		Baz int `json:"baz" query:"baz"`
-	}
-)
+type bindTestStruct struct {
+	I           int
+	PtrI        *int
+	I8          int8
+	PtrI8       *int8
+	I16         int16
+	PtrI16      *int16
+	I32         int32
+	PtrI32      *int32
+	I64         int64
+	PtrI64      *int64
+	UI          uint
+	PtrUI       *uint
+	UI8         uint8
+	PtrUI8      *uint8
+	UI16        uint16
+	PtrUI16     *uint16
+	UI32        uint32
+	PtrUI32     *uint32
+	UI64        uint64
+	PtrUI64     *uint64
+	B           bool
+	PtrB        *bool
+	F32         float32
+	PtrF32      *float32
+	F64         float64
+	PtrF64      *float64
+	S           string
+	PtrS        *string
+	cantSet     string
+	DoesntExist string
+	GoT         time.Time
+	GoTptr      *time.Time
+	T           Timestamp
+	Tptr        *Timestamp
+	SA          StringArray
+}
+
+type 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"`
+}
+
+type Timestamp time.Time
+type TA []Timestamp
+type StringArray []string
+type Struct struct {
+	Foo string
+}
+type Bar struct {
+	Baz int `json:"baz" query:"baz"`
+}
 
 func (t *Timestamp) UnmarshalParam(src string) error {
 	ts, err := time.Parse(time.RFC3339, src)
@@ -429,6 +432,109 @@ func TestBindUnsupportedMediaType(t *testing.T) {
 	testBindError(t, strings.NewReader(invalidContent), MIMEApplicationJSON, &json.SyntaxError{})
 }
 
+func TestDefaultBinder_bindDataToMap(t *testing.T) {
+	exampleData := map[string][]string{
+		"multiple": {"1", "2"},
+		"single":   {"3"},
+	}
+
+	t.Run("ok, bind to map[string]string", func(t *testing.T) {
+		dest := map[string]string{}
+		assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
+		assert.Equal(t,
+			map[string]string{
+				"multiple": "1",
+				"single":   "3",
+			},
+			dest,
+		)
+	})
+
+	t.Run("ok, bind to map[string]string with nil map", func(t *testing.T) {
+		var dest map[string]string
+		assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
+		assert.Equal(t,
+			map[string]string{
+				"multiple": "1",
+				"single":   "3",
+			},
+			dest,
+		)
+	})
+
+	t.Run("ok, bind to map[string][]string", func(t *testing.T) {
+		dest := map[string][]string{}
+		assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
+		assert.Equal(t,
+			map[string][]string{
+				"multiple": {"1", "2"},
+				"single":   {"3"},
+			},
+			dest,
+		)
+	})
+
+	t.Run("ok, bind to map[string][]string with nil map", func(t *testing.T) {
+		var dest map[string][]string
+		assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
+		assert.Equal(t,
+			map[string][]string{
+				"multiple": {"1", "2"},
+				"single":   {"3"},
+			},
+			dest,
+		)
+	})
+
+	t.Run("ok, bind to map[string]interface", func(t *testing.T) {
+		dest := map[string]interface{}{}
+		assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
+		assert.Equal(t,
+			map[string]interface{}{
+				"multiple": []string{"1", "2"},
+				"single":   []string{"3"},
+			},
+			dest,
+		)
+	})
+
+	t.Run("ok, bind to map[string]interface with nil map", func(t *testing.T) {
+		var dest map[string]interface{}
+		assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
+		assert.Equal(t,
+			map[string]interface{}{
+				"multiple": []string{"1", "2"},
+				"single":   []string{"3"},
+			},
+			dest,
+		)
+	})
+
+	t.Run("ok, bind to map[string]int skips", func(t *testing.T) {
+		dest := map[string]int{}
+		assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
+		assert.Equal(t, map[string]int{}, dest)
+	})
+
+	t.Run("ok, bind to map[string]int skips with nil map", func(t *testing.T) {
+		var dest map[string]int
+		assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
+		assert.Equal(t, map[string]int(nil), dest)
+	})
+
+	t.Run("ok, bind to map[string][]int skips", func(t *testing.T) {
+		dest := map[string][]int{}
+		assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
+		assert.Equal(t, map[string][]int{}, dest)
+	})
+
+	t.Run("ok, bind to map[string][]int skips with nil map", func(t *testing.T) {
+		var dest map[string][]int
+		assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
+		assert.Equal(t, map[string][]int(nil), dest)
+	})
+}
+
 func TestBindbindData(t *testing.T) {
 	ts := new(bindTestStruct)
 	b := new(DefaultBinder)
diff --git a/binder_external_test.go b/binder_external_test.go
index f1aecb5..e44055a 100644
--- a/binder_external_test.go
+++ b/binder_external_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 // run tests as external package to get real feel for API
 package echo_test
 
diff --git a/binder_test.go b/binder_test.go
index 0b27cae..d552b60 100644
--- a/binder_test.go
+++ b/binder_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/context_fs_test.go b/context_fs_test.go
index 51346c9..83232ea 100644
--- a/context_fs_test.go
+++ b/context_fs_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/context_test.go b/context_test.go
index 01a8784..e5c4a21 100644
--- a/context_test.go
+++ b/context_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
@@ -22,11 +25,9 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-type (
-	Template struct {
-		templates *template.Template
-	}
-)
+type Template struct {
+	templates *template.Template
+}
 
 var testUser = user{1, "Jon Snow"}
 
@@ -154,7 +155,7 @@ func TestContextJSON(t *testing.T) {
 	err := c.JSON(http.StatusOK, user{1, "Jon Snow"})
 	if assert.NoError(t, err) {
 		assert.Equal(t, http.StatusOK, rec.Code)
-		assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType))
+		assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType))
 		assert.Equal(t, userJSON+"\n", rec.Body.String())
 	}
 }
@@ -178,7 +179,7 @@ func TestContextJSONPrettyURL(t *testing.T) {
 	err := c.JSON(http.StatusOK, user{1, "Jon Snow"})
 	if assert.NoError(t, err) {
 		assert.Equal(t, http.StatusOK, rec.Code)
-		assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType))
+		assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType))
 		assert.Equal(t, userJSONPretty+"\n", rec.Body.String())
 	}
 }
@@ -192,7 +193,7 @@ func TestContextJSONPretty(t *testing.T) {
 	err := c.JSONPretty(http.StatusOK, user{1, "Jon Snow"}, "  ")
 	if assert.NoError(t, err) {
 		assert.Equal(t, http.StatusOK, rec.Code)
-		assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType))
+		assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType))
 		assert.Equal(t, userJSONPretty+"\n", rec.Body.String())
 	}
 }
@@ -213,7 +214,7 @@ func TestContextJSONWithEmptyIntent(t *testing.T) {
 	err := c.json(http.StatusOK, user{1, "Jon Snow"}, emptyIndent)
 	if assert.NoError(t, err) {
 		assert.Equal(t, http.StatusOK, rec.Code)
-		assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType))
+		assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType))
 		assert.Equal(t, buf.String(), rec.Body.String())
 	}
 }
@@ -244,7 +245,7 @@ func TestContextJSONBlob(t *testing.T) {
 	err = c.JSONBlob(http.StatusOK, data)
 	if assert.NoError(t, err) {
 		assert.Equal(t, http.StatusOK, rec.Code)
-		assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType))
+		assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType))
 		assert.Equal(t, userJSON, rec.Body.String())
 	}
 }
@@ -533,7 +534,7 @@ func TestContext_JSON_CommitsCustomResponseCode(t *testing.T) {
 
 	if assert.NoError(t, err) {
 		assert.Equal(t, http.StatusCreated, rec.Code)
-		assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType))
+		assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType))
 		assert.Equal(t, userJSON+"\n", rec.Body.String())
 	}
 }
diff --git a/echo_fs_test.go b/echo_fs_test.go
index eb072a2..e882a06 100644
--- a/echo_fs_test.go
+++ b/echo_fs_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/echo_test.go b/echo_test.go
index a352e40..57c257b 100644
--- a/echo_test.go
+++ b/echo_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
@@ -22,12 +25,10 @@ import (
 	"golang.org/x/net/http2"
 )
 
-type (
-	user struct {
-		ID   int    `json:"id" xml:"id" form:"id" query:"id" param:"id" header:"id"`
-		Name string `json:"name" xml:"name" form:"name" query:"name" param:"name" header:"name"`
-	}
-)
+type user struct {
+	ID   int    `json:"id" xml:"id" form:"id" query:"id" param:"id" header:"id"`
+	Name string `json:"name" xml:"name" form:"name" query:"name" param:"name" header:"name"`
+}
 
 const (
 	userJSON                    = `{"id":1,"name":"Jon Snow"}`
@@ -1572,7 +1573,7 @@ func TestEcho_OnAddRouteHandler(t *testing.T) {
 		})
 	}
 
-	e.GET("/static", NotFoundHandler)
+	e.GET("/static", dummyHandler)
 	e.Host("domain.site").GET("/static/*", dummyHandler, func(next HandlerFunc) HandlerFunc {
 		return func(c Context) error {
 			return next(c)
@@ -1582,7 +1583,7 @@ func TestEcho_OnAddRouteHandler(t *testing.T) {
 	assert.Len(t, added, 2)
 
 	assert.Equal(t, "", added[0].host)
-	assert.Equal(t, Route{Method: http.MethodGet, Path: "/static", Name: "github.com/labstack/echo/v4.glob..func1"}, added[0].route)
+	assert.Equal(t, Route{Method: http.MethodGet, Path: "/static", Name: "github.com/labstack/echo/v4.TestEcho_OnAddRouteHandler.func1"}, added[0].route)
 	assert.Len(t, added[0].middleware, 0)
 
 	assert.Equal(t, "domain.site", added[1].host)
diff --git a/group_fs_test.go b/group_fs_test.go
index 958d9ef..8bcd547 100644
--- a/group_fs_test.go
+++ b/group_fs_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/group_test.go b/group_test.go
index d22f564..a973714 100644
--- a/group_test.go
+++ b/group_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/ip_test.go b/ip_test.go
index 38c4a1c..cf26e04 100644
--- a/ip_test.go
+++ b/ip_test.go
@@ -1,10 +1,14 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
-	"github.com/stretchr/testify/assert"
 	"net"
 	"net/http"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func mustParseCIDR(s string) *net.IPNet {
@@ -458,7 +462,7 @@ func TestExtractIPDirect(t *testing.T) {
 }
 
 func TestExtractIPFromRealIPHeader(t *testing.T) {
-	_, ipForRemoteAddrExternalRange, _ := net.ParseCIDR("203.0.113.199/24")
+	_, ipForRemoteAddrExternalRange, _ := net.ParseCIDR("203.0.113.0/24")
 	_, ipv6ForRemoteAddrExternalRange, _ := net.ParseCIDR("2001:db8::/64")
 
 	var testCases = []struct {
@@ -486,36 +490,42 @@ func TestExtractIPFromRealIPHeader(t *testing.T) {
 		},
 		{
 			name: "request is from external IP has valid + UNTRUSTED external X-Real-Ip header, extract IP from remote addr",
+			givenTrustOptions: []TrustOption{ // case for "trust direct-facing proxy"
+				TrustIPRange(ipForRemoteAddrExternalRange), // we trust external IP range "203.0.113.199/24"
+			},
 			whenRequest: http.Request{
 				Header: http.Header{
-					HeaderXRealIP: []string{"203.0.113.199"}, // <-- this is untrusted
+					HeaderXRealIP: []string{"203.0.113.199"},
 				},
-				RemoteAddr: "203.0.113.1:8080",
+				RemoteAddr: "8.8.8.8:8080", // <-- this is untrusted
 			},
-			expectIP: "203.0.113.1",
+			expectIP: "8.8.8.8",
 		},
 		{
 			name: "request is from external IP has valid + UNTRUSTED external X-Real-Ip header, extract IP from remote addr",
+			givenTrustOptions: []TrustOption{ // case for "trust direct-facing proxy"
+				TrustIPRange(ipv6ForRemoteAddrExternalRange), // we trust external IP range "203.0.113.199/24"
+			},
 			whenRequest: http.Request{
 				Header: http.Header{
-					HeaderXRealIP: []string{"[2001:db8::113:199]"}, // <-- this is untrusted
+					HeaderXRealIP: []string{"[bc01:1010::9090:1888]"},
 				},
-				RemoteAddr: "[2001:db8::113:1]:8080",
+				RemoteAddr: "[fe64:aa10::1]:8080", // <-- this is untrusted
 			},
-			expectIP: "2001:db8::113:1",
+			expectIP: "fe64:aa10::1",
 		},
 		{
 			name: "request is from external IP has valid + TRUSTED X-Real-Ip header, extract IP from X-Real-Ip header",
 			givenTrustOptions: []TrustOption{ // case for "trust direct-facing proxy"
-				TrustIPRange(ipForRemoteAddrExternalRange), // we trust external IP range "203.0.113.199/24"
+				TrustIPRange(ipForRemoteAddrExternalRange), // we trust external IP range "203.0.113.0/24"
 			},
 			whenRequest: http.Request{
 				Header: http.Header{
-					HeaderXRealIP: []string{"203.0.113.199"},
+					HeaderXRealIP: []string{"8.8.8.8"},
 				},
 				RemoteAddr: "203.0.113.1:8080",
 			},
-			expectIP: "203.0.113.199",
+			expectIP: "8.8.8.8",
 		},
 		{
 			name: "request is from external IP has valid + TRUSTED X-Real-Ip header, extract IP from X-Real-Ip header",
@@ -524,11 +534,11 @@ func TestExtractIPFromRealIPHeader(t *testing.T) {
 			},
 			whenRequest: http.Request{
 				Header: http.Header{
-					HeaderXRealIP: []string{"[2001:db8::113:199]"},
+					HeaderXRealIP: []string{"[fe64:db8::113:199]"},
 				},
 				RemoteAddr: "[2001:db8::113:1]:8080",
 			},
-			expectIP: "2001:db8::113:199",
+			expectIP: "fe64:db8::113:199",
 		},
 		{
 			name: "request is from external IP has XFF and valid + TRUSTED X-Real-Ip header, extract IP from X-Real-Ip header",
@@ -537,12 +547,12 @@ func TestExtractIPFromRealIPHeader(t *testing.T) {
 			},
 			whenRequest: http.Request{
 				Header: http.Header{
-					HeaderXRealIP:       []string{"203.0.113.199"},
-					HeaderXForwardedFor: []string{"203.0.113.198, 203.0.113.197"}, // <-- should not affect anything
+					HeaderXRealIP:       []string{"8.8.8.8"},
+					HeaderXForwardedFor: []string{"1.1.1.1 ,8.8.8.8"}, // <-- should not affect anything
 				},
 				RemoteAddr: "203.0.113.1:8080",
 			},
-			expectIP: "203.0.113.199",
+			expectIP: "8.8.8.8",
 		},
 		{
 			name: "request is from external IP has XFF and valid + TRUSTED X-Real-Ip header, extract IP from X-Real-Ip header",
@@ -551,12 +561,12 @@ func TestExtractIPFromRealIPHeader(t *testing.T) {
 			},
 			whenRequest: http.Request{
 				Header: http.Header{
-					HeaderXRealIP:       []string{"[2001:db8::113:199]"},
-					HeaderXForwardedFor: []string{"[2001:db8::113:198], [2001:db8::113:197]"}, // <-- should not affect anything
+					HeaderXRealIP:       []string{"[fe64:db8::113:199]"},
+					HeaderXForwardedFor: []string{"[feab:cde9::113:198], [fe64:db8::113:199]"}, // <-- should not affect anything
 				},
 				RemoteAddr: "[2001:db8::113:1]:8080",
 			},
-			expectIP: "2001:db8::113:199",
+			expectIP: "fe64:db8::113:199",
 		},
 	}
 
diff --git a/json_test.go b/json_test.go
index 8fb9ebc..0b15ed1 100644
--- a/json_test.go
+++ b/json_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
diff --git a/middleware/basic_auth_test.go b/middleware/basic_auth_test.go
index 20e7692..6e07065 100644
--- a/middleware/basic_auth_test.go
+++ b/middleware/basic_auth_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -32,7 +35,6 @@ func TestBasicAuth(t *testing.T) {
 	assert.NoError(t, h(c))
 
 	h = BasicAuthWithConfig(BasicAuthConfig{
-		Skipper:   nil,
 		Validator: f,
 		Realm:     "someRealm",
 	})(func(c echo.Context) error {
@@ -72,4 +74,20 @@ func TestBasicAuth(t *testing.T) {
 	req.Header.Set(echo.HeaderAuthorization, auth)
 	he = h(c).(*echo.HTTPError)
 	assert.Equal(t, http.StatusUnauthorized, he.Code)
+
+	h = BasicAuthWithConfig(BasicAuthConfig{
+		Validator: f,
+		Realm:     "someRealm",
+		Skipper: func(c echo.Context) bool {
+			return true
+		},
+	})(func(c echo.Context) error {
+		return c.String(http.StatusOK, "test")
+	})
+
+	// Skipped Request
+	auth = basic + " " + base64.StdEncoding.EncodeToString([]byte("joe:skip"))
+	req.Header.Set(echo.HeaderAuthorization, auth)
+	assert.NoError(t, h(c))
+
 }
diff --git a/middleware/body_dump_test.go b/middleware/body_dump_test.go
index de1de33..e880af4 100644
--- a/middleware/body_dump_test.go
+++ b/middleware/body_dump_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -87,3 +90,53 @@ func TestBodyDumpFails(t *testing.T) {
 		}
 	})
 }
+
+func TestBodyDumpResponseWriter_CanNotFlush(t *testing.T) {
+	bdrw := bodyDumpResponseWriter{
+		ResponseWriter: new(testResponseWriterNoFlushHijack), // this RW does not support flush
+	}
+
+	assert.PanicsWithError(t, "response writer flushing is not supported", func() {
+		bdrw.Flush()
+	})
+}
+
+func TestBodyDumpResponseWriter_CanFlush(t *testing.T) {
+	trwu := testResponseWriterUnwrapperHijack{testResponseWriterUnwrapper: testResponseWriterUnwrapper{rw: httptest.NewRecorder()}}
+	bdrw := bodyDumpResponseWriter{
+		ResponseWriter: &trwu,
+	}
+
+	bdrw.Flush()
+	assert.Equal(t, 1, trwu.unwrapCalled)
+}
+
+func TestBodyDumpResponseWriter_CanUnwrap(t *testing.T) {
+	trwu := &testResponseWriterUnwrapper{rw: httptest.NewRecorder()}
+	bdrw := bodyDumpResponseWriter{
+		ResponseWriter: trwu,
+	}
+
+	result := bdrw.Unwrap()
+	assert.Equal(t, trwu, result)
+}
+
+func TestBodyDumpResponseWriter_CanHijack(t *testing.T) {
+	trwu := testResponseWriterUnwrapperHijack{testResponseWriterUnwrapper: testResponseWriterUnwrapper{rw: httptest.NewRecorder()}}
+	bdrw := bodyDumpResponseWriter{
+		ResponseWriter: &trwu, // this RW supports hijacking through unwrapping
+	}
+
+	_, _, err := bdrw.Hijack()
+	assert.EqualError(t, err, "can hijack")
+}
+
+func TestBodyDumpResponseWriter_CanNotHijack(t *testing.T) {
+	trwu := testResponseWriterUnwrapper{rw: httptest.NewRecorder()}
+	bdrw := bodyDumpResponseWriter{
+		ResponseWriter: &trwu, // this RW supports hijacking through unwrapping
+	}
+
+	_, _, err := bdrw.Hijack()
+	assert.EqualError(t, err, "feature not supported")
+}
diff --git a/middleware/body_limit_test.go b/middleware/body_limit_test.go
index 0fd66ee..d14c2b6 100644
--- a/middleware/body_limit_test.go
+++ b/middleware/body_limit_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/compress_test.go b/middleware/compress_test.go
index 0ed16c8..4bbdfdb 100644
--- a/middleware/compress_test.go
+++ b/middleware/compress_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -311,6 +314,36 @@ func TestGzipWithStatic(t *testing.T) {
 	}
 }
 
+func TestGzipResponseWriter_CanUnwrap(t *testing.T) {
+	trwu := &testResponseWriterUnwrapper{rw: httptest.NewRecorder()}
+	bdrw := gzipResponseWriter{
+		ResponseWriter: trwu,
+	}
+
+	result := bdrw.Unwrap()
+	assert.Equal(t, trwu, result)
+}
+
+func TestGzipResponseWriter_CanHijack(t *testing.T) {
+	trwu := testResponseWriterUnwrapperHijack{testResponseWriterUnwrapper: testResponseWriterUnwrapper{rw: httptest.NewRecorder()}}
+	bdrw := gzipResponseWriter{
+		ResponseWriter: &trwu, // this RW supports hijacking through unwrapping
+	}
+
+	_, _, err := bdrw.Hijack()
+	assert.EqualError(t, err, "can hijack")
+}
+
+func TestGzipResponseWriter_CanNotHijack(t *testing.T) {
+	trwu := testResponseWriterUnwrapper{rw: httptest.NewRecorder()}
+	bdrw := gzipResponseWriter{
+		ResponseWriter: &trwu, // this RW supports hijacking through unwrapping
+	}
+
+	_, _, err := bdrw.Hijack()
+	assert.EqualError(t, err, "feature not supported")
+}
+
 func BenchmarkGzip(b *testing.B) {
 	e := echo.New()
 
diff --git a/middleware/context_timeout_test.go b/middleware/context_timeout_test.go
index 24c6203..e69bcd2 100644
--- a/middleware/context_timeout_test.go
+++ b/middleware/context_timeout_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/cors_test.go b/middleware/cors_test.go
index 797600c..64e5c65 100644
--- a/middleware/cors_test.go
+++ b/middleware/cors_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/csrf_test.go b/middleware/csrf_test.go
index 6b20297..98e5d04 100644
--- a/middleware/csrf_test.go
+++ b/middleware/csrf_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/decompress_test.go b/middleware/decompress_test.go
index 2e73ba8..63b1a68 100644
--- a/middleware/decompress_test.go
+++ b/middleware/decompress_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -131,7 +134,7 @@ func TestDecompressSkipper(t *testing.T) {
 	rec := httptest.NewRecorder()
 	c := e.NewContext(req, rec)
 	e.ServeHTTP(rec, req)
-	assert.Equal(t, rec.Header().Get(echo.HeaderContentType), echo.MIMEApplicationJSONCharsetUTF8)
+	assert.Equal(t, rec.Header().Get(echo.HeaderContentType), echo.MIMEApplicationJSON)
 	reqBody, err := io.ReadAll(c.Request().Body)
 	assert.NoError(t, err)
 	assert.Equal(t, body, string(reqBody))
diff --git a/middleware/extractor_test.go b/middleware/extractor_test.go
index 428c556..42cbcfe 100644
--- a/middleware/extractor_test.go
+++ b/middleware/extractor_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/jwt_test.go b/middleware/jwt_test.go
index 90e8cad..bbe4b88 100644
--- a/middleware/jwt_test.go
+++ b/middleware/jwt_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 //go:build go1.15
 // +build go1.15
 
diff --git a/middleware/key_auth_test.go b/middleware/key_auth_test.go
index ff8968c..447f0be 100644
--- a/middleware/key_auth_test.go
+++ b/middleware/key_auth_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/logger_test.go b/middleware/logger_test.go
index 9f35a70..d5236e1 100644
--- a/middleware/logger_test.go
+++ b/middleware/logger_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/method_override_test.go b/middleware/method_override_test.go
index 5760b15..0000d1d 100644
--- a/middleware/method_override_test.go
+++ b/middleware/method_override_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go
index 44f4414..7f3dc38 100644
--- a/middleware/middleware_test.go
+++ b/middleware/middleware_test.go
@@ -1,7 +1,13 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
+	"bufio"
+	"errors"
 	"github.com/stretchr/testify/assert"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"regexp"
@@ -90,3 +96,46 @@ func TestRewriteURL(t *testing.T) {
 		})
 	}
 }
+
+type testResponseWriterNoFlushHijack struct {
+}
+
+func (w *testResponseWriterNoFlushHijack) WriteHeader(statusCode int) {
+}
+
+func (w *testResponseWriterNoFlushHijack) Write([]byte) (int, error) {
+	return 0, nil
+}
+
+func (w *testResponseWriterNoFlushHijack) Header() http.Header {
+	return nil
+}
+
+type testResponseWriterUnwrapper struct {
+	unwrapCalled int
+	rw           http.ResponseWriter
+}
+
+func (w *testResponseWriterUnwrapper) WriteHeader(statusCode int) {
+}
+
+func (w *testResponseWriterUnwrapper) Write([]byte) (int, error) {
+	return 0, nil
+}
+
+func (w *testResponseWriterUnwrapper) Header() http.Header {
+	return nil
+}
+
+func (w *testResponseWriterUnwrapper) Unwrap() http.ResponseWriter {
+	w.unwrapCalled++
+	return w.rw
+}
+
+type testResponseWriterUnwrapperHijack struct {
+	testResponseWriterUnwrapper
+}
+
+func (w *testResponseWriterUnwrapperHijack) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+	return nil, nil, errors.New("can hijack")
+}
diff --git a/middleware/proxy_test.go b/middleware/proxy_test.go
index 1c93ba0..e87229a 100644
--- a/middleware/proxy_test.go
+++ b/middleware/proxy_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/rate_limiter_test.go b/middleware/rate_limiter_test.go
index f66961f..1de7b63 100644
--- a/middleware/rate_limiter_test.go
+++ b/middleware/rate_limiter_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/recover_test.go b/middleware/recover_test.go
index 3e0d35d..8fa34fa 100644
--- a/middleware/recover_test.go
+++ b/middleware/recover_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/redirect_test.go b/middleware/redirect_test.go
index 9d1b562..88068ea 100644
--- a/middleware/redirect_test.go
+++ b/middleware/redirect_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/request_id_test.go b/middleware/request_id_test.go
index 21b7778..4e68b12 100644
--- a/middleware/request_id_test.go
+++ b/middleware/request_id_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/request_logger_test.go b/middleware/request_logger_test.go
index 51d617a..c612f5c 100644
--- a/middleware/request_logger_test.go
+++ b/middleware/request_logger_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
@@ -194,7 +197,7 @@ func TestRequestLogger_LogValuesFuncError(t *testing.T) {
 	e.ServeHTTP(rec, req)
 
 	// NOTE: when global error handler received error returned from middleware the status has already
-	// been written to the client and response has been "commited" therefore global error handler does not do anything
+	// been written to the client and response has been "committed" therefore global error handler does not do anything
 	// and error that bubbled up in middleware chain will not be reflected in response code.
 	assert.Equal(t, http.StatusTeapot, rec.Code)
 	assert.Equal(t, http.StatusTeapot, expect.Status)
diff --git a/middleware/rewrite_test.go b/middleware/rewrite_test.go
index 47d707c..d137b2d 100644
--- a/middleware/rewrite_test.go
+++ b/middleware/rewrite_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/secure_test.go b/middleware/secure_test.go
index 79bd172..b579a6d 100644
--- a/middleware/secure_test.go
+++ b/middleware/secure_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/slash_test.go b/middleware/slash_test.go
index ddb0710..1b365cf 100644
--- a/middleware/slash_test.go
+++ b/middleware/slash_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/static_test.go b/middleware/static_test.go
index f26d97a..a10ab80 100644
--- a/middleware/static_test.go
+++ b/middleware/static_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/timeout_test.go b/middleware/timeout_test.go
index 98d96ba..e8415d6 100644
--- a/middleware/timeout_test.go
+++ b/middleware/timeout_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/middleware/util_test.go b/middleware/util_test.go
index d0f20bb..b54f126 100644
--- a/middleware/util_test.go
+++ b/middleware/util_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package middleware
 
 import (
diff --git a/response_test.go b/response_test.go
index e4fd636..70cba97 100644
--- a/response_test.go
+++ b/response_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (
@@ -57,6 +60,31 @@ func TestResponse_Flush(t *testing.T) {
 	assert.True(t, rec.Flushed)
 }
 
+type testResponseWriter struct {
+}
+
+func (w *testResponseWriter) WriteHeader(statusCode int) {
+}
+
+func (w *testResponseWriter) Write([]byte) (int, error) {
+	return 0, nil
+}
+
+func (w *testResponseWriter) Header() http.Header {
+	return nil
+}
+
+func TestResponse_FlushPanics(t *testing.T) {
+	e := New()
+	rw := new(testResponseWriter)
+	res := &Response{echo: e, Writer: rw}
+
+	// we test that we behave as before unwrapping flushers - flushing writer that does not support it causes panic
+	assert.PanicsWithError(t, "response writer flushing is not supported", func() {
+		res.Flush()
+	})
+}
+
 func TestResponse_ChangeStatusCodeBeforeWrite(t *testing.T) {
 	e := New()
 	rec := httptest.NewRecorder()
diff --git a/router_test.go b/router_test.go
index 619cce0..52d9a0a 100644
--- a/router_test.go
+++ b/router_test.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
 package echo
 
 import (