Repository: gin-gonic/gin
Pull request #2798 (2021-07-30 01:39:39)

use StringToBytes func
- With pull requests:
  - Open your pull request against `master`
  - Your pull request should have no more than two commits, if not you should squash them.
  - It should pass all tests in the available continuous integration systems such as GitHub Actions.
  - You should add/modify tests to cover your proposed code changes.
  - If your pull request contains a new feature, please document it on the README.



codecov[bot]: # [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/2798?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) Report
> Merging [#2798](https://codecov.io/gh/gin-gonic/gin/pull/2798?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (0e826f4) into [master](https://codecov.io/gh/gin-gonic/gin/commit/caf2802593277033683c4e8cb5f22c81cc35eae8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (caf2802) will **decrease** coverage by `0.15%`.
> The diff coverage is `100.00%`.

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

```diff
@@            Coverage Diff             @@
##           master    #2798      +/-   ##
==========================================
- Coverage   98.70%   98.54%   -0.16%     
==========================================
  Files          41       38       -3     
  Lines        2085     2062      -23     
==========================================
- Hits         2058     2032      -26     
- Misses         15       17       +2     
- Partials       12       13       +1     
```


| [Impacted Files](https://codecov.io/gh/gin-gonic/gin/pull/2798?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | Coverage Δ | |
|---|---|---|
| [auth.go](https://codecov.io/gh/gin-gonic/gin/pull/2798/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-YXV0aC5nbw==) | `100.00% <100.00%> (ø)` | |
| [debug.go](https://codecov.io/gh/gin-gonic/gin/pull/2798/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-ZGVidWcuZ28=) | `92.30% <0.00%> (-7.70%)` | :arrow_down: |
| [tree.go](https://codecov.io/gh/gin-gonic/gin/pull/2798/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-dHJlZS5nbw==) | `100.00% <0.00%> (ø)` | |
| [binding/binding.go](https://codecov.io/gh/gin-gonic/gin/pull/2798/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-YmluZGluZy9iaW5kaW5nLmdv) | | |
| [binding/msgpack.go](https://codecov.io/gh/gin-gonic/gin/pull/2798/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-YmluZGluZy9tc2dwYWNrLmdv) | | |
| [render/msgpack.go](https://codecov.io/gh/gin-gonic/gin/pull/2798/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-cmVuZGVyL21zZ3BhY2suZ28=) | | |

------

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


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..632e8eb
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+  - package-ecosystem: github-actions
+    directory: /
+    schedule:
+      interval: weekly
+  - package-ecosystem: gomod
+    directory: /
+    schedule:
+      interval: weekly
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..4cbc455
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,49 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [ master ]
+  schedule:
+    - cron: '0 17 * * 5'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+
+    permissions:
+      # required for all workflows
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        # Override automatic language detection by changing the below list
+        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
+        # TODO: Enable for javascript later
+        language: [ 'go']
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v3
+
+      # Initializes the CodeQL tools for scanning.
+      - name: Initialize CodeQL
+        uses: github/codeql-action/init@v1
+        with:
+          languages: ${{ matrix.language }}
+          # If you wish to specify custom queries, you can do so here or in a config file.
+          # By default, queries listed here will override any specified in a config file.
+          # Prefix the list here with "+" to use these queries and those in the config file.
+          # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+      - name: Perform CodeQL Analysis
+        uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml
index 85cc560..c5e2744 100644
--- a/.github/workflows/gin.yml
+++ b/.github/workflows/gin.yml
@@ -9,12 +9,32 @@ on:
       - master
 
 jobs:
+  lint:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Setup go
+        uses: actions/setup-go@v3
+        with:
+          go-version: '^1.16'
+      - name: Checkout repository
+        uses: actions/checkout@v3
+      - name: Setup golangci-lint
+        uses: golangci/golangci-lint-action@v3.1.0
+        with:
+          version: v1.45.0
+          args: --verbose
   test:
+    needs: lint
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest]
-        go: [1.13, 1.14, 1.15, 1.16]
+        go: [1.14, 1.15, 1.16, 1.17, 1.18]
         test-tags: ['', nomsgpack]
+        include:
+          - os: ubuntu-latest
+            go-build: ~/.cache/go-build
+          - os: macos-latest
+            go-build: ~/Library/Caches/go-build
     name: ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }}
     runs-on: ${{ matrix.os }}
     env:
@@ -23,29 +43,31 @@ jobs:
       GOPROXY: https://proxy.golang.org
     steps:
       - name: Set up Go ${{ matrix.go }}
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v3
         with:
           go-version: ${{ matrix.go }}
 
       - name: Checkout Code
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
         with:
           ref: ${{ github.ref }}
 
-      - name: Install Dependencies
-        run: make tools
-
-      - name: Run Check
-        run: |
-          make vet
-          make fmt-check
-          make misspell-check
+      - uses: actions/cache@v3
+        with:
+          path: |
+            ${{ matrix.go-build }}
+            ~/go/pkg/mod
+          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+          restore-keys: |
+            ${{ runner.os }}-go-
 
       - name: Run Tests
         run: make test
 
       - name: Upload coverage to Codecov
-        uses: codecov/codecov-action@v1
+        uses: codecov/codecov-action@v3
+        with:
+          flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
   notification-gitter:
     needs: test
     runs-on: ubuntu-latest
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..c5e1de3
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,39 @@
+run:
+  timeout: 5m
+linters:
+  enable:
+    - asciicheck
+    - depguard
+    - dogsled
+    - durationcheck
+    - errcheck
+    - errorlint
+    - exportloopref
+    - gci
+    - gofmt
+    - goimports
+    - gosec
+    - misspell
+    - nakedret
+    - nilerr
+    - nolintlint
+    - revive
+    - wastedassign
+issues:
+  exclude-rules:
+    - linters:
+        - structcheck
+        - unused
+      text: "`data` is unused"
+    - linters:
+        - staticcheck
+      text: "SA1019:"
+    - linters:
+        - revive
+      text: "var-naming:"
+    - linters:
+        - revive
+      text: "exported:"
+    - path: _test\.go
+      linters:
+        - gosec # security is not make sense in tests
diff --git a/AUTHORS.md b/AUTHORS.md
index c634e6b..533204e 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -109,6 +109,11 @@ People and companies, who have contributed, in alphabetical order.
 - Fix typo in comment
 
 
+**@jincheng9 (Jincheng Zhang)**
+- ★ support TSR when wildcard follows named param
+- Fix errors and typos in comments
+
+
 **@joiggama (Ignacio Galindo)**
 - Add utf-8 charset header on renders
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a28edc8..4c806a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,41 @@
 # Gin ChangeLog
 
+## Gin v1.7.7
+
+### BUGFIXES
+
+* Fixed X-Forwarded-For unsafe handling of CVE-2020-28483 [#2844](https://github.com/gin-gonic/gin/pull/2844), closed issue [#2862](https://github.com/gin-gonic/gin/issues/2862).
+* Tree: updated the code logic for `latestNode` [#2897](https://github.com/gin-gonic/gin/pull/2897), closed issue [#2894](https://github.com/gin-gonic/gin/issues/2894) [#2878](https://github.com/gin-gonic/gin/issues/2878).
+* Tree: fixed the misplacement of adding slashes [#2847](https://github.com/gin-gonic/gin/pull/2847), closed issue [#2843](https://github.com/gin-gonic/gin/issues/2843).
+* Tree: fixed tsr with mixed static and wildcard paths [#2924](https://github.com/gin-gonic/gin/pull/2924), closed issue [#2918](https://github.com/gin-gonic/gin/issues/2918).
+
+### ENHANCEMENTS
+
+* TrustedProxies: make it backward-compatible [#2887](https://github.com/gin-gonic/gin/pull/2887), closed issue [#2819](https://github.com/gin-gonic/gin/issues/2819).
+* TrustedPlatform: provide custom options for another CDN services [#2906](https://github.com/gin-gonic/gin/pull/2906).
+
+### DOCS
+
+* NoMethod: added usage annotation ([#2832](https://github.com/gin-gonic/gin/pull/2832#issuecomment-929954463)).
+
+## Gin v1.7.6
+
+### BUGFIXES
+
+* bump new release to fix v1.7.5 release error by using v1.7.4 codes.
+
+## Gin v1.7.4
+
+### BUGFIXES
+
+* bump new release to fix checksum mismatch
+
+## Gin v1.7.3
+
+### BUGFIXES
+
+* fix level 1 router match [#2767](https://github.com/gin-gonic/gin/issues/2767), [#2796](https://github.com/gin-gonic/gin/issues/2796)
+
 ## Gin v1.7.2
 
 ### BUGFIXES
diff --git a/README.md b/README.md
index ef80117..af9a1e5 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
     - [http2 server push](#http2-server-push)
     - [Define format for the log of routes](#define-format-for-the-log-of-routes)
     - [Set and get a cookie](#set-and-get-a-cookie)
+  - [Don't trust all proxies](#dont-trust-all-proxies)
   - [Testing](#testing)
   - [Users](#users)
 
@@ -85,7 +86,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
 
 To install Gin package, you need to install Go and set your Go workspace first.
 
-1. The first need [Go](https://golang.org/) installed (**version 1.13+ is required**), then you can use the below Go command to install Gin.
+1. You first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin.
 
 ```sh
 $ go get -u github.com/gin-gonic/gin
@@ -256,14 +257,15 @@ func main() {
 
 	// For each matched request Context will hold the route definition
 	router.POST("/user/:name/*action", func(c *gin.Context) {
-		c.FullPath() == "/user/:name/*action" // true
+		b := c.FullPath() == "/user/:name/*action" // true
+		c.String(http.StatusOK, "%t", b)
 	})
 
 	// This handler will add a new router for /user/groups.
 	// Exact routes are resolved before param routes, regardless of the order they were defined.
 	// Routes starting with /user/groups are never interpreted as /user/:name/... routes
 	router.GET("/user/groups", func(c *gin.Context) {
-		c.String(http.StatusOK, "The available groups are [...]", name)
+		c.String(http.StatusOK, "The available groups are [...]")
 	})
 
 	router.Run(":8080")
@@ -382,7 +384,7 @@ func main() {
 	// Set a lower memory limit for multipart forms (default is 32 MiB)
 	router.MaxMultipartMemory = 8 << 20  // 8 MiB
 	router.POST("/upload", func(c *gin.Context) {
-		// single file
+		// Single file
 		file, _ := c.FormFile("file")
 		log.Println(file.Filename)
 
@@ -511,6 +513,7 @@ func main() {
 
 		// nested group
 		testing := authorized.Group("testing")
+		// visit 0.0.0.0:8080/testing/analytics
 		testing.GET("/analytics", analyticsEndpoint)
 	}
 
@@ -904,7 +907,7 @@ func startPage(c *gin.Context) {
 	var person Person
 	// If `GET`, only `Form` binding engine (`query`) used.
 	// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
-	// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
+	// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88
         if c.ShouldBind(&person) == nil {
                 log.Println(person.Name)
                 log.Println(person.Address)
@@ -1241,7 +1244,8 @@ func main() {
 	router.Static("/assets", "./assets")
 	router.StaticFS("/more_static", http.Dir("my_file_system"))
 	router.StaticFile("/favicon.ico", "./resources/favicon.ico")
-
+	router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))
+	
 	// Listen and serve on 0.0.0.0:8080
 	router.Run(":8080")
 }
@@ -1407,7 +1411,7 @@ import (
 
 func formatAsDate(t time.Time) string {
     year, month, day := t.Date()
-    return fmt.Sprintf("%d%02d/%02d", year, month, day)
+    return fmt.Sprintf("%d/%02d/%02d", year, month, day)
 }
 
 func main() {
@@ -1826,7 +1830,7 @@ func main() {
 	quit := make(chan os.Signal)
 	// kill (no param) default send syscall.SIGTERM
 	// kill -2 is syscall.SIGINT
-	// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
+	// kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it
 	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
 	<-quit
 	log.Println("Shutting down server...")
@@ -1999,7 +2003,7 @@ func SomeHandler(c *gin.Context) {
   objA := formA{}
   objB := formB{}
   // This reads c.Request.Body and stores the result into the context.
-  if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
+  if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil {
     c.String(http.StatusOK, `the body should be formA`)
   // At this time, it reuses body stored in the context.
   } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
@@ -2201,11 +2205,17 @@ Gin lets you specify which headers to hold the real client IP (if any),
 as well as specifying which proxies (or direct clients) you trust to
 specify one of these headers.
 
-The `TrustedProxies` slice on your `gin.Engine` specifes network addresses or
-network CIDRs from where clients which their request headers related to client
+Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses
+or network CIDRs from where clients which their request headers related to client
 IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
 IPv6 CIDRs.
 
+**Attention:** Gin trust all proxies by default if you don't specify a trusted 
+proxy using the function above, **this is NOT safe**. At the same time, if you don't
+use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
+then `Context.ClientIP()` will return the remote address directly to avoid some
+unnecessary computation.
+
 ```go
 import (
 	"fmt"
@@ -2216,7 +2226,7 @@ import (
 func main() {
 
 	router := gin.Default()
-	router.TrustedProxies = []string{"192.168.1.2"}
+	router.SetTrustedProxies([]string{"192.168.1.2"})
 
 	router.GET("/", func(c *gin.Context) {
 		// If the client is 192.168.1.2, use the X-Forwarded-For
@@ -2229,6 +2239,34 @@ func main() {
 }
 ```
 
+**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform`
+to skip TrustedProxies check, it has a higher priority than TrustedProxies. 
+Look at the example below:
+```go
+import (
+	"fmt"
+
+	"github.com/gin-gonic/gin"
+)
+
+func main() {
+
+	router := gin.Default()
+	// Use predefined header gin.PlatformXXX
+	router.TrustedPlatform = gin.PlatformGoogleAppEngine
+	// Or set your own trusted request header for another trusted proxy service
+	// Don't set it to any suspect request header, it's unsafe
+	router.TrustedPlatform = "X-CDN-IP"
+
+	router.GET("/", func(c *gin.Context) {
+		// If you set TrustedPlatform, ClientIP() will resolve the
+		// corresponding header and return IP directly
+		fmt.Printf("ClientIP: %s\n", c.ClientIP())
+	})
+	router.Run()
+}
+```
+
 ## Testing
 
 The `net/http/httptest` package is preferable way for HTTP testing.
diff --git a/any.go b/any.go
new file mode 100644
index 0000000..a0104dc
--- /dev/null
+++ b/any.go
@@ -0,0 +1,10 @@
+// Copyright 2022 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+//go:build !go1.18
+// +build !go1.18
+
+package gin
+
+type any = interface{}
diff --git a/auth.go b/auth.go
index 4d8a6ce..482c499 100644
--- a/auth.go
+++ b/auth.go
@@ -31,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
 		return "", false
 	}
 	for _, pair := range a {
-		if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 {
+		if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
 			return pair.user, true
 		}
 	}
diff --git a/binding/any.go b/binding/any.go
new file mode 100644
index 0000000..1331d39
--- /dev/null
+++ b/binding/any.go
@@ -0,0 +1,10 @@
+// Copyright 2022 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+//go:build !go1.18
+// +build !go1.18
+
+package binding
+
+type any = interface{}
diff --git a/binding/binding.go b/binding/binding.go
index deb7166..703a1cf 100644
--- a/binding/binding.go
+++ b/binding/binding.go
@@ -29,21 +29,21 @@ const (
 // the form POST.
 type Binding interface {
 	Name() string
-	Bind(*http.Request, interface{}) error
+	Bind(*http.Request, any) error
 }
 
 // BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
 // but it reads the body from supplied bytes instead of req.Body.
 type BindingBody interface {
 	Binding
-	BindBody([]byte, interface{}) error
+	BindBody([]byte, any) error
 }
 
 // BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
-// but it read the Params.
+// but it reads the Params.
 type BindingUri interface {
 	Name() string
-	BindUri(map[string][]string, interface{}) error
+	BindUri(map[string][]string, any) error
 }
 
 // StructValidator is the minimal interface which needs to be implemented in
@@ -57,11 +57,11 @@ type StructValidator interface {
 	// If the received type is a struct or pointer to a struct, the validation should be performed.
 	// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
 	// Otherwise nil must be returned.
-	ValidateStruct(interface{}) error
+	ValidateStruct(any) error
 
 	// Engine returns the underlying validator engine which powers the
 	// StructValidator implementation.
-	Engine() interface{}
+	Engine() any
 }
 
 // Validator is the default validator which implements the StructValidator
@@ -110,7 +110,7 @@ func Default(method, contentType string) Binding {
 	}
 }
 
-func validate(obj interface{}) error {
+func validate(obj any) error {
 	if Validator == nil {
 		return nil
 	}
diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go
index 2342447..b381854 100644
--- a/binding/binding_nomsgpack.go
+++ b/binding/binding_nomsgpack.go
@@ -27,21 +27,21 @@ const (
 // the form POST.
 type Binding interface {
 	Name() string
-	Bind(*http.Request, interface{}) error
+	Bind(*http.Request, any) error
 }
 
 // BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
 // but it reads the body from supplied bytes instead of req.Body.
 type BindingBody interface {
 	Binding
-	BindBody([]byte, interface{}) error
+	BindBody([]byte, any) error
 }
 
 // BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
-// but it read the Params.
+// but it reads the Params.
 type BindingUri interface {
 	Name() string
-	BindUri(map[string][]string, interface{}) error
+	BindUri(map[string][]string, any) error
 }
 
 // StructValidator is the minimal interface which needs to be implemented in
@@ -54,11 +54,11 @@ type StructValidator interface {
 	// If the received type is a struct or pointer to a struct, the validation should be performed.
 	// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
 	// Otherwise nil must be returned.
-	ValidateStruct(interface{}) error
+	ValidateStruct(any) error
 
 	// Engine returns the underlying validator engine which powers the
 	// StructValidator implementation.
-	Engine() interface{}
+	Engine() any
 }
 
 // Validator is the default validator which implements the StructValidator
@@ -104,7 +104,7 @@ func Default(method, contentType string) Binding {
 	}
 }
 
-func validate(obj interface{}) error {
+func validate(obj any) error {
 	if Validator == nil {
 		return nil
 	}
diff --git a/binding/default_validator.go b/binding/default_validator.go
index 87fc4c6..3515a8c 100644
--- a/binding/default_validator.go
+++ b/binding/default_validator.go
@@ -18,10 +18,10 @@ type defaultValidator struct {
 	validate *validator.Validate
 }
 
-type sliceValidateError []error
+type SliceValidationError []error
 
-// Error concatenates all error elements in sliceValidateError into a single string separated by \n.
-func (err sliceValidateError) Error() string {
+// Error concatenates all error elements in SliceValidationError into a single string separated by \n.
+func (err SliceValidationError) Error() string {
 	n := len(err)
 	switch n {
 	case 0:
@@ -46,7 +46,7 @@ func (err sliceValidateError) Error() string {
 var _ StructValidator = &defaultValidator{}
 
 // ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
-func (v *defaultValidator) ValidateStruct(obj interface{}) error {
+func (v *defaultValidator) ValidateStruct(obj any) error {
 	if obj == nil {
 		return nil
 	}
@@ -59,7 +59,7 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
 		return v.validateStruct(obj)
 	case reflect.Slice, reflect.Array:
 		count := value.Len()
-		validateRet := make(sliceValidateError, 0)
+		validateRet := make(SliceValidationError, 0)
 		for i := 0; i < count; i++ {
 			if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
 				validateRet = append(validateRet, err)
@@ -75,7 +75,7 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
 }
 
 // validateStruct receives struct type
-func (v *defaultValidator) validateStruct(obj interface{}) error {
+func (v *defaultValidator) validateStruct(obj any) error {
 	v.lazyinit()
 	return v.validate.Struct(obj)
 }
@@ -84,7 +84,7 @@ func (v *defaultValidator) validateStruct(obj interface{}) error {
 // Validator instance. This is useful if you want to register custom validations
 // or struct level validations. See validator GoDoc for more info -
 // https://pkg.go.dev/github.com/go-playground/validator/v10
-func (v *defaultValidator) Engine() interface{} {
+func (v *defaultValidator) Engine() any {
 	v.lazyinit()
 	return v.validate
 }
diff --git a/binding/form.go b/binding/form.go
index 040af9e..f5cbf57 100644
--- a/binding/form.go
+++ b/binding/form.go
@@ -5,6 +5,7 @@
 package binding
 
 import (
+	"errors"
 	"net/http"
 )
 
@@ -18,11 +19,11 @@ func (formBinding) Name() string {
 	return "form"
 }
 
-func (formBinding) Bind(req *http.Request, obj interface{}) error {
+func (formBinding) Bind(req *http.Request, obj any) error {
 	if err := req.ParseForm(); err != nil {
 		return err
 	}
-	if err := req.ParseMultipartForm(defaultMemory); err != nil && err != http.ErrNotMultipart {
+	if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {
 		return err
 	}
 	if err := mapForm(obj, req.Form); err != nil {
@@ -35,7 +36,7 @@ func (formPostBinding) Name() string {
 	return "form-urlencoded"
 }
 
-func (formPostBinding) Bind(req *http.Request, obj interface{}) error {
+func (formPostBinding) Bind(req *http.Request, obj any) error {
 	if err := req.ParseForm(); err != nil {
 		return err
 	}
@@ -49,7 +50,7 @@ func (formMultipartBinding) Name() string {
 	return "multipart/form-data"
 }
 
-func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
+func (formMultipartBinding) Bind(req *http.Request, obj any) error {
 	if err := req.ParseMultipartForm(defaultMemory); err != nil {
 		return err
 	}
diff --git a/binding/form_mapping.go b/binding/form_mapping.go
index cb66dd4..c24dd55 100644
--- a/binding/form_mapping.go
+++ b/binding/form_mapping.go
@@ -26,24 +26,24 @@ var (
 	ErrConvertToMapString = errors.New("can not convert to map of strings")
 )
 
-func mapUri(ptr interface{}, m map[string][]string) error {
+func mapURI(ptr any, m map[string][]string) error {
 	return mapFormByTag(ptr, m, "uri")
 }
 
-func mapForm(ptr interface{}, form map[string][]string) error {
+func mapForm(ptr any, form map[string][]string) error {
 	return mapFormByTag(ptr, form, "form")
 }
 
-func MapFormWithTag(ptr interface{}, form map[string][]string, tag string) error {
+func MapFormWithTag(ptr any, form map[string][]string, tag string) error {
 	return mapFormByTag(ptr, form, tag)
 }
 
 var emptyField = reflect.StructField{}
 
-func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
+func mapFormByTag(ptr any, form map[string][]string, tag string) error {
 	// Check if ptr is a map
 	ptrVal := reflect.ValueOf(ptr)
-	var pointed interface{}
+	var pointed any
 	if ptrVal.Kind() == reflect.Ptr {
 		ptrVal = ptrVal.Elem()
 		pointed = ptrVal.Interface()
@@ -61,7 +61,7 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
 
 // setter tries to set value on a walking by fields of a struct
 type setter interface {
-	TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error)
+	TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error)
 }
 
 type formSource map[string][]string
@@ -69,11 +69,11 @@ type formSource map[string][]string
 var _ setter = formSource(nil)
 
 // TrySet tries to set a value by request's form source (like map[string][]string)
-func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
+func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) {
 	return setByForm(value, field, form, tagValue, opt)
 }
 
-func mappingByPtr(ptr interface{}, setter setter, tag string) error {
+func mappingByPtr(ptr any, setter setter, tag string) error {
 	_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
 	return err
 }
@@ -83,7 +83,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
 		return false, nil
 	}
 
-	var vKind = value.Kind()
+	vKind := value.Kind()
 
 	if vKind == reflect.Ptr {
 		var isNew bool
@@ -92,14 +92,14 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
 			isNew = true
 			vPtr = reflect.New(value.Type().Elem())
 		}
-		isSetted, err := mapping(vPtr.Elem(), field, setter, tag)
+		isSet, err := mapping(vPtr.Elem(), field, setter, tag)
 		if err != nil {
 			return false, err
 		}
-		if isNew && isSetted {
+		if isNew && isSet {
 			value.Set(vPtr)
 		}
-		return isSetted, nil
+		return isSet, nil
 	}
 
 	if vKind != reflect.Struct || !field.Anonymous {
@@ -115,7 +115,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
 	if vKind == reflect.Struct {
 		tValue := value.Type()
 
-		var isSetted bool
+		var isSet bool
 		for i := 0; i < value.NumField(); i++ {
 			sf := tValue.Field(i)
 			if sf.PkgPath != "" && !sf.Anonymous { // unexported
@@ -125,9 +125,9 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
 			if err != nil {
 				return false, err
 			}
-			isSetted = isSetted || ok
+			isSet = isSet || ok
 		}
-		return isSetted, nil
+		return isSet, nil
 	}
 	return false, nil
 }
@@ -164,7 +164,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
 	return setter.TrySet(value, field, tagValue, setOpt)
 }
 
-func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) {
+func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
 	vs, ok := form[tagValue]
 	if !ok && !opt.isDefaultExists {
 		return false, nil
@@ -210,7 +210,7 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
 	case reflect.Int64:
 		switch value.Interface().(type) {
 		case time.Duration:
-			return setTimeDuration(val, value, field)
+			return setTimeDuration(val, value)
 		}
 		return setIntField(val, 64, value)
 	case reflect.Uint:
@@ -310,7 +310,6 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
 		t := time.Unix(tv/int64(d), tv%int64(d))
 		value.Set(reflect.ValueOf(t))
 		return nil
-
 	}
 
 	if val == "" {
@@ -360,7 +359,7 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err
 	return nil
 }
 
-func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error {
+func setTimeDuration(val string, value reflect.Value) error {
 	d, err := time.ParseDuration(val)
 	if err != nil {
 		return err
@@ -377,7 +376,7 @@ func head(str, sep string) (head string, tail string) {
 	return str[:idx], str[idx+len(sep):]
 }
 
-func setFormMap(ptr interface{}, form map[string][]string) error {
+func setFormMap(ptr any, form map[string][]string) error {
 	el := reflect.TypeOf(ptr).Elem()
 
 	if el.Kind() == reflect.Slice {
diff --git a/binding/header.go b/binding/header.go
index b99302a..14f525e 100644
--- a/binding/header.go
+++ b/binding/header.go
@@ -12,7 +12,7 @@ func (headerBinding) Name() string {
 	return "header"
 }
 
-func (headerBinding) Bind(req *http.Request, obj interface{}) error {
+func (headerBinding) Bind(req *http.Request, obj any) error {
 
 	if err := mapHeader(obj, req.Header); err != nil {
 		return err
@@ -21,7 +21,7 @@ func (headerBinding) Bind(req *http.Request, obj interface{}) error {
 	return validate(obj)
 }
 
-func mapHeader(ptr interface{}, h map[string][]string) error {
+func mapHeader(ptr any, h map[string][]string) error {
 	return mappingByPtr(ptr, headerSource(h), "header")
 }
 
diff --git a/binding/json.go b/binding/json.go
index 45aaa49..2e3e1dd 100644
--- a/binding/json.go
+++ b/binding/json.go
@@ -30,18 +30,18 @@ func (jsonBinding) Name() string {
 	return "json"
 }
 
-func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
+func (jsonBinding) Bind(req *http.Request, obj any) error {
 	if req == nil || req.Body == nil {
 		return errors.New("invalid request")
 	}
 	return decodeJSON(req.Body, obj)
 }
 
-func (jsonBinding) BindBody(body []byte, obj interface{}) error {
+func (jsonBinding) BindBody(body []byte, obj any) error {
 	return decodeJSON(bytes.NewReader(body), obj)
 }
 
-func decodeJSON(r io.Reader, obj interface{}) error {
+func decodeJSON(r io.Reader, obj any) error {
 	decoder := json.NewDecoder(r)
 	if EnableDecoderUseNumber {
 		decoder.UseNumber()
diff --git a/binding/msgpack.go b/binding/msgpack.go
index 2a44299..6519771 100644
--- a/binding/msgpack.go
+++ b/binding/msgpack.go
@@ -21,15 +21,15 @@ func (msgpackBinding) Name() string {
 	return "msgpack"
 }
 
-func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
+func (msgpackBinding) Bind(req *http.Request, obj any) error {
 	return decodeMsgPack(req.Body, obj)
 }
 
-func (msgpackBinding) BindBody(body []byte, obj interface{}) error {
+func (msgpackBinding) BindBody(body []byte, obj any) error {
 	return decodeMsgPack(bytes.NewReader(body), obj)
 }
 
-func decodeMsgPack(r io.Reader, obj interface{}) error {
+func decodeMsgPack(r io.Reader, obj any) error {
 	cdc := new(codec.MsgpackHandle)
 	if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
 		return err
diff --git a/binding/multipart_form_mapping.go b/binding/multipart_form_mapping.go
index 69c0a54..c4d7ed7 100644
--- a/binding/multipart_form_mapping.go
+++ b/binding/multipart_form_mapping.go
@@ -32,7 +32,7 @@ func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField
 	return setByForm(value, field, r.MultipartForm.Value, key, opt)
 }
 
-func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
+func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
 	switch value.Kind() {
 	case reflect.Ptr:
 		switch value.Interface().(type) {
@@ -48,9 +48,9 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file
 		}
 	case reflect.Slice:
 		slice := reflect.MakeSlice(value.Type(), len(files), len(files))
-		isSetted, err = setArrayOfMultipartFormFiles(slice, field, files)
-		if err != nil || !isSetted {
-			return isSetted, err
+		isSet, err = setArrayOfMultipartFormFiles(slice, field, files)
+		if err != nil || !isSet {
+			return isSet, err
 		}
 		value.Set(slice)
 		return true, nil
@@ -60,14 +60,14 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file
 	return false, ErrMultiFileHeader
 }
 
-func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
+func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
 	if value.Len() != len(files) {
 		return false, ErrMultiFileHeaderLenInvalid
 	}
 	for i := range files {
-		setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
-		if err != nil || !setted {
-			return setted, err
+		set, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
+		if err != nil || !set {
+			return set, err
 		}
 	}
 	return true, nil
diff --git a/binding/protobuf.go b/binding/protobuf.go
index f9ece92..ace8f65 100644
--- a/binding/protobuf.go
+++ b/binding/protobuf.go
@@ -5,10 +5,11 @@
 package binding
 
 import (
+	"errors"
 	"io/ioutil"
 	"net/http"
 
-	"github.com/golang/protobuf/proto"
+	"google.golang.org/protobuf/proto"
 )
 
 type protobufBinding struct{}
@@ -17,7 +18,7 @@ func (protobufBinding) Name() string {
 	return "protobuf"
 }
 
-func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
+func (b protobufBinding) Bind(req *http.Request, obj any) error {
 	buf, err := ioutil.ReadAll(req.Body)
 	if err != nil {
 		return err
@@ -25,8 +26,12 @@ func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
 	return b.BindBody(buf, obj)
 }
 
-func (protobufBinding) BindBody(body []byte, obj interface{}) error {
-	if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil {
+func (protobufBinding) BindBody(body []byte, obj any) error {
+	msg, ok := obj.(proto.Message)
+	if !ok {
+		return errors.New("obj is not ProtoMessage")
+	}
+	if err := proto.Unmarshal(body, msg); err != nil {
 		return err
 	}
 	// Here it's same to return validate(obj), but util now we can't add
diff --git a/binding/query.go b/binding/query.go
index 219743f..9790ce6 100644
--- a/binding/query.go
+++ b/binding/query.go
@@ -12,7 +12,7 @@ func (queryBinding) Name() string {
 	return "query"
 }
 
-func (queryBinding) Bind(req *http.Request, obj interface{}) error {
+func (queryBinding) Bind(req *http.Request, obj any) error {
 	values := req.URL.Query()
 	if err := mapForm(obj, values); err != nil {
 		return err
diff --git a/binding/uri.go b/binding/uri.go
index f91ec38..4d44ab9 100644
--- a/binding/uri.go
+++ b/binding/uri.go
@@ -10,8 +10,8 @@ func (uriBinding) Name() string {
 	return "uri"
 }
 
-func (uriBinding) BindUri(m map[string][]string, obj interface{}) error {
-	if err := mapUri(obj, m); err != nil {
+func (uriBinding) BindUri(m map[string][]string, obj any) error {
+	if err := mapURI(obj, m); err != nil {
 		return err
 	}
 	return validate(obj)
diff --git a/binding/xml.go b/binding/xml.go
index 4e90114..e62af4a 100644
--- a/binding/xml.go
+++ b/binding/xml.go
@@ -17,14 +17,14 @@ func (xmlBinding) Name() string {
 	return "xml"
 }
 
-func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
+func (xmlBinding) Bind(req *http.Request, obj any) error {
 	return decodeXML(req.Body, obj)
 }
 
-func (xmlBinding) BindBody(body []byte, obj interface{}) error {
+func (xmlBinding) BindBody(body []byte, obj any) error {
 	return decodeXML(bytes.NewReader(body), obj)
 }
-func decodeXML(r io.Reader, obj interface{}) error {
+func decodeXML(r io.Reader, obj any) error {
 	decoder := xml.NewDecoder(r)
 	if err := decoder.Decode(obj); err != nil {
 		return err
diff --git a/binding/yaml.go b/binding/yaml.go
index a2d36d6..183f141 100644
--- a/binding/yaml.go
+++ b/binding/yaml.go
@@ -18,15 +18,15 @@ func (yamlBinding) Name() string {
 	return "yaml"
 }
 
-func (yamlBinding) Bind(req *http.Request, obj interface{}) error {
+func (yamlBinding) Bind(req *http.Request, obj any) error {
 	return decodeYAML(req.Body, obj)
 }
 
-func (yamlBinding) BindBody(body []byte, obj interface{}) error {
+func (yamlBinding) BindBody(body []byte, obj any) error {
 	return decodeYAML(bytes.NewReader(body), obj)
 }
 
-func decodeYAML(r io.Reader, obj interface{}) error {
+func decodeYAML(r io.Reader, obj any) error {
 	decoder := yaml.NewDecoder(r)
 	if err := decoder.Decode(obj); err != nil {
 		return err
diff --git a/context.go b/context.go
index ecf74ba..0fe1333 100644
--- a/context.go
+++ b/context.go
@@ -6,7 +6,6 @@ package gin
 
 import (
 	"errors"
-	"fmt"
 	"io"
 	"io/ioutil"
 	"log"
@@ -55,14 +54,15 @@ type Context struct {
 	index    int8
 	fullPath string
 
-	engine *Engine
-	params *Params
+	engine       *Engine
+	params       *Params
+	skippedNodes *[]skippedNode
 
-	// This mutex protect Keys map
+	// This mutex protects Keys map.
 	mu sync.RWMutex
 
 	// Keys is a key/value pair exclusively for the context of each request.
-	Keys map[string]interface{}
+	Keys map[string]any
 
 	// Errors is a list of errors attached to all the handlers/middlewares who used this context.
 	Errors errorMsgs
@@ -70,10 +70,10 @@ type Context struct {
 	// Accepted defines a list of manually accepted formats for content negotiation.
 	Accepted []string
 
-	// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
+	// queryCache caches the query result from c.Request.URL.Query().
 	queryCache url.Values
 
-	// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
+	// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
 	// or PUT body parameters.
 	formCache url.Values
 
@@ -98,7 +98,9 @@ func (c *Context) reset() {
 	c.Accepted = nil
 	c.queryCache = nil
 	c.formCache = nil
+	c.sameSite = 0
 	*c.params = (*c.params)[:0]
+	*c.skippedNodes = (*c.skippedNodes)[:0]
 }
 
 // Copy returns a copy of the current context that can be safely used outside the request's scope.
@@ -114,7 +116,7 @@ func (c *Context) Copy() *Context {
 	cp.Writer = &cp.writermem
 	cp.index = abortIndex
 	cp.handlers = nil
-	cp.Keys = map[string]interface{}{}
+	cp.Keys = map[string]any{}
 	for k, v := range c.Keys {
 		cp.Keys[k] = v
 	}
@@ -193,7 +195,7 @@ func (c *Context) AbortWithStatus(code int) {
 // AbortWithStatusJSON calls `Abort()` and then `JSON` internally.
 // This method stops the chain, writes the status code and return a JSON body.
 // It also sets the Content-Type as "application/json".
-func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) {
+func (c *Context) AbortWithStatusJSON(code int, jsonObj any) {
 	c.Abort()
 	c.JSON(code, jsonObj)
 }
@@ -220,7 +222,8 @@ func (c *Context) Error(err error) *Error {
 		panic("err is nil")
 	}
 
-	parsedError, ok := err.(*Error)
+	var parsedError *Error
+	ok := errors.As(err, &parsedError)
 	if !ok {
 		parsedError = &Error{
 			Err:  err,
@@ -238,10 +241,10 @@ func (c *Context) Error(err error) *Error {
 
 // Set is used to store a new key/value pair exclusively for this context.
 // It also lazy initializes  c.Keys if it was not used previously.
-func (c *Context) Set(key string, value interface{}) {
+func (c *Context) Set(key string, value any) {
 	c.mu.Lock()
 	if c.Keys == nil {
-		c.Keys = make(map[string]interface{})
+		c.Keys = make(map[string]any)
 	}
 
 	c.Keys[key] = value
@@ -249,8 +252,8 @@ func (c *Context) Set(key string, value interface{}) {
 }
 
 // Get returns the value for the given key, ie: (value, true).
-// If the value does not exists it returns (nil, false)
-func (c *Context) Get(key string) (value interface{}, exists bool) {
+// If the value does not exist it returns (nil, false)
+func (c *Context) Get(key string) (value any, exists bool) {
 	c.mu.RLock()
 	value, exists = c.Keys[key]
 	c.mu.RUnlock()
@@ -258,7 +261,7 @@ func (c *Context) Get(key string) (value interface{}, exists bool) {
 }
 
 // MustGet returns the value for the given key if it exists, otherwise it panics.
-func (c *Context) MustGet(key string) interface{} {
+func (c *Context) MustGet(key string) any {
 	if value, exists := c.Get(key); exists {
 		return value
 	}
@@ -346,9 +349,9 @@ func (c *Context) GetStringSlice(key string) (ss []string) {
 }
 
 // GetStringMap returns the value associated with the key as a map of interfaces.
-func (c *Context) GetStringMap(key string) (sm map[string]interface{}) {
+func (c *Context) GetStringMap(key string) (sm map[string]any) {
 	if val, ok := c.Get(key); ok && val != nil {
-		sm, _ = val.(map[string]interface{})
+		sm, _ = val.(map[string]any)
 	}
 	return
 }
@@ -383,6 +386,15 @@ func (c *Context) Param(key string) string {
 	return c.Params.ByName(key)
 }
 
+// AddParam adds param to context and
+// replaces path param key with given value for e2e testing purposes
+// Example Route: "/user/:id"
+// AddParam("id", 1)
+// Result: "/user/1"
+func (c *Context) AddParam(key, value string) {
+	c.Params = append(c.Params, Param{Key: key, Value: value})
+}
+
 // Query returns the keyed url query value if it exists,
 // otherwise it returns an empty string `("")`.
 // It is shortcut for `c.Request.URL.Query().Get(key)`
@@ -391,9 +403,9 @@ func (c *Context) Param(key string) string {
 // 	   c.Query("name") == "Manu"
 // 	   c.Query("value") == ""
 // 	   c.Query("wtf") == ""
-func (c *Context) Query(key string) string {
-	value, _ := c.GetQuery(key)
-	return value
+func (c *Context) Query(key string) (value string) {
+	value, _ = c.GetQuery(key)
+	return
 }
 
 // DefaultQuery returns the keyed url query value if it exists,
@@ -427,9 +439,9 @@ func (c *Context) GetQuery(key string) (string, bool) {
 
 // QueryArray returns a slice of strings for a given query key.
 // The length of the slice depends on the number of params with the given key.
-func (c *Context) QueryArray(key string) []string {
-	values, _ := c.GetQueryArray(key)
-	return values
+func (c *Context) QueryArray(key string) (values []string) {
+	values, _ = c.GetQueryArray(key)
+	return
 }
 
 func (c *Context) initQueryCache() {
@@ -444,18 +456,16 @@ func (c *Context) initQueryCache() {
 
 // GetQueryArray returns a slice of strings for a given query key, plus
 // a boolean value whether at least one value exists for the given key.
-func (c *Context) GetQueryArray(key string) ([]string, bool) {
+func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
 	c.initQueryCache()
-	if values, ok := c.queryCache[key]; ok && len(values) > 0 {
-		return values, true
-	}
-	return []string{}, false
+	values, ok = c.queryCache[key]
+	return
 }
 
 // QueryMap returns a map for a given query key.
-func (c *Context) QueryMap(key string) map[string]string {
-	dicts, _ := c.GetQueryMap(key)
-	return dicts
+func (c *Context) QueryMap(key string) (dicts map[string]string) {
+	dicts, _ = c.GetQueryMap(key)
+	return
 }
 
 // GetQueryMap returns a map for a given query key, plus a boolean value
@@ -467,9 +477,9 @@ func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
 
 // PostForm returns the specified key from a POST urlencoded form or multipart form
 // when it exists, otherwise it returns an empty string `("")`.
-func (c *Context) PostForm(key string) string {
-	value, _ := c.GetPostForm(key)
-	return value
+func (c *Context) PostForm(key string) (value string) {
+	value, _ = c.GetPostForm(key)
+	return
 }
 
 // DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
@@ -498,9 +508,9 @@ func (c *Context) GetPostForm(key string) (string, bool) {
 
 // PostFormArray returns a slice of strings for a given form key.
 // The length of the slice depends on the number of params with the given key.
-func (c *Context) PostFormArray(key string) []string {
-	values, _ := c.GetPostFormArray(key)
-	return values
+func (c *Context) PostFormArray(key string) (values []string) {
+	values, _ = c.GetPostFormArray(key)
+	return
 }
 
 func (c *Context) initFormCache() {
@@ -508,7 +518,7 @@ func (c *Context) initFormCache() {
 		c.formCache = make(url.Values)
 		req := c.Request
 		if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
-			if err != http.ErrNotMultipart {
+			if !errors.Is(err, http.ErrNotMultipart) {
 				debugPrint("error on parse multipart form array: %v", err)
 			}
 		}
@@ -518,18 +528,16 @@ func (c *Context) initFormCache() {
 
 // GetPostFormArray returns a slice of strings for a given form key, plus
 // a boolean value whether at least one value exists for the given key.
-func (c *Context) GetPostFormArray(key string) ([]string, bool) {
+func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
 	c.initFormCache()
-	if values := c.formCache[key]; len(values) > 0 {
-		return values, true
-	}
-	return []string{}, false
+	values, ok = c.formCache[key]
+	return
 }
 
 // PostFormMap returns a map for a given form key.
-func (c *Context) PostFormMap(key string) map[string]string {
-	dicts, _ := c.GetPostFormMap(key)
-	return dicts
+func (c *Context) PostFormMap(key string) (dicts map[string]string) {
+	dicts, _ = c.GetPostFormMap(key)
+	return
 }
 
 // GetPostFormMap returns a map for a given form key, plus a boolean value
@@ -593,47 +601,46 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
 	return err
 }
 
-// Bind checks the Content-Type to select a binding engine automatically,
-// Depending the "Content-Type" header different bindings are used:
+// Bind checks the Method and Content-Type to select a binding engine automatically,
+// Depending on the "Content-Type" header different bindings are used, for example:
 //     "application/json" --> JSON binding
 //     "application/xml"  --> XML binding
-// otherwise --> returns an error.
 // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
 // It decodes the json payload into the struct specified as a pointer.
 // It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
-func (c *Context) Bind(obj interface{}) error {
+func (c *Context) Bind(obj any) error {
 	b := binding.Default(c.Request.Method, c.ContentType())
 	return c.MustBindWith(obj, b)
 }
 
 // BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
-func (c *Context) BindJSON(obj interface{}) error {
+func (c *Context) BindJSON(obj any) error {
 	return c.MustBindWith(obj, binding.JSON)
 }
 
 // BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
-func (c *Context) BindXML(obj interface{}) error {
+func (c *Context) BindXML(obj any) error {
 	return c.MustBindWith(obj, binding.XML)
 }
 
 // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
-func (c *Context) BindQuery(obj interface{}) error {
+func (c *Context) BindQuery(obj any) error {
 	return c.MustBindWith(obj, binding.Query)
 }
 
 // BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
-func (c *Context) BindYAML(obj interface{}) error {
+func (c *Context) BindYAML(obj any) error {
 	return c.MustBindWith(obj, binding.YAML)
 }
 
 // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
-func (c *Context) BindHeader(obj interface{}) error {
+func (c *Context) BindHeader(obj any) error {
 	return c.MustBindWith(obj, binding.Header)
 }
 
 // BindUri binds the passed struct pointer using binding.Uri.
 // It will abort the request with HTTP 400 if any error occurs.
-func (c *Context) BindUri(obj interface{}) error {
+func (c *Context) BindUri(obj any) error {
 	if err := c.ShouldBindUri(obj); err != nil {
 		c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
 		return err
@@ -644,7 +651,7 @@ func (c *Context) BindUri(obj interface{}) error {
 // MustBindWith binds the passed struct pointer using the specified binding engine.
 // It will abort the request with HTTP 400 if any error occurs.
 // See the binding package.
-func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {
+func (c *Context) MustBindWith(obj any, b binding.Binding) error {
 	if err := c.ShouldBindWith(obj, b); err != nil {
 		c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
 		return err
@@ -652,46 +659,45 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {
 	return nil
 }
 
-// ShouldBind checks the Content-Type to select a binding engine automatically,
-// Depending the "Content-Type" header different bindings are used:
+// ShouldBind checks the Method and Content-Type to select a binding engine automatically,
+// Depending on the "Content-Type" header different bindings are used, for example:
 //     "application/json" --> JSON binding
 //     "application/xml"  --> XML binding
-// otherwise --> returns an error
 // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
 // It decodes the json payload into the struct specified as a pointer.
-// Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid.
-func (c *Context) ShouldBind(obj interface{}) error {
+// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
+func (c *Context) ShouldBind(obj any) error {
 	b := binding.Default(c.Request.Method, c.ContentType())
 	return c.ShouldBindWith(obj, b)
 }
 
 // ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
-func (c *Context) ShouldBindJSON(obj interface{}) error {
+func (c *Context) ShouldBindJSON(obj any) error {
 	return c.ShouldBindWith(obj, binding.JSON)
 }
 
 // ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
-func (c *Context) ShouldBindXML(obj interface{}) error {
+func (c *Context) ShouldBindXML(obj any) error {
 	return c.ShouldBindWith(obj, binding.XML)
 }
 
 // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
-func (c *Context) ShouldBindQuery(obj interface{}) error {
+func (c *Context) ShouldBindQuery(obj any) error {
 	return c.ShouldBindWith(obj, binding.Query)
 }
 
 // ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
-func (c *Context) ShouldBindYAML(obj interface{}) error {
+func (c *Context) ShouldBindYAML(obj any) error {
 	return c.ShouldBindWith(obj, binding.YAML)
 }
 
 // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
-func (c *Context) ShouldBindHeader(obj interface{}) error {
+func (c *Context) ShouldBindHeader(obj any) error {
 	return c.ShouldBindWith(obj, binding.Header)
 }
 
 // ShouldBindUri binds the passed struct pointer using the specified binding engine.
-func (c *Context) ShouldBindUri(obj interface{}) error {
+func (c *Context) ShouldBindUri(obj any) error {
 	m := make(map[string][]string)
 	for _, v := range c.Params {
 		m[v.Key] = []string{v.Value}
@@ -701,7 +707,7 @@ func (c *Context) ShouldBindUri(obj interface{}) error {
 
 // ShouldBindWith binds the passed struct pointer using the specified binding engine.
 // See the binding package.
-func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
+func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
 	return b.Bind(c.Request, obj)
 }
 
@@ -710,7 +716,7 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
 //
 // NOTE: This method reads the body before binding. So you should use
 // ShouldBindWith for better performance if you need to call only once.
-func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {
+func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error) {
 	var body []byte
 	if cb, ok := c.Get(BodyBytesKey); ok {
 		if cbb, ok := cb.([]byte); ok {
@@ -727,20 +733,16 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e
 	return bb.BindBody(body, obj)
 }
 
-// ClientIP implements a best effort algorithm to return the real client IP.
+// ClientIP implements one best effort algorithm to return the real client IP.
 // It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
-// If it's it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
-// If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy,
-// the remote IP (coming form Request.RemoteAddr) is returned.
+// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
+// If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy,
+// the remote IP (coming from Request.RemoteAddr) is returned.
 func (c *Context) ClientIP() string {
-	// Check if we're running on a trusted platform
-	switch c.engine.TrustedPlatform {
-	case PlatformGoogleAppEngine:
-		if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
-			return addr
-		}
-	case PlatformCloudflare:
-		if addr := c.requestHeader("CF-Connecting-IP"); addr != "" {
+	// Check if we're running on a trusted platform, continue running backwards if error
+	if c.engine.TrustedPlatform != "" {
+		// Developers can define their own header of Trusted Platform or use predefined constants
+		if addr := c.requestHeader(c.engine.TrustedPlatform); addr != "" {
 			return addr
 		}
 	}
@@ -753,14 +755,18 @@ func (c *Context) ClientIP() string {
 		}
 	}
 
-	remoteIP, trusted := c.RemoteIP()
+	// It also checks if the remoteIP is a trusted proxy or not.
+	// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
+	// defined by Engine.SetTrustedProxies()
+	remoteIP := net.ParseIP(c.RemoteIP())
 	if remoteIP == nil {
 		return ""
 	}
+	trusted := c.engine.isTrustedProxy(remoteIP)
 
 	if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {
 		for _, headerName := range c.engine.RemoteIPHeaders {
-			ip, valid := validateHeader(c.requestHeader(headerName))
+			ip, valid := c.engine.validateHeader(c.requestHeader(headerName))
 			if valid {
 				return ip
 			}
@@ -770,51 +776,12 @@ func (c *Context) ClientIP() string {
 }
 
 // RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
-// It also checks if the remoteIP is a trusted proxy or not.
-// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
-// defined in Engine.TrustedProxies
-func (c *Context) RemoteIP() (net.IP, bool) {
+func (c *Context) RemoteIP() string {
 	ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
 	if err != nil {
-		return nil, false
-	}
-	remoteIP := net.ParseIP(ip)
-	if remoteIP == nil {
-		return nil, false
-	}
-
-	if c.engine.trustedCIDRs != nil {
-		for _, cidr := range c.engine.trustedCIDRs {
-			if cidr.Contains(remoteIP) {
-				return remoteIP, true
-			}
-		}
-	}
-
-	return remoteIP, false
-}
-
-func validateHeader(header string) (clientIP string, valid bool) {
-	if header == "" {
-		return "", false
-	}
-	items := strings.Split(header, ",")
-	for i, ipStr := range items {
-		ipStr = strings.TrimSpace(ipStr)
-		ip := net.ParseIP(ipStr)
-		if ip == nil {
-			return "", false
-		}
-
-		// We need to return the first IP in the list, but,
-		// we should not early return since we need to validate that
-		// the rest of the header is syntactically valid
-		if i == 0 {
-			clientIP = ipStr
-			valid = true
-		}
+		return ""
 	}
-	return
+	return ip
 }
 
 // ContentType returns the Content-Type header of the request.
@@ -858,7 +825,7 @@ func (c *Context) Status(code int) {
 	c.Writer.WriteHeader(code)
 }
 
-// Header is a intelligent shortcut for c.Writer.Header().Set(key, value).
+// Header is an intelligent shortcut for c.Writer.Header().Set(key, value).
 // It writes a header in the response.
 // If value == "", this method removes the header `c.Writer.Header().Del(key)`
 func (c *Context) Header(key, value string) {
@@ -874,7 +841,7 @@ func (c *Context) GetHeader(key string) string {
 	return c.requestHeader(key)
 }
 
-// GetRawData return stream data.
+// GetRawData returns stream data.
 func (c *Context) GetRawData() ([]byte, error) {
 	return ioutil.ReadAll(c.Request.Body)
 }
@@ -934,30 +901,30 @@ func (c *Context) Render(code int, r render.Render) {
 // HTML renders the HTTP template specified by its file name.
 // It also updates the HTTP code and sets the Content-Type as "text/html".
 // See http://golang.org/doc/articles/wiki/
-func (c *Context) HTML(code int, name string, obj interface{}) {
+func (c *Context) HTML(code int, name string, obj any) {
 	instance := c.engine.HTMLRender.Instance(name, obj)
 	c.Render(code, instance)
 }
 
 // IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.
 // It also sets the Content-Type as "application/json".
-// WARNING: we recommend to use this only for development purposes since printing pretty JSON is
+// WARNING: we recommend using this only for development purposes since printing pretty JSON is
 // more CPU and bandwidth consuming. Use Context.JSON() instead.
-func (c *Context) IndentedJSON(code int, obj interface{}) {
+func (c *Context) IndentedJSON(code int, obj any) {
 	c.Render(code, render.IndentedJSON{Data: obj})
 }
 
 // SecureJSON serializes the given struct as Secure JSON into the response body.
 // Default prepends "while(1)," to response body if the given struct is array values.
 // It also sets the Content-Type as "application/json".
-func (c *Context) SecureJSON(code int, obj interface{}) {
+func (c *Context) SecureJSON(code int, obj any) {
 	c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})
 }
 
 // JSONP serializes the given struct as JSON into the response body.
 // It adds padding to response body to request data from a server residing in a different domain than the client.
 // It also sets the Content-Type as "application/javascript".
-func (c *Context) JSONP(code int, obj interface{}) {
+func (c *Context) JSONP(code int, obj any) {
 	callback := c.DefaultQuery("callback", "")
 	if callback == "" {
 		c.Render(code, render.JSON{Data: obj})
@@ -968,44 +935,44 @@ func (c *Context) JSONP(code int, obj interface{}) {
 
 // JSON serializes the given struct as JSON into the response body.
 // It also sets the Content-Type as "application/json".
-func (c *Context) JSON(code int, obj interface{}) {
+func (c *Context) JSON(code int, obj any) {
 	c.Render(code, render.JSON{Data: obj})
 }
 
 // AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
 // It also sets the Content-Type as "application/json".
-func (c *Context) AsciiJSON(code int, obj interface{}) {
+func (c *Context) AsciiJSON(code int, obj any) {
 	c.Render(code, render.AsciiJSON{Data: obj})
 }
 
 // PureJSON serializes the given struct as JSON into the response body.
 // PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
-func (c *Context) PureJSON(code int, obj interface{}) {
+func (c *Context) PureJSON(code int, obj any) {
 	c.Render(code, render.PureJSON{Data: obj})
 }
 
 // XML serializes the given struct as XML into the response body.
 // It also sets the Content-Type as "application/xml".
-func (c *Context) XML(code int, obj interface{}) {
+func (c *Context) XML(code int, obj any) {
 	c.Render(code, render.XML{Data: obj})
 }
 
 // YAML serializes the given struct as YAML into the response body.
-func (c *Context) YAML(code int, obj interface{}) {
+func (c *Context) YAML(code int, obj any) {
 	c.Render(code, render.YAML{Data: obj})
 }
 
 // ProtoBuf serializes the given struct as ProtoBuf into the response body.
-func (c *Context) ProtoBuf(code int, obj interface{}) {
+func (c *Context) ProtoBuf(code int, obj any) {
 	c.Render(code, render.ProtoBuf{Data: obj})
 }
 
 // String writes the given string into the response body.
-func (c *Context) String(code int, format string, values ...interface{}) {
+func (c *Context) String(code int, format string, values ...any) {
 	c.Render(code, render.String{Format: format, Data: values})
 }
 
-// Redirect returns a HTTP redirect to the specific location.
+// Redirect returns an HTTP redirect to the specific location.
 func (c *Context) Redirect(code int, location string) {
 	c.Render(-1, render.Redirect{
 		Code:     code,
@@ -1051,12 +1018,16 @@ func (c *Context) FileFromFS(filepath string, fs http.FileSystem) {
 // FileAttachment writes the specified file into the body stream in an efficient way
 // On the client side, the file will typically be downloaded with the given filename
 func (c *Context) FileAttachment(filepath, filename string) {
-	c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
+	if isASCII(filename) {
+		c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
+	} else {
+		c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename))
+	}
 	http.ServeFile(c.Writer, c.Request, filepath)
 }
 
 // SSEvent writes a Server-Sent Event into the body stream.
-func (c *Context) SSEvent(name string, message interface{}) {
+func (c *Context) SSEvent(name string, message any) {
 	c.Render(-1, sse.Event{
 		Event: name,
 		Data:  message,
@@ -1090,14 +1061,14 @@ func (c *Context) Stream(step func(w io.Writer) bool) bool {
 type Negotiate struct {
 	Offered  []string
 	HTMLName string
-	HTMLData interface{}
-	JSONData interface{}
-	XMLData  interface{}
-	YAMLData interface{}
-	Data     interface{}
+	HTMLData any
+	JSONData any
+	XMLData  any
+	YAMLData any
+	Data     any
 }
 
-// Negotiate calls different Render according acceptable Accept format.
+// Negotiate calls different Render according to acceptable Accept format.
 func (c *Context) Negotiate(code int, config Negotiate) {
 	switch c.NegotiateFormat(config.Offered...) {
 	case binding.MIMEJSON:
@@ -1161,28 +1132,34 @@ func (c *Context) SetAccepted(formats ...string) {
 /***** GOLANG.ORG/X/NET/CONTEXT *****/
 /************************************/
 
-// Deadline always returns that there is no deadline (ok==false),
-// maybe you want to use Request.Context().Deadline() instead.
+// Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
 func (c *Context) Deadline() (deadline time.Time, ok bool) {
-	return
+	if c.Request == nil || c.Request.Context() == nil {
+		return
+	}
+	return c.Request.Context().Deadline()
 }
 
-// Done always returns nil (chan which will wait forever),
-// if you want to abort your work when the connection was closed
-// you should use Request.Context().Done() instead.
+// Done returns nil (chan which will wait forever) when c.Request has no Context.
 func (c *Context) Done() <-chan struct{} {
-	return nil
+	if c.Request == nil || c.Request.Context() == nil {
+		return nil
+	}
+	return c.Request.Context().Done()
 }
 
-// Err always returns nil, maybe you want to use Request.Context().Err() instead.
+// Err returns nil when c.Request has no Context.
 func (c *Context) Err() error {
-	return nil
+	if c.Request == nil || c.Request.Context() == nil {
+		return nil
+	}
+	return c.Request.Context().Err()
 }
 
 // Value returns the value associated with this context for key, or nil
 // if no value is associated with key. Successive calls to Value with
 // the same key returns the same result.
-func (c *Context) Value(key interface{}) interface{} {
+func (c *Context) Value(key any) any {
 	if key == 0 {
 		return c.Request
 	}
diff --git a/debug.go b/debug.go
index ed31386..d8d6c8e 100644
--- a/debug.go
+++ b/debug.go
@@ -12,7 +12,7 @@ import (
 	"strings"
 )
 
-const ginSupportMinGoVer = 13
+const ginSupportMinGoVer = 14
 
 // IsDebugging returns true if the framework is running in debug mode.
 // Use SetMode(gin.ReleaseMode) to disable debug mode.
@@ -47,7 +47,7 @@ func debugPrintLoadTemplate(tmpl *template.Template) {
 	}
 }
 
-func debugPrint(format string, values ...interface{}) {
+func debugPrint(format string, values ...any) {
 	if IsDebugging() {
 		if !strings.HasSuffix(format, "\n") {
 			format += "\n"
@@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) {
 
 func debugPrintWARNINGDefault() {
 	if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
-		debugPrint(`[WARNING] Now Gin requires Go 1.13+.
+		debugPrint(`[WARNING] Now Gin requires Go 1.14+.
 
 `)
 	}
diff --git a/deprecated.go b/deprecated.go
index ab44742..004e13d 100644
--- a/deprecated.go
+++ b/deprecated.go
@@ -12,7 +12,7 @@ import (
 
 // BindWith binds the passed struct pointer using the specified binding engine.
 // See the binding package.
-func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
+func (c *Context) BindWith(obj any, b binding.Binding) error {
 	log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
 	be deprecated, please check issue #662 and either use MustBindWith() if you
 	want HTTP 400 to be automatically returned if any error occur, or use
diff --git a/errors.go b/errors.go
index 0f276c1..578e857 100644
--- a/errors.go
+++ b/errors.go
@@ -34,7 +34,7 @@ const (
 type Error struct {
 	Err  error
 	Type ErrorType
-	Meta interface{}
+	Meta any
 }
 
 type errorMsgs []*Error
@@ -48,13 +48,13 @@ func (msg *Error) SetType(flags ErrorType) *Error {
 }
 
 // SetMeta sets the error's meta data.
-func (msg *Error) SetMeta(data interface{}) *Error {
+func (msg *Error) SetMeta(data any) *Error {
 	msg.Meta = data
 	return msg
 }
 
 // JSON creates a properly formatted JSON
-func (msg *Error) JSON() interface{} {
+func (msg *Error) JSON() any {
 	jsonData := H{}
 	if msg.Meta != nil {
 		value := reflect.ValueOf(msg.Meta)
@@ -122,7 +122,7 @@ func (a errorMsgs) Last() *Error {
 	return nil
 }
 
-// Errors returns an array will all the error messages.
+// Errors returns an array with all the error messages.
 // Example:
 // 		c.Error(errors.New("first"))
 // 		c.Error(errors.New("second"))
@@ -139,14 +139,14 @@ func (a errorMsgs) Errors() []string {
 	return errorStrings
 }
 
-func (a errorMsgs) JSON() interface{} {
+func (a errorMsgs) JSON() any {
 	switch length := len(a); length {
 	case 0:
 		return nil
 	case 1:
 		return a.Last().JSON()
 	default:
-		jsonData := make([]interface{}, length)
+		jsonData := make([]any, length)
 		for i, err := range a {
 			jsonData[i] = err.JSON()
 		}
diff --git a/gin.go b/gin.go
index 6ab2be6..c2e95f2 100644
--- a/gin.go
+++ b/gin.go
@@ -16,6 +16,8 @@ import (
 
 	"github.com/gin-gonic/gin/internal/bytesconv"
 	"github.com/gin-gonic/gin/render"
+	"golang.org/x/net/http2"
+	"golang.org/x/net/http2/h2c"
 )
 
 const defaultMultipartMemory = 32 << 20 // 32 MB
@@ -27,13 +29,24 @@ var (
 
 var defaultPlatform string
 
+var defaultTrustedCIDRs = []*net.IPNet{
+	{ // 0.0.0.0/0 (IPv4)
+		IP:   net.IP{0x0, 0x0, 0x0, 0x0},
+		Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
+	},
+	{ // ::/0 (IPv6)
+		IP:   net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
+		Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
+	},
+}
+
 // HandlerFunc defines the handler used by gin middleware as return value.
 type HandlerFunc func(*Context)
 
-// HandlersChain defines a HandlerFunc array.
+// HandlersChain defines a HandlerFunc slice.
 type HandlersChain []HandlerFunc
 
-// Last returns the last handler in the chain. ie. the last handler is the main one.
+// Last returns the last handler in the chain. i.e. the last handler is the main one.
 func (c HandlersChain) Last() HandlerFunc {
 	if length := len(c); length > 0 {
 		return c[length-1]
@@ -49,17 +62,17 @@ type RouteInfo struct {
 	HandlerFunc HandlerFunc
 }
 
-// RoutesInfo defines a RouteInfo array.
+// RoutesInfo defines a RouteInfo slice.
 type RoutesInfo []RouteInfo
 
 // Trusted platforms
 const (
-	// When running on Google App Engine. Trust X-Appengine-Remote-Addr
+	// PlatformGoogleAppEngine when running on Google App Engine. Trust X-Appengine-Remote-Addr
 	// for determining the client's IP
-	PlatformGoogleAppEngine = "google-app-engine"
-	// When using Cloudflare's CDN. Trust CF-Connecting-IP for determining
+	PlatformGoogleAppEngine = "X-Appengine-Remote-Addr"
+	// PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
 	// the client's IP
-	PlatformCloudflare = "cloudflare"
+	PlatformCloudflare = "CF-Connecting-IP"
 )
 
 // Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
@@ -67,14 +80,14 @@ const (
 type Engine struct {
 	RouterGroup
 
-	// Enables automatic redirection if the current route can't be matched but a
+	// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
 	// handler for the path with (without) the trailing slash exists.
 	// For example if /foo/ is requested but a route only exists for /foo, the
 	// client is redirected to /foo with http status code 301 for GET requests
 	// and 307 for all other request methods.
 	RedirectTrailingSlash bool
 
-	// If enabled, the router tries to fix the current request path, if no
+	// RedirectFixedPath if enabled, the router tries to fix the current request path, if no
 	// handle is registered for it.
 	// First superfluous path elements like ../ or // are removed.
 	// Afterwards the router does a case-insensitive lookup of the cleaned path.
@@ -85,7 +98,7 @@ type Engine struct {
 	// RedirectTrailingSlash is independent of this option.
 	RedirectFixedPath bool
 
-	// If enabled, the router checks if another method is allowed for the
+	// HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
 	// current route, if the current request can not be routed.
 	// If this is the case, the request is answered with 'Method Not Allowed'
 	// and HTTP status code 405.
@@ -93,21 +106,22 @@ type Engine struct {
 	// handler.
 	HandleMethodNotAllowed bool
 
-	// If enabled, client IP will be parsed from the request's headers that
+	// ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that
 	// match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was
 	// fetched, it falls back to the IP obtained from
 	// `(*gin.Context).Request.RemoteAddr`.
 	ForwardedByClientIP bool
 
-	// DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.GoogleAppEngine` INSTEAD
+	// AppEngine was deprecated.
+	// Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD
 	// #726 #755 If enabled, it will trust some headers starting with
 	// 'X-AppEngine...' for better integration with that PaaS.
 	AppEngine bool
 
-	// If enabled, the url.RawPath will be used to find parameters.
+	// UseRawPath if enabled, the url.RawPath will be used to find parameters.
 	UseRawPath bool
 
-	// If true, the path value will be unescaped.
+	// UnescapePathValues if true, the path value will be unescaped.
 	// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
 	// as url.Path gonna be used, which is already unescaped.
 	UnescapePathValues bool
@@ -116,26 +130,23 @@ type Engine struct {
 	// See the PR #1817 and issue #1644
 	RemoveExtraSlash bool
 
-	// List of headers used to obtain the client IP when
+	// RemoteIPHeaders list of headers used to obtain the client IP when
 	// `(*gin.Engine).ForwardedByClientIP` is `true` and
 	// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
-	// network origins of `(*gin.Engine).TrustedProxies`.
+	// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
 	RemoteIPHeaders []string
 
-	// List of network origins (IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
-	// IPv6 CIDRs) from which to trust request's headers that contain
-	// alternative client IP when `(*gin.Engine).ForwardedByClientIP` is
-	// `true`.
-	TrustedProxies []string
-
-	// If set to a constant of value gin.Platform*, trusts the headers set by
+	// TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by
 	// that platform, for example to determine the client IP
 	TrustedPlatform string
 
-	// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
+	// MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
 	// method call.
 	MaxMultipartMemory int64
 
+	// UseH2C enable h2c support.
+	UseH2C bool
+
 	delims           render.Delims
 	secureJSONPrefix string
 	HTMLRender       render.HTMLRender
@@ -147,13 +158,15 @@ type Engine struct {
 	pool             sync.Pool
 	trees            methodTrees
 	maxParams        uint16
+	maxSections      uint16
+	trustedProxies   []string
 	trustedCIDRs     []*net.IPNet
 }
 
 var _ IRouter = &Engine{}
 
 // New returns a new blank Engine instance without any middleware attached.
-// By default the configuration is:
+// By default, the configuration is:
 // - RedirectTrailingSlash:  true
 // - RedirectFixedPath:      false
 // - HandleMethodNotAllowed: false
@@ -174,7 +187,6 @@ func New() *Engine {
 		HandleMethodNotAllowed: false,
 		ForwardedByClientIP:    true,
 		RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"},
-		TrustedProxies:         []string{"0.0.0.0/0"},
 		TrustedPlatform:        defaultPlatform,
 		UseRawPath:             false,
 		RemoveExtraSlash:       false,
@@ -183,9 +195,11 @@ func New() *Engine {
 		trees:                  make(methodTrees, 0, 9),
 		delims:                 render.Delims{Left: "{{", Right: "}}"},
 		secureJSONPrefix:       "while(1);",
+		trustedProxies:         []string{"0.0.0.0/0"},
+		trustedCIDRs:           defaultTrustedCIDRs,
 	}
 	engine.RouterGroup.engine = engine
-	engine.pool.New = func() interface{} {
+	engine.pool.New = func() any {
 		return engine.allocateContext()
 	}
 	return engine
@@ -199,12 +213,22 @@ func Default() *Engine {
 	return engine
 }
 
+func (engine *Engine) Handler() http.Handler {
+	if !engine.UseH2C {
+		return engine
+	}
+
+	h2s := &http2.Server{}
+	return h2c.NewHandler(engine, h2s)
+}
+
 func (engine *Engine) allocateContext() *Context {
 	v := make(Params, 0, engine.maxParams)
-	return &Context{engine: engine, params: &v}
+	skippedNodes := make([]skippedNode, 0, engine.maxSections)
+	return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
 }
 
-// Delims sets template left and right delims and returns a Engine instance.
+// Delims sets template left and right delims and returns an Engine instance.
 func (engine *Engine) Delims(left, right string) *Engine {
 	engine.delims = render.Delims{Left: left, Right: right}
 	return engine
@@ -258,19 +282,19 @@ func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
 	engine.FuncMap = funcMap
 }
 
-// NoRoute adds handlers for NoRoute. It return a 404 code by default.
+// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
 func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
 	engine.noRoute = handlers
 	engine.rebuild404Handlers()
 }
 
-// NoMethod sets the handlers called when... TODO.
+// NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.
 func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
 	engine.noMethod = handlers
 	engine.rebuild405Handlers()
 }
 
-// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
+// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
 // included in the handlers chain for every single request. Even 404, 405, static files...
 // For example, this is the right place for a logger or error management middleware.
 func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
@@ -307,6 +331,10 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
 	if paramsCount := countParams(path); paramsCount > engine.maxParams {
 		engine.maxParams = paramsCount
 	}
+
+	if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
+		engine.maxSections = sectionsCount
+	}
 }
 
 // Routes returns a slice of registered routes, including some useful information, such as:
@@ -341,24 +369,24 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
 func (engine *Engine) Run(addr ...string) (err error) {
 	defer func() { debugPrintError(err) }()
 
-	err = engine.parseTrustedProxies()
-	if err != nil {
-		return err
+	if engine.isUnsafeTrustedProxies() {
+		debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+			"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
 	}
 
 	address := resolveAddress(addr)
 	debugPrint("Listening and serving HTTP on %s\n", address)
-	err = http.ListenAndServe(address, engine)
+	err = http.ListenAndServe(address, engine.Handler())
 	return
 }
 
 func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
-	if engine.TrustedProxies == nil {
+	if engine.trustedProxies == nil {
 		return nil, nil
 	}
 
-	cidr := make([]*net.IPNet, 0, len(engine.TrustedProxies))
-	for _, trustedProxy := range engine.TrustedProxies {
+	cidr := make([]*net.IPNet, 0, len(engine.trustedProxies))
+	for _, trustedProxy := range engine.trustedProxies {
 		if !strings.Contains(trustedProxy, "/") {
 			ip := parseIP(trustedProxy)
 			if ip == nil {
@@ -381,19 +409,66 @@ func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
 	return cidr, nil
 }
 
-// SetTrustedProxies  set Engine.TrustedProxies
+// SetTrustedProxies set a list of network origins (IPv4 addresses,
+// IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust
+// request's headers that contain alternative client IP when
+// `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies`
+// feature is enabled by default, and it also trusts all proxies
+// by default. If you want to disable this feature, use
+// Engine.SetTrustedProxies(nil), then Context.ClientIP() will
+// return the remote address directly.
 func (engine *Engine) SetTrustedProxies(trustedProxies []string) error {
-	engine.TrustedProxies = trustedProxies
+	engine.trustedProxies = trustedProxies
 	return engine.parseTrustedProxies()
 }
 
-// parseTrustedProxies parse Engine.TrustedProxies to Engine.trustedCIDRs
+// isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true)
+func (engine *Engine) isUnsafeTrustedProxies() bool {
+	return engine.isTrustedProxy(net.ParseIP("0.0.0.0")) || engine.isTrustedProxy(net.ParseIP("::"))
+}
+
+// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
 func (engine *Engine) parseTrustedProxies() error {
 	trustedCIDRs, err := engine.prepareTrustedCIDRs()
 	engine.trustedCIDRs = trustedCIDRs
 	return err
 }
 
+// isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs
+func (engine *Engine) isTrustedProxy(ip net.IP) bool {
+	if engine.trustedCIDRs == nil {
+		return false
+	}
+	for _, cidr := range engine.trustedCIDRs {
+		if cidr.Contains(ip) {
+			return true
+		}
+	}
+	return false
+}
+
+// validateHeader will parse X-Forwarded-For header and return the trusted client IP address
+func (engine *Engine) validateHeader(header string) (clientIP string, valid bool) {
+	if header == "" {
+		return "", false
+	}
+	items := strings.Split(header, ",")
+	for i := len(items) - 1; i >= 0; i-- {
+		ipStr := strings.TrimSpace(items[i])
+		ip := net.ParseIP(ipStr)
+		if ip == nil {
+			break
+		}
+
+		// X-Forwarded-For is appended by proxy
+		// Check IPs in reverse order and stop when find untrusted proxy
+		if (i == 0) || (!engine.isTrustedProxy(ip)) {
+			return ipStr, true
+		}
+	}
+	return "", false
+}
+
 // parseIP parse a string representation of an IP and returns a net.IP with the
 // minimum byte representation or nil if input is invalid.
 func parseIP(ip string) net.IP {
@@ -415,25 +490,25 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
 	debugPrint("Listening and serving HTTPS on %s\n", addr)
 	defer func() { debugPrintError(err) }()
 
-	err = engine.parseTrustedProxies()
-	if err != nil {
-		return err
+	if engine.isUnsafeTrustedProxies() {
+		debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+			"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
 	}
 
-	err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
+	err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
 	return
 }
 
 // RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
-// through the specified unix socket (ie. a file).
+// through the specified unix socket (i.e. a file).
 // Note: this method will block the calling goroutine indefinitely unless an error happens.
 func (engine *Engine) RunUnix(file string) (err error) {
 	debugPrint("Listening and serving HTTP on unix:/%s", file)
 	defer func() { debugPrintError(err) }()
 
-	err = engine.parseTrustedProxies()
-	if err != nil {
-		return err
+	if engine.isUnsafeTrustedProxies() {
+		debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+			"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
 	}
 
 	listener, err := net.Listen("unix", file)
@@ -443,7 +518,7 @@ func (engine *Engine) RunUnix(file string) (err error) {
 	defer listener.Close()
 	defer os.Remove(file)
 
-	err = http.Serve(listener, engine)
+	err = http.Serve(listener, engine.Handler())
 	return
 }
 
@@ -454,9 +529,9 @@ func (engine *Engine) RunFd(fd int) (err error) {
 	debugPrint("Listening and serving HTTP on fd@%d", fd)
 	defer func() { debugPrintError(err) }()
 
-	err = engine.parseTrustedProxies()
-	if err != nil {
-		return err
+	if engine.isUnsafeTrustedProxies() {
+		debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+			"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
 	}
 
 	f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
@@ -475,12 +550,12 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
 	debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
 	defer func() { debugPrintError(err) }()
 
-	err = engine.parseTrustedProxies()
-	if err != nil {
-		return err
+	if engine.isUnsafeTrustedProxies() {
+		debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+			"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
 	}
 
-	err = http.Serve(listener, engine)
+	err = http.Serve(listener, engine.Handler())
 	return
 }
 
@@ -496,9 +571,9 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	engine.pool.Put(c)
 }
 
-// HandleContext re-enter a context that has been rewritten.
+// HandleContext re-enters a context that has been rewritten.
 // This can be done by setting c.Request.URL.Path to your new target.
-// Disclaimer: You can loop yourself to death with this, use wisely.
+// Disclaimer: You can loop yourself to deal with this, use wisely.
 func (engine *Engine) HandleContext(c *Context) {
 	oldIndexValue := c.index
 	c.reset()
@@ -528,7 +603,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
 		}
 		root := t[i].root
 		// Find route in tree
-		value := root.getValue(rPath, c.params, unescape)
+		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
 		if value.params != nil {
 			c.Params = *value.params
 		}
@@ -556,7 +631,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
 			if tree.method == httpMethod {
 				continue
 			}
-			if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
+			if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
 				c.handlers = engine.allNoMethod
 				serveError(c, http.StatusMethodNotAllowed, default405Body)
 				return
diff --git a/ginS/gins.go b/ginS/gins.go
index 3080fd3..0802e08 100644
--- a/ginS/gins.go
+++ b/ginS/gins.go
@@ -37,7 +37,7 @@ func SetHTMLTemplate(templ *template.Template) {
 	engine().SetHTMLTemplate(templ)
 }
 
-// NoRoute adds handlers for NoRoute. It return a 404 code by default.
+// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
 func NoRoute(handlers ...gin.HandlerFunc) {
 	engine().NoRoute(handlers...)
 }
@@ -118,7 +118,7 @@ func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
 	return engine().StaticFS(relativePath, fs)
 }
 
-// Use attaches a global middleware to the router. ie. the middlewares attached though Use() will be
+// Use attaches a global middleware to the router. i.e. the middlewares attached through Use() will be
 // included in the handlers chain for every single request. Even 404, 405, static files...
 // For example, this is the right place for a logger or error management middleware.
 func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
@@ -145,7 +145,7 @@ func RunTLS(addr, certFile, keyFile string) (err error) {
 }
 
 // RunUnix attaches to a http.Server and starts listening and serving HTTP requests
-// through the specified unix socket (ie. a file)
+// through the specified unix socket (i.e. a file)
 // Note: this method will block the calling goroutine indefinitely unless an error happens.
 func RunUnix(file string) (err error) {
 	return engine().RunUnix(file)
diff --git a/go.mod b/go.mod
index 9484b26..bfb462b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,15 +1,30 @@
 module github.com/gin-gonic/gin
 
-go 1.13
+go 1.18
 
 require (
 	github.com/gin-contrib/sse v0.1.0
-	github.com/go-playground/validator/v10 v10.6.1
-	github.com/goccy/go-json v0.5.1
-	github.com/golang/protobuf v1.3.3
-	github.com/json-iterator/go v1.1.9
-	github.com/mattn/go-isatty v0.0.12
-	github.com/stretchr/testify v1.4.0
-	github.com/ugorji/go/codec v1.2.6
-	gopkg.in/yaml.v2 v2.2.8
+	github.com/go-playground/validator/v10 v10.10.0
+	github.com/goccy/go-json v0.9.6
+	github.com/json-iterator/go v1.1.12
+	github.com/mattn/go-isatty v0.0.14
+	github.com/stretchr/testify v1.7.1
+	github.com/ugorji/go/codec v1.2.7
+	golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
+	google.golang.org/protobuf v1.28.0
+	gopkg.in/yaml.v2 v2.4.0
+)
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/go-playground/locales v0.14.0 // indirect
+	github.com/go-playground/universal-translator v0.18.0 // indirect
+	github.com/leodido/go-urn v1.2.1 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
+	golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
+	golang.org/x/text v0.3.6 // indirect
+	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )
diff --git a/go.sum b/go.sum
index e61ef90..983b50a 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,4 @@
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 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=
@@ -5,51 +6,77 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
-github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
-github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
-github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
-github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0k417gfr+V6u4I=
-github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
-github.com/goccy/go-json v0.5.1 h1:R9UYTOUvo7eIY9aeDMZ4L6OVtHaSr1k2No9W6MKjXrA=
-github.com/goccy/go-json v0.5.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
-github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
+github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
+github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
+github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
+github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
+github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
+github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
+github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
-github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
-github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
-github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
-github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
-github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
+github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
+github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+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-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/logger.go b/logger.go
index 22138a8..1f9d63a 100644
--- a/logger.go
+++ b/logger.go
@@ -44,7 +44,7 @@ type LoggerConfig struct {
 	// Optional. Default value is gin.DefaultWriter.
 	Output io.Writer
 
-	// SkipPaths is a url path array which logs are not written.
+	// SkipPaths is an url path array which logs are not written.
 	// Optional.
 	SkipPaths []string
 }
@@ -70,12 +70,12 @@ type LogFormatterParams struct {
 	Path string
 	// ErrorMessage is set if error has occurred in processing the request.
 	ErrorMessage string
-	// isTerm shows whether does gin's output descriptor refers to a terminal.
+	// isTerm shows whether gin's output descriptor refers to a terminal.
 	isTerm bool
 	// BodySize is the size of the Response Body
 	BodySize int
 	// Keys are the keys set on the request's context.
-	Keys map[string]interface{}
+	Keys map[string]any
 }
 
 // StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
@@ -161,12 +161,12 @@ func ForceConsoleColor() {
 	consoleColorMode = forceColor
 }
 
-// ErrorLogger returns a handlerfunc for any error type.
+// ErrorLogger returns a HandlerFunc for any error type.
 func ErrorLogger() HandlerFunc {
 	return ErrorLoggerT(ErrorTypeAny)
 }
 
-// ErrorLoggerT returns a handlerfunc for a given error type.
+// ErrorLoggerT returns a HandlerFunc for a given error type.
 func ErrorLoggerT(typ ErrorType) HandlerFunc {
 	return func(c *Context) {
 		c.Next()
@@ -178,7 +178,7 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc {
 }
 
 // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
-// By default gin.DefaultWriter = os.Stdout.
+// By default, gin.DefaultWriter = os.Stdout.
 func Logger() HandlerFunc {
 	return LoggerWithConfig(LoggerConfig{})
 }
diff --git a/mode.go b/mode.go
index c8813af..1fb994b 100644
--- a/mode.go
+++ b/mode.go
@@ -41,8 +41,10 @@ var DefaultWriter io.Writer = os.Stdout
 // DefaultErrorWriter is the default io.Writer used by Gin to debug errors
 var DefaultErrorWriter io.Writer = os.Stderr
 
-var ginMode = debugCode
-var modeName = DebugMode
+var (
+	ginMode  = debugCode
+	modeName = DebugMode
+)
 
 func init() {
 	mode := os.Getenv(EnvGinMode)
@@ -86,7 +88,7 @@ func EnableJsonDecoderDisallowUnknownFields() {
 	binding.EnableDecoderDisallowUnknownFields = true
 }
 
-// Mode returns currently gin mode.
+// Mode returns current gin mode.
 func Mode() string {
 	return modeName
 }
diff --git a/recovery.go b/recovery.go
index 3101fe2..3efe146 100644
--- a/recovery.go
+++ b/recovery.go
@@ -6,6 +6,7 @@ package gin
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -27,7 +28,7 @@ var (
 )
 
 // RecoveryFunc defines the function passable to CustomRecovery.
-type RecoveryFunc func(c *Context, err interface{})
+type RecoveryFunc func(c *Context, err any)
 
 // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
 func Recovery() HandlerFunc {
@@ -60,7 +61,8 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
 				// condition that warrants a panic stack trace.
 				var brokenPipe bool
 				if ne, ok := err.(*net.OpError); ok {
-					if se, ok := ne.Err.(*os.SyscallError); ok {
+					var se *os.SyscallError
+					if errors.As(ne, &se) {
 						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
 							brokenPipe = true
 						}
@@ -100,7 +102,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
 	}
 }
 
-func defaultHandleRecovery(c *Context, err interface{}) {
+func defaultHandleRecovery(c *Context, err any) {
 	c.AbortWithStatus(http.StatusInternalServerError)
 }
 
@@ -153,7 +155,7 @@ func function(pc uintptr) []byte {
 	//	runtime/debug.*T·ptrmethod
 	// and want
 	//	*T.ptrmethod
-	// Also the package path might contains dot (e.g. code.google.com/...),
+	// Also the package path might contain dot (e.g. code.google.com/...),
 	// so first eliminate the path prefix
 	if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
 		name = name[lastSlash+1:]
diff --git a/render/any.go b/render/any.go
new file mode 100644
index 0000000..57349be
--- /dev/null
+++ b/render/any.go
@@ -0,0 +1,10 @@
+// Copyright 2021 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+//go:build !go1.18
+// +build !go1.18
+
+package render
+
+type any = interface{}
diff --git a/render/html.go b/render/html.go
index 6696ece..bdfaf11 100644
--- a/render/html.go
+++ b/render/html.go
@@ -20,7 +20,7 @@ type Delims struct {
 // HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
 type HTMLRender interface {
 	// Instance returns an HTML instance.
-	Instance(string, interface{}) Render
+	Instance(string, any) Render
 }
 
 // HTMLProduction contains template reference and its delims.
@@ -41,13 +41,13 @@ type HTMLDebug struct {
 type HTML struct {
 	Template *template.Template
 	Name     string
-	Data     interface{}
+	Data     any
 }
 
 var htmlContentType = []string{"text/html; charset=utf-8"}
 
 // Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.
-func (r HTMLProduction) Instance(name string, data interface{}) Render {
+func (r HTMLProduction) Instance(name string, data any) Render {
 	return HTML{
 		Template: r.Template,
 		Name:     name,
@@ -56,7 +56,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render {
 }
 
 // Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.
-func (r HTMLDebug) Instance(name string, data interface{}) Render {
+func (r HTMLDebug) Instance(name string, data any) Render {
 	return HTML{
 		Template: r.loadTemplate(),
 		Name:     name,
diff --git a/render/json.go b/render/json.go
index e25415b..0a7dcef 100644
--- a/render/json.go
+++ b/render/json.go
@@ -16,40 +16,40 @@ import (
 
 // JSON contains the given interface object.
 type JSON struct {
-	Data interface{}
+	Data any
 }
 
 // IndentedJSON contains the given interface object.
 type IndentedJSON struct {
-	Data interface{}
+	Data any
 }
 
 // SecureJSON contains the given interface object and its prefix.
 type SecureJSON struct {
 	Prefix string
-	Data   interface{}
+	Data   any
 }
 
 // JsonpJSON contains the given interface object its callback.
 type JsonpJSON struct {
 	Callback string
-	Data     interface{}
+	Data     any
 }
 
 // AsciiJSON contains the given interface object.
 type AsciiJSON struct {
-	Data interface{}
+	Data any
 }
 
 // PureJSON contains the given interface object.
 type PureJSON struct {
-	Data interface{}
+	Data any
 }
 
 var (
 	jsonContentType      = []string{"application/json; charset=utf-8"}
 	jsonpContentType     = []string{"application/javascript; charset=utf-8"}
-	jsonAsciiContentType = []string{"application/json"}
+	jsonASCIIContentType = []string{"application/json"}
 )
 
 // Render (JSON) writes data with custom ContentType.
@@ -66,7 +66,7 @@ func (r JSON) WriteContentType(w http.ResponseWriter) {
 }
 
 // WriteJSON marshals the given interface object and writes it with custom ContentType.
-func WriteJSON(w http.ResponseWriter, obj interface{}) error {
+func WriteJSON(w http.ResponseWriter, obj any) error {
 	writeContentType(w, jsonContentType)
 	jsonBytes, err := json.Marshal(obj)
 	if err != nil {
@@ -102,8 +102,7 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
 	// if the jsonBytes is array values
 	if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
 		bytesconv.StringToBytes("]")) {
-		_, err = w.Write(bytesconv.StringToBytes(r.Prefix))
-		if err != nil {
+		if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil {
 			return err
 		}
 	}
@@ -130,20 +129,19 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
 	}
 
 	callback := template.JSEscapeString(r.Callback)
-	_, err = w.Write(bytesconv.StringToBytes(callback))
-	if err != nil {
+	if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil {
 		return err
 	}
-	_, err = w.Write(bytesconv.StringToBytes("("))
-	if err != nil {
+
+	if _, err = w.Write(bytesconv.StringToBytes("(")); err != nil {
 		return err
 	}
-	_, err = w.Write(ret)
-	if err != nil {
+
+	if _, err = w.Write(ret); err != nil {
 		return err
 	}
-	_, err = w.Write(bytesconv.StringToBytes(");"))
-	if err != nil {
+
+	if _, err = w.Write(bytesconv.StringToBytes(");")); err != nil {
 		return err
 	}
 
@@ -178,7 +176,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
 
 // WriteContentType (AsciiJSON) writes JSON ContentType.
 func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
-	writeContentType(w, jsonAsciiContentType)
+	writeContentType(w, jsonASCIIContentType)
 }
 
 // Render (PureJSON) writes custom ContentType and encodes the given interface object.
diff --git a/render/msgpack.go b/render/msgpack.go
index 7f17ca4..2c0e9aa 100644
--- a/render/msgpack.go
+++ b/render/msgpack.go
@@ -21,7 +21,7 @@ var (
 
 // MsgPack contains the given interface object.
 type MsgPack struct {
-	Data interface{}
+	Data any
 }
 
 var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
@@ -37,7 +37,7 @@ func (r MsgPack) Render(w http.ResponseWriter) error {
 }
 
 // WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
-func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
+func WriteMsgPack(w http.ResponseWriter, obj any) error {
 	writeContentType(w, msgpackContentType)
 	var mh codec.MsgpackHandle
 	return codec.NewEncoder(w, &mh).Encode(obj)
diff --git a/render/protobuf.go b/render/protobuf.go
index 15aca99..2e57f5c 100644
--- a/render/protobuf.go
+++ b/render/protobuf.go
@@ -7,12 +7,12 @@ package render
 import (
 	"net/http"
 
-	"github.com/golang/protobuf/proto"
+	"google.golang.org/protobuf/proto"
 )
 
 // ProtoBuf contains the given interface object.
 type ProtoBuf struct {
-	Data interface{}
+	Data any
 }
 
 var protobufContentType = []string{"application/x-protobuf"}
diff --git a/render/text.go b/render/text.go
index 461b720..b77a776 100644
--- a/render/text.go
+++ b/render/text.go
@@ -14,7 +14,7 @@ import (
 // String contains the given interface object slice and its format.
 type String struct {
 	Format string
-	Data   []interface{}
+	Data   []any
 }
 
 var plainContentType = []string{"text/plain; charset=utf-8"}
@@ -30,7 +30,7 @@ func (r String) WriteContentType(w http.ResponseWriter) {
 }
 
 // WriteString writes data according to its format and write custom ContentType.
-func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) {
+func WriteString(w http.ResponseWriter, format string, data []any) (err error) {
 	writeContentType(w, plainContentType)
 	if len(data) > 0 {
 		_, err = fmt.Fprintf(w, format, data...)
diff --git a/render/xml.go b/render/xml.go
index cc5390a..c396a5a 100644
--- a/render/xml.go
+++ b/render/xml.go
@@ -11,7 +11,7 @@ import (
 
 // XML contains the given interface object.
 type XML struct {
-	Data interface{}
+	Data any
 }
 
 var xmlContentType = []string{"application/xml; charset=utf-8"}
diff --git a/render/yaml.go b/render/yaml.go
index 0df7836..0fc7a66 100644
--- a/render/yaml.go
+++ b/render/yaml.go
@@ -12,7 +12,7 @@ import (
 
 // YAML contains the given interface object.
 type YAML struct {
-	Data interface{}
+	Data any
 }
 
 var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
diff --git a/response_writer.go b/response_writer.go
index 2682668..7f9095f 100644
--- a/response_writer.go
+++ b/response_writer.go
@@ -23,23 +23,23 @@ type ResponseWriter interface {
 	http.Flusher
 	http.CloseNotifier
 
-	// Returns the HTTP response status code of the current request.
+	// Status returns the HTTP response status code of the current request.
 	Status() int
 
-	// Returns the number of bytes already written into the response http body.
+	// Size returns the number of bytes already written into the response http body.
 	// See Written()
 	Size() int
 
-	// Writes the string into the response body.
+	// WriteString writes the string into the response body.
 	WriteString(string) (int, error)
 
-	// Returns true if the response body was already written.
+	// Written returns true if the response body was already written.
 	Written() bool
 
-	// Forces to write the http header (status code + headers).
+	// WriteHeaderNow forces to write the http header (status code + headers).
 	WriteHeaderNow()
 
-	// get the http.Pusher for server push
+	// Pusher get the http.Pusher for server push
 	Pusher() http.Pusher
 }
 
@@ -107,12 +107,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
 	return w.ResponseWriter.(http.Hijacker).Hijack()
 }
 
-// CloseNotify implements the http.CloseNotify interface.
+// CloseNotify implements the http.CloseNotifier interface.
 func (w *responseWriter) CloseNotify() <-chan bool {
 	return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
 }
 
-// Flush implements the http.Flush interface.
+// Flush implements the http.Flusher interface.
 func (w *responseWriter) Flush() {
 	w.WriteHeaderNow()
 	w.ResponseWriter.(http.Flusher).Flush()
diff --git a/routergroup.go b/routergroup.go
index bb24bd5..3fba3a9 100644
--- a/routergroup.go
+++ b/routergroup.go
@@ -12,8 +12,15 @@ import (
 )
 
 var (
-	// reg match english letters for http method name
+	// regEnLetter matches english letters for http method name
 	regEnLetter = regexp.MustCompile("^[A-Z]+$")
+
+	// anyMethods for RouterGroup Any method
+	anyMethods = []string{
+		http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
+		http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
+		http.MethodTrace,
+	}
 )
 
 // IRouter defines all router handle interface includes single and group router.
@@ -37,6 +44,7 @@ type IRoutes interface {
 	HEAD(string, ...HandlerFunc) IRoutes
 
 	StaticFile(string, string) IRoutes
+	StaticFileFS(string, string, http.FileSystem) IRoutes
 	Static(string, string) IRoutes
 	StaticFS(string, http.FileSystem) IRoutes
 }
@@ -136,27 +144,34 @@ func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRo
 // Any registers a route that matches all the HTTP methods.
 // GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
 func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
-	group.handle(http.MethodGet, relativePath, handlers)
-	group.handle(http.MethodPost, relativePath, handlers)
-	group.handle(http.MethodPut, relativePath, handlers)
-	group.handle(http.MethodPatch, relativePath, handlers)
-	group.handle(http.MethodHead, relativePath, handlers)
-	group.handle(http.MethodOptions, relativePath, handlers)
-	group.handle(http.MethodDelete, relativePath, handlers)
-	group.handle(http.MethodConnect, relativePath, handlers)
-	group.handle(http.MethodTrace, relativePath, handlers)
+	for _, method := range anyMethods {
+		group.handle(method, relativePath, handlers)
+	}
+
 	return group.returnObj()
 }
 
 // StaticFile registers a single route in order to serve a single file of the local filesystem.
 // router.StaticFile("favicon.ico", "./resources/favicon.ico")
 func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
+	return group.staticFileHandler(relativePath, func(c *Context) {
+		c.File(filepath)
+	})
+}
+
+// StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead..
+// router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false})
+// Gin by default user: gin.Dir()
+func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes {
+	return group.staticFileHandler(relativePath, func(c *Context) {
+		c.FileFromFS(filepath, fs)
+	})
+}
+
+func (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes {
 	if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
 		panic("URL parameters can not be used when serving a static file")
 	}
-	handler := func(c *Context) {
-		c.File(filepath)
-	}
 	group.GET(relativePath, handler)
 	group.HEAD(relativePath, handler)
 	return group.returnObj()
diff --git a/tree.go b/tree.go
index eb54959..88100ee 100644
--- a/tree.go
+++ b/tree.go
@@ -17,6 +17,7 @@ import (
 var (
 	strColon = []byte(":")
 	strStar  = []byte("*")
+	strSlash = []byte("/")
 )
 
 // Param is a single URL parameter, consisting of a key and a value.
@@ -80,7 +81,7 @@ func longestCommonPrefix(a, b string) int {
 	return i
 }
 
-// addChild will add a child node, keeping wildcards at the end
+// addChild will add a child node, keeping wildcardChild at the end
 func (n *node) addChild(child *node) {
 	if n.wildChild && len(n.children) > 0 {
 		wildcardChild := n.children[len(n.children)-1]
@@ -98,11 +99,15 @@ func countParams(path string) uint16 {
 	return n
 }
 
+func countSections(path string) uint16 {
+	s := bytesconv.StringToBytes(path)
+	return uint16(bytes.Count(s, strSlash))
+}
+
 type nodeType uint8
 
 const (
-	static nodeType = iota // default
-	root
+	root nodeType = iota + 1
 	param
 	catchAll
 )
@@ -291,7 +296,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
 			break
 		}
 
-		// The wildcard name must not contain ':' and '*'
+		// The wildcard name must only contain one ':' or '*' character
 		if !valid {
 			panic("only one wildcard per path segment is allowed, has: '" +
 				wildcard + "' in path '" + fullPath + "'")
@@ -320,7 +325,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
 			n.priority++
 
 			// if the path doesn't end with the wildcard, then there
-			// will be another non-wildcard subpath starting with '/'
+			// will be another subpath starting with '/'
 			if len(wildcard) < len(path) {
 				path = path[len(wildcard):]
 
@@ -344,7 +349,12 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
 		}
 
 		if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
-			panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
+			pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0]
+			panic("catch-all wildcard '" + path +
+				"' in new path '" + fullPath +
+				"' conflicts with existing path segment '" + pathSeg +
+				"' in existing prefix '" + n.path + pathSeg +
+				"'")
 		}
 
 		// currently fixed width 1 for '/'
@@ -394,16 +404,19 @@ type nodeValue struct {
 	fullPath string
 }
 
+type skippedNode struct {
+	path        string
+	node        *node
+	paramsCount int16
+}
+
 // Returns the handle registered with the given path (key). The values of
 // wildcards are saved to a map.
 // If no handle can be found, a TSR (trailing slash redirect) recommendation is
 // made if a handle exists with an extra (without the) trailing slash for the
 // given path.
-func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) {
-	var (
-		skippedPath string
-		latestNode  = n // Caching the latest node
-	)
+func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
+	var globalParamsCount int16
 
 walk: // Outer loop for walking the tree
 	for {
@@ -418,15 +431,20 @@ walk: // Outer loop for walking the tree
 					if c == idxc {
 						//  strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
 						if n.wildChild {
-							skippedPath = prefix + path
-							latestNode = &node{
-								path:      n.path,
-								wildChild: n.wildChild,
-								nType:     n.nType,
-								priority:  n.priority,
-								children:  n.children,
-								handlers:  n.handlers,
-								fullPath:  n.fullPath,
+							index := len(*skippedNodes)
+							*skippedNodes = (*skippedNodes)[:index+1]
+							(*skippedNodes)[index] = skippedNode{
+								path: prefix + path,
+								node: &node{
+									path:      n.path,
+									wildChild: n.wildChild,
+									nType:     n.nType,
+									priority:  n.priority,
+									children:  n.children,
+									handlers:  n.handlers,
+									fullPath:  n.fullPath,
+								},
+								paramsCount: globalParamsCount,
 							}
 						}
 
@@ -434,15 +452,26 @@ walk: // Outer loop for walking the tree
 						continue walk
 					}
 				}
-				// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
-				// the current node needs to be equal to the latest matching node
-				matched := path != "/" && !n.wildChild
-				if matched {
-					n = latestNode
-				}
 
-				// If there is no wildcard pattern, recommend a redirection
 				if !n.wildChild {
+					// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
+					// the current node needs to roll back to last vaild skippedNode
+					if path != "/" {
+						for l := len(*skippedNodes); l > 0; {
+							skippedNode := (*skippedNodes)[l-1]
+							*skippedNodes = (*skippedNodes)[:l-1]
+							if strings.HasSuffix(skippedNode.path, path) {
+								path = skippedNode.path
+								n = skippedNode.node
+								if value.params != nil {
+									*value.params = (*value.params)[:skippedNode.paramsCount]
+								}
+								globalParamsCount = skippedNode.paramsCount
+								continue walk
+							}
+						}
+					}
+
 					// Nothing found.
 					// We can recommend to redirect to the same URL without a
 					// trailing slash if a leaf exists for that path.
@@ -452,18 +481,12 @@ walk: // Outer loop for walking the tree
 
 				// Handle wildcard child, which is always at the end of the array
 				n = n.children[len(n.children)-1]
+				globalParamsCount++
 
 				switch n.nType {
 				case param:
 					// fix truncate the parameter
 					// tree_test.go  line: 204
-					if matched {
-						path = prefix + path
-						// The saved path is used after the prefix route is intercepted by matching
-						if n.indices == "/" {
-							path = skippedPath[1:]
-						}
-					}
 
 					// Find param end (either '/' or path end)
 					end := 0
@@ -512,7 +535,7 @@ walk: // Outer loop for walking the tree
 						// No handle found. Check if a handle for this path + a
 						// trailing slash exists for TSR recommendation
 						n = n.children[0]
-						value.tsr = n.path == "/" && n.handlers != nil
+						value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
 					}
 					return
 
@@ -549,9 +572,22 @@ walk: // Outer loop for walking the tree
 
 		if path == prefix {
 			// If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node
-			// the current node needs to be equal to the latest matching node
-			if latestNode.wildChild && n.handlers == nil && path != "/" {
-				n = latestNode.children[len(latestNode.children)-1]
+			// the current node needs to roll back to last vaild skippedNode
+			if n.handlers == nil && path != "/" {
+				for l := len(*skippedNodes); l > 0; {
+					skippedNode := (*skippedNodes)[l-1]
+					*skippedNodes = (*skippedNodes)[:l-1]
+					if strings.HasSuffix(skippedNode.path, path) {
+						path = skippedNode.path
+						n = skippedNode.node
+						if value.params != nil {
+							*value.params = (*value.params)[:skippedNode.paramsCount]
+						}
+						globalParamsCount = skippedNode.paramsCount
+						continue walk
+					}
+				}
+				//	n = latestNode.children[len(latestNode.children)-1]
 			}
 			// We should have reached the node containing the handle.
 			// Check if this node has a handle registered.
@@ -582,25 +618,29 @@ walk: // Outer loop for walking the tree
 			return
 		}
 
-		if path != "/" && len(skippedPath) > 0 && strings.HasSuffix(skippedPath, path) {
-			path = skippedPath
-			// Reduce the number of cycles
-			n, latestNode = latestNode, n
-			// skippedPath cannot execute
-			// example:
-			// * /:cc/cc
-			// call /a/cc 	     expectations:match/200      Actual:match/200
-			// call /a/dd 	     expectations:unmatch/404    Actual: panic
-			// call /addr/dd/aa  expectations:unmatch/404    Actual: panic
-			// skippedPath: It can only be executed if the secondary route is not found
-			skippedPath = ""
-			continue walk
-		}
-
 		// Nothing found. We can recommend to redirect to the same URL with an
 		// extra trailing slash if a leaf exists for that path
 		value.tsr = path == "/" ||
-			(len(prefix) == len(path)+1 && n.handlers != nil)
+			(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
+				path == prefix[:len(prefix)-1] && n.handlers != nil)
+
+		// roll back to last valid skippedNode
+		if !value.tsr && path != "/" {
+			for l := len(*skippedNodes); l > 0; {
+				skippedNode := (*skippedNodes)[l-1]
+				*skippedNodes = (*skippedNodes)[:l-1]
+				if strings.HasSuffix(skippedNode.path, path) {
+					path = skippedNode.path
+					n = skippedNode.node
+					if value.params != nil {
+						*value.params = (*value.params)[:skippedNode.paramsCount]
+					}
+					globalParamsCount = skippedNode.paramsCount
+					continue walk
+				}
+			}
+		}
+
 		return
 	}
 }
diff --git a/utils.go b/utils.go
index c32f0ee..b41a3b0 100644
--- a/utils.go
+++ b/utils.go
@@ -12,13 +12,14 @@ import (
 	"reflect"
 	"runtime"
 	"strings"
+	"unicode"
 )
 
 // BindKey indicates a default bind key.
 const BindKey = "_gin-gonic/gin/bindkey"
 
 // Bind is a helper function for given interface object and returns a Gin middleware.
-func Bind(val interface{}) HandlerFunc {
+func Bind(val any) HandlerFunc {
 	value := reflect.ValueOf(val)
 	if value.Kind() == reflect.Ptr {
 		panic(`Bind struct can not be a pointer. Example:
@@ -50,7 +51,7 @@ func WrapH(h http.Handler) HandlerFunc {
 }
 
 // H is a shortcut for map[string]interface{}
-type H map[string]interface{}
+type H map[string]any
 
 // MarshalXML allows type H to be used with xml.Marshal.
 func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
@@ -89,7 +90,7 @@ func filterFlags(content string) string {
 	return content
 }
 
-func chooseData(custom, wildcard interface{}) interface{} {
+func chooseData(custom, wildcard any) any {
 	if custom != nil {
 		return custom
 	}
@@ -120,7 +121,7 @@ func lastChar(str string) uint8 {
 	return str[len(str)-1]
 }
 
-func nameOfFunction(f interface{}) string {
+func nameOfFunction(f any) string {
 	return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
 }
 
@@ -151,3 +152,13 @@ func resolveAddress(addr []string) string {
 		panic("too many parameters")
 	}
 }
+
+// https://stackoverflow.com/questions/53069040/checking-a-string-contains-only-ascii-characters
+func isASCII(s string) bool {
+	for i := 0; i < len(s); i++ {
+		if s[i] > unicode.MaxASCII {
+			return false
+		}
+	}
+	return true
+}
diff --git a/version.go b/version.go
index a80ab69..4b69b9b 100644
--- a/version.go
+++ b/version.go
@@ -5,4 +5,4 @@
 package gin
 
 // Version is the current gin framework's version.
-const Version = "v1.7.2"
+const Version = "v1.7.7"


Test patch:
diff --git a/binding/binding_test.go b/binding/binding_test.go
index 1733617..d9746e2 100644
--- a/binding/binding_test.go
+++ b/binding/binding_test.go
@@ -20,8 +20,8 @@ import (
 	"time"
 
 	"github.com/gin-gonic/gin/testdata/protoexample"
-	"github.com/golang/protobuf/proto"
 	"github.com/stretchr/testify/assert"
+	"google.golang.org/protobuf/proto"
 )
 
 type appkey struct {
@@ -61,11 +61,11 @@ type FooDefaultBarStruct struct {
 }
 
 type FooStructUseNumber struct {
-	Foo interface{} `json:"foo" binding:"required"`
+	Foo any `json:"foo" binding:"required"`
 }
 
 type FooStructDisallowUnknownFields struct {
-	Foo interface{} `json:"foo" binding:"required"`
+	Foo any `json:"foo" binding:"required"`
 }
 
 type FooBarStructForTimeType struct {
@@ -93,7 +93,7 @@ type FooStructForTimeTypeFailLocation struct {
 }
 
 type FooStructForMapType struct {
-	MapFoo map[string]interface{} `form:"map_foo"`
+	MapFoo map[string]any `form:"map_foo"`
 }
 
 type FooStructForIgnoreFormTag struct {
@@ -106,7 +106,7 @@ type InvalidNameType struct {
 
 type InvalidNameMapType struct {
 	TestName struct {
-		MapFoo map[string]interface{} `form:"map_foo"`
+		MapFoo map[string]any `form:"map_foo"`
 	}
 }
 
@@ -128,7 +128,7 @@ type FooStructForStructPointerType struct {
 
 type FooStructForSliceMapType struct {
 	// Unknown type: not support map
-	SliceMapFoo []map[string]interface{} `form:"slice_map_foo"`
+	SliceMapFoo []map[string]any `form:"slice_map_foo"`
 }
 
 type FooStructForBoolType struct {
@@ -141,7 +141,7 @@ type FooStructForStringPtrType struct {
 }
 
 type FooStructForMapPtrType struct {
-	PtrBar *map[string]interface{} `form:"ptr_bar"`
+	PtrBar *map[string]any `form:"ptr_bar"`
 }
 
 func TestBindingDefault(t *testing.T) {
@@ -768,7 +768,7 @@ func TestHeaderBinding(t *testing.T) {
 	req.Header.Add("fail", `{fail:fail}`)
 
 	type failStruct struct {
-		Fail map[string]interface{} `header:"fail"`
+		Fail map[string]any `header:"fail"`
 	}
 
 	err := h.Bind(req, &failStruct{})
@@ -789,11 +789,11 @@ func TestUriBinding(t *testing.T) {
 	assert.Equal(t, "thinkerou", tag.Name)
 
 	type NotSupportStruct struct {
-		Name map[string]interface{} `uri:"name"`
+		Name map[string]any `uri:"name"`
 	}
 	var not NotSupportStruct
 	assert.Error(t, b.BindUri(m, &not))
-	assert.Equal(t, map[string]interface{}(nil), not.Name)
+	assert.Equal(t, map[string]any(nil), not.Name)
 }
 
 func TestUriInnerBinding(t *testing.T) {
@@ -832,7 +832,6 @@ func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, ba
 	assert.Equal(t, 1, obj.Page)
 	assert.Equal(t, 2, obj.Size)
 	assert.Equal(t, "test-appkey", obj.Appkey)
-
 }
 
 func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
@@ -1340,6 +1339,13 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
 	err := b.Bind(req, &obj)
 	assert.Error(t, err)
 
+	invalidobj := FooStruct{}
+	req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`))
+	req.Header.Add("Content-Type", MIMEPROTOBUF)
+	err = b.Bind(req, &invalidobj)
+	assert.Error(t, err)
+	assert.Equal(t, err.Error(), "obj is not ProtoMessage")
+
 	obj = protoexample.Test{}
 	req = requestWithBody("POST", badPath, badBody)
 	req.Header.Add("Content-Type", MIMEPROTOBUF)
diff --git a/binding/default_validator_benchmark_test.go b/binding/default_validator_benchmark_test.go
index 839cf71..8d62836 100644
--- a/binding/default_validator_benchmark_test.go
+++ b/binding/default_validator_benchmark_test.go
@@ -6,10 +6,10 @@ import (
 	"testing"
 )
 
-func BenchmarkSliceValidateError(b *testing.B) {
+func BenchmarkSliceValidationError(b *testing.B) {
 	const size int = 100
 	for i := 0; i < b.N; i++ {
-		e := make(sliceValidateError, size)
+		e := make(SliceValidationError, size)
 		for j := 0; j < size; j++ {
 			e[j] = errors.New(strconv.Itoa(j))
 		}
diff --git a/binding/default_validator_test.go b/binding/default_validator_test.go
index e9debe5..df7742b 100644
--- a/binding/default_validator_test.go
+++ b/binding/default_validator_test.go
@@ -9,24 +9,24 @@ import (
 	"testing"
 )
 
-func TestSliceValidateError(t *testing.T) {
+func TestSliceValidationError(t *testing.T) {
 	tests := []struct {
 		name string
-		err  sliceValidateError
+		err  SliceValidationError
 		want string
 	}{
-		{"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"},
-		{"has zero elements", sliceValidateError{}, ""},
-		{"has one element", sliceValidateError{errors.New("test one error")}, "[0]: test one error"},
+		{"has nil elements", SliceValidationError{errors.New("test error"), nil}, "[0]: test error"},
+		{"has zero elements", SliceValidationError{}, ""},
+		{"has one element", SliceValidationError{errors.New("test one error")}, "[0]: test one error"},
 		{"has two elements",
-			sliceValidateError{
+			SliceValidationError{
 				errors.New("first error"),
 				errors.New("second error"),
 			},
 			"[0]: first error\n[1]: second error",
 		},
 		{"has many elements",
-			sliceValidateError{
+			SliceValidationError{
 				errors.New("first error"),
 				errors.New("second error"),
 				nil,
@@ -40,7 +40,7 @@ func TestSliceValidateError(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			if got := tt.err.Error(); got != tt.want {
-				t.Errorf("sliceValidateError.Error() = %v, want %v", got, tt.want)
+				t.Errorf("SliceValidationError.Error() = %v, want %v", got, tt.want)
 			}
 		})
 	}
@@ -54,7 +54,7 @@ func TestDefaultValidator(t *testing.T) {
 	tests := []struct {
 		name    string
 		v       *defaultValidator
-		obj     interface{}
+		obj     any
 		wantErr bool
 	}{
 		{"validate nil obj", &defaultValidator{}, nil, false},
diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go
index 608594e..78f4df0 100644
--- a/binding/form_mapping_test.go
+++ b/binding/form_mapping_test.go
@@ -18,9 +18,9 @@ func TestMappingBaseTypes(t *testing.T) {
 	}
 	for _, tt := range []struct {
 		name   string
-		value  interface{}
+		value  any
 		form   string
-		expect interface{}
+		expect any
 	}{
 		{"base type", struct{ F int }{}, "9", int(9)},
 		{"base type", struct{ F int8 }{}, "9", int8(9)},
@@ -131,7 +131,7 @@ func TestMappingURI(t *testing.T) {
 	var s struct {
 		F int `uri:"field"`
 	}
-	err := mapUri(&s, map[string][]string{"field": {"6"}})
+	err := mapURI(&s, map[string][]string{"field": {"6"}})
 	assert.NoError(t, err)
 	assert.Equal(t, int(6), s.F)
 }
diff --git a/binding/msgpack_test.go b/binding/msgpack_test.go
index 75600ba..11561c8 100644
--- a/binding/msgpack_test.go
+++ b/binding/msgpack_test.go
@@ -26,7 +26,7 @@ func TestMsgpackBindingBindBody(t *testing.T) {
 	assert.Equal(t, "FOO", s.Foo)
 }
 
-func msgpackBody(t *testing.T, obj interface{}) []byte {
+func msgpackBody(t *testing.T, obj any) []byte {
 	var bs bytes.Buffer
 	h := &codec.MsgpackHandle{}
 	err := codec.NewEncoder(&bs, h).Encode(obj)
diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go
index 4aaa60b..545914d 100644
--- a/binding/multipart_form_mapping_test.go
+++ b/binding/multipart_form_mapping_test.go
@@ -76,7 +76,7 @@ func TestFormMultipartBindingBindError(t *testing.T) {
 
 	for _, tt := range []struct {
 		name string
-		s    interface{}
+		s    any
 	}{
 		{"wrong type", &struct {
 			Files int `form:"file"`
diff --git a/binding/validate_test.go b/binding/validate_test.go
index 5299fbf..d05c891 100644
--- a/binding/validate_test.go
+++ b/binding/validate_test.go
@@ -59,7 +59,7 @@ type structNoValidationValues struct {
 	StructSlice        []substructNoValidation
 	InterfaceSlice     []testInterface
 
-	UniversalInterface interface{}
+	UniversalInterface any
 	CustomInterface    testInterface
 
 	FloatMap  map[string]float32
@@ -169,7 +169,7 @@ func TestValidateNoValidationPointers(t *testing.T) {
 	//assert.Equal(t, origin, test)
 }
 
-type Object map[string]interface{}
+type Object map[string]any
 
 func TestValidatePrimitives(t *testing.T) {
 	obj := Object{"foo": "bar", "bar": 1}
diff --git a/context_1.16_test.go b/context_1.16_test.go
new file mode 100644
index 0000000..053e6c5
--- /dev/null
+++ b/context_1.16_test.go
@@ -0,0 +1,31 @@
+// Copyright 2021 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+//go:build !go1.17
+// +build !go1.17
+
+package gin
+
+import (
+	"bytes"
+	"mime/multipart"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestContextFormFileFailed16(t *testing.T) {
+	buf := new(bytes.Buffer)
+	mw := multipart.NewWriter(buf)
+	mw.Close()
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	c.Request, _ = http.NewRequest("POST", "/", nil)
+	c.Request.Header.Set("Content-Type", mw.FormDataContentType())
+	c.engine.MaxMultipartMemory = 8 << 20
+	f, err := c.FormFile("file")
+	assert.Error(t, err)
+	assert.Nil(t, f)
+}
diff --git a/context_1.17_test.go b/context_1.17_test.go
new file mode 100644
index 0000000..431d54c
--- /dev/null
+++ b/context_1.17_test.go
@@ -0,0 +1,33 @@
+// Copyright 2021 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+//go:build go1.17
+// +build go1.17
+
+package gin
+
+import (
+	"bytes"
+	"mime/multipart"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestContextFormFileFailed17(t *testing.T) {
+	buf := new(bytes.Buffer)
+	mw := multipart.NewWriter(buf)
+	mw.Close()
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	c.Request, _ = http.NewRequest("POST", "/", nil)
+	c.Request.Header.Set("Content-Type", mw.FormDataContentType())
+	c.engine.MaxMultipartMemory = 8 << 20
+	assert.Panics(t, func() {
+		f, err := c.FormFile("file")
+		assert.Error(t, err)
+		assert.Nil(t, f)
+	})
+}
diff --git a/context_test.go b/context_test.go
index 2a4d218..fb46e67 100644
--- a/context_test.go
+++ b/context_test.go
@@ -12,8 +12,10 @@ import (
 	"html/template"
 	"io"
 	"mime/multipart"
+	"net"
 	"net/http"
 	"net/http/httptest"
+	"net/url"
 	"os"
 	"reflect"
 	"strings"
@@ -23,10 +25,9 @@ import (
 
 	"github.com/gin-contrib/sse"
 	"github.com/gin-gonic/gin/binding"
-	"github.com/golang/protobuf/proto"
-	"github.com/stretchr/testify/assert"
-
 	testdata "github.com/gin-gonic/gin/testdata/protoexample"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/protobuf/proto"
 )
 
 var _ context.Context = &Context{}
@@ -87,19 +88,6 @@ func TestContextFormFile(t *testing.T) {
 	assert.NoError(t, c.SaveUploadedFile(f, "test"))
 }
 
-func TestContextFormFileFailed(t *testing.T) {
-	buf := new(bytes.Buffer)
-	mw := multipart.NewWriter(buf)
-	mw.Close()
-	c, _ := CreateTestContext(httptest.NewRecorder())
-	c.Request, _ = http.NewRequest("POST", "/", nil)
-	c.Request.Header.Set("Content-Type", mw.FormDataContentType())
-	c.engine.MaxMultipartMemory = 8 << 20
-	f, err := c.FormFile("file")
-	assert.Error(t, err)
-	assert.Nil(t, f)
-}
-
 func TestContextMultipartForm(t *testing.T) {
 	buf := new(bytes.Buffer)
 	mw := multipart.NewWriter(buf)
@@ -224,7 +212,7 @@ func TestContextSetGetValues(t *testing.T) {
 	c.Set("uint64", uint64(42))
 	c.Set("float32", float32(4.2))
 	c.Set("float64", 4.2)
-	var a interface{} = 1
+	var a any = 1
 	c.Set("intInterface", a)
 
 	assert.Exactly(t, c.MustGet("string").(string), "this is a string")
@@ -234,7 +222,6 @@ func TestContextSetGetValues(t *testing.T) {
 	assert.Exactly(t, c.MustGet("float32").(float32), float32(4.2))
 	assert.Exactly(t, c.MustGet("float64").(float64), 4.2)
 	assert.Exactly(t, c.MustGet("intInterface").(int), 1)
-
 }
 
 func TestContextGetString(t *testing.T) {
@@ -300,7 +287,7 @@ func TestContextGetStringSlice(t *testing.T) {
 
 func TestContextGetStringMap(t *testing.T) {
 	c, _ := CreateTestContext(httptest.NewRecorder())
-	var m = make(map[string]interface{})
+	m := make(map[string]any)
 	m["foo"] = 1
 	c.Set("map", m)
 
@@ -310,7 +297,7 @@ func TestContextGetStringMap(t *testing.T) {
 
 func TestContextGetStringMapString(t *testing.T) {
 	c, _ := CreateTestContext(httptest.NewRecorder())
-	var m = make(map[string]string)
+	m := make(map[string]string)
 	m["foo"] = "bar"
 	c.Set("map", m)
 
@@ -320,7 +307,7 @@ func TestContextGetStringMapString(t *testing.T) {
 
 func TestContextGetStringMapStringSlice(t *testing.T) {
 	c, _ := CreateTestContext(httptest.NewRecorder())
-	var m = make(map[string][]string)
+	m := make(map[string][]string)
 	m["foo"] = []string{"foo"}
 	c.Set("map", m)
 
@@ -369,15 +356,12 @@ func TestContextHandlerNames(t *testing.T) {
 }
 
 func handlerNameTest(c *Context) {
-
 }
 
 func handlerNameTest2(c *Context) {
-
 }
 
 var handlerTest HandlerFunc = func(c *Context) {
-
 }
 
 func TestContextHandler(t *testing.T) {
@@ -659,8 +643,7 @@ func TestContextBodyAllowedForStatus(t *testing.T) {
 	assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
 }
 
-type TestPanicRender struct {
-}
+type TestPanicRender struct{}
 
 func (*TestPanicRender) Render(http.ResponseWriter) error {
 	return errors.New("TestPanicRender")
@@ -1051,6 +1034,19 @@ func TestContextRenderAttachment(t *testing.T) {
 	assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition"))
 }
 
+func TestContextRenderUTF8Attachment(t *testing.T) {
+	w := httptest.NewRecorder()
+	c, _ := CreateTestContext(w)
+	newFilename := "new🧡_filename.go"
+
+	c.Request, _ = http.NewRequest("GET", "/", nil)
+	c.FileAttachment("./gin.go", newFilename)
+
+	assert.Equal(t, 200, w.Code)
+	assert.Contains(t, w.Body.String(), "func New() *Engine {")
+	assert.Equal(t, `attachment; filename*=UTF-8''`+url.QueryEscape(newFilename), w.Header().Get("Content-Disposition"))
+}
+
 // TestContextRenderYAML tests that the response is serialized as YAML
 // and Content-Type is set to application/x-yaml
 func TestContextRenderYAML(t *testing.T) {
@@ -1329,7 +1325,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
 	_, err := buf.ReadFrom(w.Body)
 	assert.NoError(t, err)
 	jsonStringBody := buf.String()
-	assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}"), jsonStringBody)
+	assert.Equal(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody)
 }
 
 func TestContextError(t *testing.T) {
@@ -1423,18 +1419,27 @@ func TestContextClientIP(t *testing.T) {
 	// Tests exercising the TrustedProxies functionality
 	resetContextForClientIPTests(c)
 
+	// IPv6 support
+	c.Request.RemoteAddr = "[::1]:12345"
+	assert.Equal(t, "20.20.20.20", c.ClientIP())
+
+	resetContextForClientIPTests(c)
 	// No trusted proxies
 	_ = c.engine.SetTrustedProxies([]string{})
 	c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"}
 	assert.Equal(t, "40.40.40.40", c.ClientIP())
 
+	// Disabled TrustedProxies feature
+	_ = c.engine.SetTrustedProxies(nil)
+	assert.Equal(t, "40.40.40.40", c.ClientIP())
+
 	// Last proxy is trusted, but the RemoteAddr is not
 	_ = c.engine.SetTrustedProxies([]string{"30.30.30.30"})
 	assert.Equal(t, "40.40.40.40", c.ClientIP())
 
 	// Only trust RemoteAddr
 	_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
-	assert.Equal(t, "20.20.20.20", c.ClientIP())
+	assert.Equal(t, "30.30.30.30", c.ClientIP())
 
 	// All steps are trusted
 	_ = c.engine.SetTrustedProxies([]string{"40.40.40.40", "30.30.30.30", "20.20.20.20"})
@@ -1473,8 +1478,20 @@ func TestContextClientIP(t *testing.T) {
 	c.engine.TrustedPlatform = PlatformGoogleAppEngine
 	assert.Equal(t, "50.50.50.50", c.ClientIP())
 
-	// Test the legacy flag
+	// Use custom TrustedPlatform header
+	c.engine.TrustedPlatform = "X-CDN-IP"
+	c.Request.Header.Set("X-CDN-IP", "80.80.80.80")
+	assert.Equal(t, "80.80.80.80", c.ClientIP())
+	// wrong header
+	c.engine.TrustedPlatform = "X-Wrong-Header"
+	assert.Equal(t, "40.40.40.40", c.ClientIP())
+
+	c.Request.Header.Del("X-CDN-IP")
+	// TrustedPlatform is empty
 	c.engine.TrustedPlatform = ""
+	assert.Equal(t, "40.40.40.40", c.ClientIP())
+
+	// Test the legacy flag
 	c.engine.AppEngine = true
 	assert.Equal(t, "50.50.50.50", c.ClientIP())
 	c.engine.AppEngine = false
@@ -1503,6 +1520,7 @@ func resetContextForClientIPTests(c *Context) {
 	c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60")
 	c.Request.RemoteAddr = "  40.40.40.40:42123 "
 	c.engine.TrustedPlatform = ""
+	c.engine.trustedCIDRs = defaultTrustedCIDRs
 	c.engine.AppEngine = false
 }
 
@@ -1545,6 +1563,7 @@ func TestContextBindWithJSON(t *testing.T) {
 	assert.Equal(t, "bar", obj.Foo)
 	assert.Equal(t, 0, w.Body.Len())
 }
+
 func TestContextBindWithXML(t *testing.T) {
 	w := httptest.NewRecorder()
 	c, _ := CreateTestContext(w)
@@ -2031,8 +2050,8 @@ func TestRaceParamsContextCopy(t *testing.T) {
 			}(c.Copy(), c.Param("name"))
 		})
 	}
-	performRequest(router, "GET", "/name1/api")
-	performRequest(router, "GET", "/name2/api")
+	PerformRequest(router, "GET", "/name1/api")
+	PerformRequest(router, "GET", "/name2/api")
 	wg.Wait()
 }
 
@@ -2053,20 +2072,65 @@ func TestRemoteIPFail(t *testing.T) {
 	c, _ := CreateTestContext(httptest.NewRecorder())
 	c.Request, _ = http.NewRequest("POST", "/", nil)
 	c.Request.RemoteAddr = "[:::]:80"
-	ip, trust := c.RemoteIP()
+	ip := net.ParseIP(c.RemoteIP())
+	trust := c.engine.isTrustedProxy(ip)
 	assert.Nil(t, ip)
 	assert.False(t, trust)
 }
 
+func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) {
+	c := &Context{}
+	deadline, ok := c.Deadline()
+	assert.Zero(t, deadline)
+	assert.False(t, ok)
+
+	c2 := &Context{}
+	c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
+	d := time.Now().Add(time.Second)
+	ctx, cancel := context.WithDeadline(context.Background(), d)
+	defer cancel()
+	c2.Request = c2.Request.WithContext(ctx)
+	deadline, ok = c2.Deadline()
+	assert.Equal(t, d, deadline)
+	assert.True(t, ok)
+}
+
+func TestContextWithFallbackDoneFromRequestContext(t *testing.T) {
+	c := &Context{}
+	assert.Nil(t, c.Done())
+
+	c2 := &Context{}
+	c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
+	ctx, cancel := context.WithCancel(context.Background())
+	c2.Request = c2.Request.WithContext(ctx)
+	cancel()
+	assert.NotNil(t, <-c2.Done())
+}
+
+func TestContextWithFallbackErrFromRequestContext(t *testing.T) {
+	c := &Context{}
+	assert.Nil(t, c.Err())
+
+	c2 := &Context{}
+	c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
+	ctx, cancel := context.WithCancel(context.Background())
+	c2.Request = c2.Request.WithContext(ctx)
+	cancel()
+
+	assert.EqualError(t, c2.Err(), context.Canceled.Error())
+}
+
+type contextKey string
+
 func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
 	tests := []struct {
 		name             string
-		getContextAndKey func() (*Context, interface{})
-		value            interface{}
+		getContextAndKey func() (*Context, any)
+		value            any
 	}{
 		{
 			name: "c with struct context key",
-			getContextAndKey: func() (*Context, interface{}) {
+			getContextAndKey: func() (*Context, any) {
 				var key struct{}
 				c := &Context{}
 				c.Request, _ = http.NewRequest("POST", "/", nil)
@@ -2077,17 +2141,17 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
 		},
 		{
 			name: "c with string context key",
-			getContextAndKey: func() (*Context, interface{}) {
+			getContextAndKey: func() (*Context, any) {
 				c := &Context{}
 				c.Request, _ = http.NewRequest("POST", "/", nil)
-				c.Request = c.Request.WithContext(context.WithValue(context.TODO(), "key", "value"))
-				return c, "key"
+				c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value"))
+				return c, contextKey("key")
 			},
 			value: "value",
 		},
 		{
 			name: "c with nil http.Request",
-			getContextAndKey: func() (*Context, interface{}) {
+			getContextAndKey: func() (*Context, any) {
 				c := &Context{}
 				return c, "key"
 			},
@@ -2095,7 +2159,7 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
 		},
 		{
 			name: "c with nil http.Request.Context()",
-			getContextAndKey: func() (*Context, interface{}) {
+			getContextAndKey: func() (*Context, any) {
 				c := &Context{}
 				c.Request, _ = http.NewRequest("POST", "/", nil)
 				return c, "key"
@@ -2110,3 +2174,14 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
 		})
 	}
 }
+
+func TestContextAddParam(t *testing.T) {
+	c := &Context{}
+	id := "id"
+	value := "1"
+	c.AddParam(id, value)
+
+	v, ok := c.Params.Get(id)
+	assert.Equal(t, ok, true)
+	assert.Equal(t, value, v)
+}
diff --git a/debug_test.go b/debug_test.go
index 0550999..7c54444 100644
--- a/debug_test.go
+++ b/debug_test.go
@@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
 	})
 	m, e := getMinVer(runtime.Version())
 	if e == nil && m <= ginSupportMinGoVer {
-		assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.13+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
+		assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.14+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 	} else {
 		assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 	}
diff --git a/errors_test.go b/errors_test.go
index ee95ab3..ac72dc4 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -86,7 +86,7 @@ Error #02: second
 Error #03: third
      Meta: map[status:400]
 `, errs.String())
-	assert.Equal(t, []interface{}{
+	assert.Equal(t, []any{
 		H{"error": "first"},
 		H{"error": "second", "meta": "some data"},
 		H{"error": "third", "status": "400"},
@@ -113,7 +113,7 @@ func (e TestErr) Error() string { return string(e) }
 // TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()".
 // "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13.
 func TestErrorUnwrap(t *testing.T) {
-	innerErr := TestErr("somme error")
+	innerErr := TestErr("some error")
 
 	// 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr
 	err := fmt.Errorf("wrapped: %w", &Error{
diff --git a/gin_integration_test.go b/gin_integration_test.go
index 094c46e..0dfa903 100644
--- a/gin_integration_test.go
+++ b/gin_integration_test.go
@@ -15,6 +15,7 @@ import (
 	"net/http/httptest"
 	"os"
 	"path/filepath"
+	"runtime"
 	"sync"
 	"testing"
 	"time"
@@ -76,6 +77,12 @@ func TestRunEmpty(t *testing.T) {
 	testRequest(t, "http://localhost:8080/example")
 }
 
+func TestBadTrustedCIDRs(t *testing.T) {
+	router := New()
+	assert.Error(t, router.SetTrustedProxies([]string{"hello/world"}))
+}
+
+/* legacy tests
 func TestBadTrustedCIDRsForRun(t *testing.T) {
 	os.Setenv("PORT", "")
 	router := New()
@@ -143,6 +150,7 @@ func TestBadTrustedCIDRsForRunTLS(t *testing.T) {
 	router.TrustedProxies = []string{"hello/world"}
 	assert.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
 }
+*/
 
 func TestRunTLS(t *testing.T) {
 	router := New()
@@ -274,7 +282,16 @@ func TestFileDescriptor(t *testing.T) {
 	listener, err := net.ListenTCP("tcp", addr)
 	assert.NoError(t, err)
 	socketFile, err := listener.File()
-	assert.NoError(t, err)
+	if isWindows() {
+		// not supported by windows, it is unimplemented now
+		assert.Error(t, err)
+	} else {
+		assert.NoError(t, err)
+	}
+
+	if socketFile == nil {
+		return
+	}
 
 	go func() {
 		router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
@@ -401,8 +418,13 @@ func TestTreeRunDynamicRouting(t *testing.T) {
 	router.GET("/ab/*xx", func(c *Context) { c.String(http.StatusOK, "/ab/*xx") })
 	router.GET("/", func(c *Context) { c.String(http.StatusOK, "home") })
 	router.GET("/:cc", func(c *Context) { c.String(http.StatusOK, "/:cc") })
+	router.GET("/c1/:dd/e", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e") })
+	router.GET("/c1/:dd/e1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e1") })
+	router.GET("/c1/:dd/f1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f1") })
+	router.GET("/c1/:dd/f2", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f2") })
 	router.GET("/:cc/cc", func(c *Context) { c.String(http.StatusOK, "/:cc/cc") })
 	router.GET("/:cc/:dd/ee", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/ee") })
+	router.GET("/:cc/:dd/f", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/f") })
 	router.GET("/:cc/:dd/:ee/ff", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/ff") })
 	router.GET("/:cc/:dd/:ee/:ff/gg", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/gg") })
 	router.GET("/:cc/:dd/:ee/:ff/:gg/hh", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/:gg/hh") })
@@ -439,6 +461,10 @@ func TestTreeRunDynamicRouting(t *testing.T) {
 	testRequest(t, ts.URL+"/all", "", "/:cc")
 	testRequest(t, ts.URL+"/all/cc", "", "/:cc/cc")
 	testRequest(t, ts.URL+"/a/cc", "", "/:cc/cc")
+	testRequest(t, ts.URL+"/c1/d/e", "", "/c1/:dd/e")
+	testRequest(t, ts.URL+"/c1/d/e1", "", "/c1/:dd/e1")
+	testRequest(t, ts.URL+"/c1/d/ee", "", "/:cc/:dd/ee")
+	testRequest(t, ts.URL+"/c1/d/f", "", "/:cc/:dd/f")
 	testRequest(t, ts.URL+"/c/d/ee", "", "/:cc/:dd/ee")
 	testRequest(t, ts.URL+"/c/d/e/ff", "", "/:cc/:dd/:ee/ff")
 	testRequest(t, ts.URL+"/c/d/e/f/gg", "", "/:cc/:dd/:ee/:ff/gg")
@@ -521,7 +547,17 @@ func TestTreeRunDynamicRouting(t *testing.T) {
 	testRequest(t, ts.URL+"/get/abc/123abf/testss", "", "/get/abc/123abf/:param")
 	testRequest(t, ts.URL+"/get/abc/123abfff/te", "", "/get/abc/123abfff/:param")
 	// 404 not found
+	testRequest(t, ts.URL+"/c/d/e", "404 Not Found")
+	testRequest(t, ts.URL+"/c/d/e1", "404 Not Found")
+	testRequest(t, ts.URL+"/c/d/eee", "404 Not Found")
+	testRequest(t, ts.URL+"/c1/d/eee", "404 Not Found")
+	testRequest(t, ts.URL+"/c1/d/e2", "404 Not Found")
+	testRequest(t, ts.URL+"/cc/dd/ee/ff/gg/hh1", "404 Not Found")
 	testRequest(t, ts.URL+"/a/dd", "404 Not Found")
 	testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found")
 	testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found")
 }
+
+func isWindows() bool {
+	return runtime.GOOS == "windows"
+}
diff --git a/gin_test.go b/gin_test.go
index 678d95f..a438062 100644
--- a/gin_test.go
+++ b/gin_test.go
@@ -19,6 +19,7 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"golang.org/x/net/http2"
 )
 
 func formatAsDate(t time.Time) string {
@@ -42,7 +43,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine
 			c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
 		})
 		router.GET("/raw", func(c *Context) {
-			c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
+			c.HTML(http.StatusOK, "raw.tmpl", map[string]any{
 				"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
 			})
 		})
@@ -79,6 +80,44 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) {
 	assert.Equal(t, "<h1>Hello world</h1>", string(resp))
 }
 
+func TestH2c(t *testing.T) {
+	ln, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		fmt.Println(err)
+	}
+	r := Default()
+	r.UseH2C = true
+	r.GET("/", func(c *Context) {
+		c.String(200, "<h1>Hello world</h1>")
+	})
+	go func() {
+		err := http.Serve(ln, r.Handler())
+		if err != nil {
+			fmt.Println(err)
+		}
+	}()
+	defer ln.Close()
+
+	url := "http://" + ln.Addr().String() + "/"
+
+	http := http.Client{
+		Transport: &http2.Transport{
+			AllowHTTP: true,
+			DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
+				return net.Dial(netw, addr)
+			},
+		},
+	}
+
+	res, err := http.Get(url)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	resp, _ := ioutil.ReadAll(res.Body)
+	assert.Equal(t, "<h1>Hello world</h1>", string(resp))
+}
+
 func TestLoadHTMLGlobTestMode(t *testing.T) {
 	ts := setupHTMLFiles(
 		t,
@@ -163,7 +202,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
-	assert.Equal(t, "Date: 2017/07/01\n", string(resp))
+	assert.Equal(t, "Date: 2017/07/01", string(resp))
 }
 
 func init() {
@@ -281,7 +320,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
-	assert.Equal(t, "Date: 2017/07/01\n", string(resp))
+	assert.Equal(t, "Date: 2017/07/01", string(resp))
 }
 
 func TestAddRoute(t *testing.T) {
@@ -395,7 +434,6 @@ func TestNoMethodWithoutGlobalHandlers(t *testing.T) {
 }
 
 func TestRebuild404Handlers(t *testing.T) {
-
 }
 
 func TestNoMethodWithGlobalHandlers(t *testing.T) {
@@ -429,7 +467,7 @@ func TestNoMethodWithGlobalHandlers(t *testing.T) {
 	compareFunc(t, router.allNoMethod[2], middleware0)
 }
 
-func compareFunc(t *testing.T, a, b interface{}) {
+func compareFunc(t *testing.T, a, b any) {
 	sf1 := reflect.ValueOf(a)
 	sf2 := reflect.ValueOf(b)
 	if sf1.Pointer() != sf2.Pointer() {
@@ -491,7 +529,7 @@ func TestEngineHandleContext(t *testing.T) {
 	}
 
 	assert.NotPanics(t, func() {
-		w := performRequest(r, "GET", "/")
+		w := PerformRequest(r, "GET", "/")
 		assert.Equal(t, 301, w.Code)
 	})
 }
@@ -524,7 +562,7 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
 	})
 
 	assert.NotPanics(t, func() {
-		w := performRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value
+		w := PerformRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value
 		assert.Equal(t, 200, w.Code)
 		assert.Equal(t, expectValue, w.Body.Len())
 	})
@@ -539,19 +577,15 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
 	// valid ipv4 cidr
 	{
 		expectedTrustedCIDRs := []*net.IPNet{parseCIDR("0.0.0.0/0")}
-		r.TrustedProxies = []string{"0.0.0.0/0"}
-
-		trustedCIDRs, err := r.prepareTrustedCIDRs()
+		err := r.SetTrustedProxies([]string{"0.0.0.0/0"})
 
 		assert.NoError(t, err)
-		assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
+		assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
 	}
 
 	// invalid ipv4 cidr
 	{
-		r.TrustedProxies = []string{"192.168.1.33/33"}
-
-		_, err := r.prepareTrustedCIDRs()
+		err := r.SetTrustedProxies([]string{"192.168.1.33/33"})
 
 		assert.Error(t, err)
 	}
@@ -559,19 +593,16 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
 	// valid ipv4 address
 	{
 		expectedTrustedCIDRs := []*net.IPNet{parseCIDR("192.168.1.33/32")}
-		r.TrustedProxies = []string{"192.168.1.33"}
 
-		trustedCIDRs, err := r.prepareTrustedCIDRs()
+		err := r.SetTrustedProxies([]string{"192.168.1.33"})
 
 		assert.NoError(t, err)
-		assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
+		assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
 	}
 
 	// invalid ipv4 address
 	{
-		r.TrustedProxies = []string{"192.168.1.256"}
-
-		_, err := r.prepareTrustedCIDRs()
+		err := r.SetTrustedProxies([]string{"192.168.1.256"})
 
 		assert.Error(t, err)
 	}
@@ -579,19 +610,15 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
 	// valid ipv6 address
 	{
 		expectedTrustedCIDRs := []*net.IPNet{parseCIDR("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")}
-		r.TrustedProxies = []string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"}
-
-		trustedCIDRs, err := r.prepareTrustedCIDRs()
+		err := r.SetTrustedProxies([]string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"})
 
 		assert.NoError(t, err)
-		assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
+		assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
 	}
 
 	// invalid ipv6 address
 	{
-		r.TrustedProxies = []string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"}
-
-		_, err := r.prepareTrustedCIDRs()
+		err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"})
 
 		assert.Error(t, err)
 	}
@@ -599,19 +626,15 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
 	// valid ipv6 cidr
 	{
 		expectedTrustedCIDRs := []*net.IPNet{parseCIDR("::/0")}
-		r.TrustedProxies = []string{"::/0"}
-
-		trustedCIDRs, err := r.prepareTrustedCIDRs()
+		err := r.SetTrustedProxies([]string{"::/0"})
 
 		assert.NoError(t, err)
-		assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
+		assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
 	}
 
 	// invalid ipv6 cidr
 	{
-		r.TrustedProxies = []string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"}
-
-		_, err := r.prepareTrustedCIDRs()
+		err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"})
 
 		assert.Error(t, err)
 	}
@@ -623,39 +646,34 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
 			parseCIDR("192.168.0.0/16"),
 			parseCIDR("172.16.0.1/32"),
 		}
-		r.TrustedProxies = []string{
+		err := r.SetTrustedProxies([]string{
 			"::/0",
 			"192.168.0.0/16",
 			"172.16.0.1",
-		}
-
-		trustedCIDRs, err := r.prepareTrustedCIDRs()
+		})
 
 		assert.NoError(t, err)
-		assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
+		assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
 	}
 
 	// invalid combination
 	{
-		r.TrustedProxies = []string{
+		err := r.SetTrustedProxies([]string{
 			"::/0",
 			"192.168.0.0/16",
 			"172.16.0.256",
-		}
-		_, err := r.prepareTrustedCIDRs()
+		})
 
 		assert.Error(t, err)
 	}
 
 	// nil value
 	{
-		r.TrustedProxies = nil
-		trustedCIDRs, err := r.prepareTrustedCIDRs()
+		err := r.SetTrustedProxies(nil)
 
-		assert.Nil(t, trustedCIDRs)
+		assert.Nil(t, r.trustedCIDRs)
 		assert.Nil(t, err)
 	}
-
 }
 
 func parseCIDR(cidr string) *net.IPNet {
diff --git a/githubapi_test.go b/githubapi_test.go
index 3f440bc..5fe65a4 100644
--- a/githubapi_test.go
+++ b/githubapi_test.go
@@ -296,13 +296,13 @@ func TestShouldBindUri(t *testing.T) {
 	router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
 		var person Person
 		assert.NoError(t, c.ShouldBindUri(&person))
-		assert.True(t, "" != person.Name)
-		assert.True(t, "" != person.ID)
+		assert.True(t, person.Name != "")
+		assert.True(t, person.ID != "")
 		c.String(http.StatusOK, "ShouldBindUri test OK")
 	})
 
 	path, _ := exampleFromPath("/rest/:name/:id")
-	w := performRequest(router, http.MethodGet, path)
+	w := PerformRequest(router, http.MethodGet, path)
 	assert.Equal(t, "ShouldBindUri test OK", w.Body.String())
 	assert.Equal(t, http.StatusOK, w.Code)
 }
@@ -318,13 +318,13 @@ func TestBindUri(t *testing.T) {
 	router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
 		var person Person
 		assert.NoError(t, c.BindUri(&person))
-		assert.True(t, "" != person.Name)
-		assert.True(t, "" != person.ID)
+		assert.True(t, person.Name != "")
+		assert.True(t, person.ID != "")
 		c.String(http.StatusOK, "BindUri test OK")
 	})
 
 	path, _ := exampleFromPath("/rest/:name/:id")
-	w := performRequest(router, http.MethodGet, path)
+	w := PerformRequest(router, http.MethodGet, path)
 	assert.Equal(t, "BindUri test OK", w.Body.String())
 	assert.Equal(t, http.StatusOK, w.Code)
 }
@@ -342,7 +342,7 @@ func TestBindUriError(t *testing.T) {
 	})
 
 	path1, _ := exampleFromPath("/new/rest/:num")
-	w1 := performRequest(router, http.MethodGet, path1)
+	w1 := PerformRequest(router, http.MethodGet, path1)
 	assert.Equal(t, http.StatusBadRequest, w1.Code)
 }
 
@@ -358,7 +358,7 @@ func TestRaceContextCopy(t *testing.T) {
 		go readWriteKeys(c.Copy())
 		c.String(http.StatusOK, "run OK, no panics")
 	})
-	w := performRequest(router, http.MethodGet, "/test/copy/race")
+	w := PerformRequest(router, http.MethodGet, "/test/copy/race")
 	assert.Equal(t, "run OK, no panics", w.Body.String())
 }
 
@@ -389,7 +389,7 @@ func TestGithubAPI(t *testing.T) {
 
 	for _, route := range githubAPI {
 		path, values := exampleFromPath(route.path)
-		w := performRequest(router, route.method, path)
+		w := PerformRequest(router, route.method, path)
 
 		// TEST
 		assert.Contains(t, w.Body.String(), "\"status\":\"good\"")
diff --git a/logger_test.go b/logger_test.go
index 80961ce..b704998 100644
--- a/logger_test.go
+++ b/logger_test.go
@@ -31,7 +31,7 @@ func TestLogger(t *testing.T) {
 	router.HEAD("/example", func(c *Context) {})
 	router.OPTIONS("/example", func(c *Context) {})
 
-	performRequest(router, "GET", "/example?a=100")
+	PerformRequest(router, "GET", "/example?a=100")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "GET")
 	assert.Contains(t, buffer.String(), "/example")
@@ -41,43 +41,43 @@ func TestLogger(t *testing.T) {
 	// like integration tests because they test the whole logging process rather
 	// than individual functions.  Im not sure where these should go.
 	buffer.Reset()
-	performRequest(router, "POST", "/example")
+	PerformRequest(router, "POST", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "POST")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "PUT", "/example")
+	PerformRequest(router, "PUT", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "PUT")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "DELETE", "/example")
+	PerformRequest(router, "DELETE", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "DELETE")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "PATCH", "/example")
+	PerformRequest(router, "PATCH", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "PATCH")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "HEAD", "/example")
+	PerformRequest(router, "HEAD", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "HEAD")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "OPTIONS", "/example")
+	PerformRequest(router, "OPTIONS", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "OPTIONS")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "GET", "/notfound")
+	PerformRequest(router, "GET", "/notfound")
 	assert.Contains(t, buffer.String(), "404")
 	assert.Contains(t, buffer.String(), "GET")
 	assert.Contains(t, buffer.String(), "/notfound")
@@ -95,7 +95,7 @@ func TestLoggerWithConfig(t *testing.T) {
 	router.HEAD("/example", func(c *Context) {})
 	router.OPTIONS("/example", func(c *Context) {})
 
-	performRequest(router, "GET", "/example?a=100")
+	PerformRequest(router, "GET", "/example?a=100")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "GET")
 	assert.Contains(t, buffer.String(), "/example")
@@ -105,43 +105,43 @@ func TestLoggerWithConfig(t *testing.T) {
 	// like integration tests because they test the whole logging process rather
 	// than individual functions.  Im not sure where these should go.
 	buffer.Reset()
-	performRequest(router, "POST", "/example")
+	PerformRequest(router, "POST", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "POST")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "PUT", "/example")
+	PerformRequest(router, "PUT", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "PUT")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "DELETE", "/example")
+	PerformRequest(router, "DELETE", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "DELETE")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "PATCH", "/example")
+	PerformRequest(router, "PATCH", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "PATCH")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "HEAD", "/example")
+	PerformRequest(router, "HEAD", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "HEAD")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "OPTIONS", "/example")
+	PerformRequest(router, "OPTIONS", "/example")
 	assert.Contains(t, buffer.String(), "200")
 	assert.Contains(t, buffer.String(), "OPTIONS")
 	assert.Contains(t, buffer.String(), "/example")
 
 	buffer.Reset()
-	performRequest(router, "GET", "/notfound")
+	PerformRequest(router, "GET", "/notfound")
 	assert.Contains(t, buffer.String(), "404")
 	assert.Contains(t, buffer.String(), "GET")
 	assert.Contains(t, buffer.String(), "/notfound")
@@ -169,7 +169,7 @@ func TestLoggerWithFormatter(t *testing.T) {
 		)
 	}))
 	router.GET("/example", func(c *Context) {})
-	performRequest(router, "GET", "/example?a=100")
+	PerformRequest(router, "GET", "/example?a=100")
 
 	// output test
 	assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
@@ -181,7 +181,7 @@ func TestLoggerWithFormatter(t *testing.T) {
 
 func TestLoggerWithConfigFormatting(t *testing.T) {
 	var gotParam LogFormatterParams
-	var gotKeys map[string]interface{}
+	var gotKeys map[string]any
 	buffer := new(bytes.Buffer)
 
 	router := New()
@@ -208,8 +208,9 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
 		// set dummy ClientIP
 		c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
 		gotKeys = c.Keys
+		time.Sleep(time.Millisecond)
 	})
-	performRequest(router, "GET", "/example?a=100")
+	PerformRequest(router, "GET", "/example?a=100")
 
 	// output test
 	assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
@@ -228,7 +229,6 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
 	assert.Equal(t, "/example?a=100", gotParam.Path)
 	assert.Empty(t, gotParam.ErrorMessage)
 	assert.Equal(t, gotKeys, gotParam.Keys)
-
 }
 
 func TestDefaultLogFormatter(t *testing.T) {
@@ -282,7 +282,6 @@ func TestDefaultLogFormatter(t *testing.T) {
 
 	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|            5s |     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam))
 	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|    2743h29m3s |     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
-
 }
 
 func TestColorForMethod(t *testing.T) {
@@ -369,15 +368,15 @@ func TestErrorLogger(t *testing.T) {
 		c.String(http.StatusInternalServerError, "hola!")
 	})
 
-	w := performRequest(router, "GET", "/error")
+	w := PerformRequest(router, "GET", "/error")
 	assert.Equal(t, http.StatusOK, w.Code)
 	assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
 
-	w = performRequest(router, "GET", "/abort")
+	w = PerformRequest(router, "GET", "/abort")
 	assert.Equal(t, http.StatusUnauthorized, w.Code)
 	assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
 
-	w = performRequest(router, "GET", "/print")
+	w = PerformRequest(router, "GET", "/print")
 	assert.Equal(t, http.StatusInternalServerError, w.Code)
 	assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
 }
@@ -389,11 +388,11 @@ func TestLoggerWithWriterSkippingPaths(t *testing.T) {
 	router.GET("/logged", func(c *Context) {})
 	router.GET("/skipped", func(c *Context) {})
 
-	performRequest(router, "GET", "/logged")
+	PerformRequest(router, "GET", "/logged")
 	assert.Contains(t, buffer.String(), "200")
 
 	buffer.Reset()
-	performRequest(router, "GET", "/skipped")
+	PerformRequest(router, "GET", "/skipped")
 	assert.Contains(t, buffer.String(), "")
 }
 
@@ -407,11 +406,11 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) {
 	router.GET("/logged", func(c *Context) {})
 	router.GET("/skipped", func(c *Context) {})
 
-	performRequest(router, "GET", "/logged")
+	PerformRequest(router, "GET", "/logged")
 	assert.Contains(t, buffer.String(), "200")
 
 	buffer.Reset()
-	performRequest(router, "GET", "/skipped")
+	PerformRequest(router, "GET", "/skipped")
 	assert.Contains(t, buffer.String(), "")
 }
 
diff --git a/middleware_test.go b/middleware_test.go
index fca1c53..e0a756c 100644
--- a/middleware_test.go
+++ b/middleware_test.go
@@ -35,7 +35,7 @@ func TestMiddlewareGeneralCase(t *testing.T) {
 		signature += " XX "
 	})
 	// RUN
-	w := performRequest(router, "GET", "/")
+	w := PerformRequest(router, "GET", "/")
 
 	// TEST
 	assert.Equal(t, http.StatusOK, w.Code)
@@ -71,7 +71,7 @@ func TestMiddlewareNoRoute(t *testing.T) {
 		signature += " X "
 	})
 	// RUN
-	w := performRequest(router, "GET", "/")
+	w := PerformRequest(router, "GET", "/")
 
 	// TEST
 	assert.Equal(t, http.StatusNotFound, w.Code)
@@ -108,7 +108,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
 		signature += " XX "
 	})
 	// RUN
-	w := performRequest(router, "GET", "/")
+	w := PerformRequest(router, "GET", "/")
 
 	// TEST
 	assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
@@ -118,7 +118,10 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
 func TestMiddlewareNoMethodDisabled(t *testing.T) {
 	signature := ""
 	router := New()
+
+	// NoMethod disabled
 	router.HandleMethodNotAllowed = false
+
 	router.Use(func(c *Context) {
 		signature += "A"
 		c.Next()
@@ -144,8 +147,9 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
 	router.POST("/", func(c *Context) {
 		signature += " XX "
 	})
+
 	// RUN
-	w := performRequest(router, "GET", "/")
+	w := PerformRequest(router, "GET", "/")
 
 	// TEST
 	assert.Equal(t, http.StatusNotFound, w.Code)
@@ -171,7 +175,7 @@ func TestMiddlewareAbort(t *testing.T) {
 	})
 
 	// RUN
-	w := performRequest(router, "GET", "/")
+	w := PerformRequest(router, "GET", "/")
 
 	// TEST
 	assert.Equal(t, http.StatusUnauthorized, w.Code)
@@ -186,14 +190,13 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
 		c.Next()
 		c.AbortWithStatus(http.StatusGone)
 		signature += "B"
-
 	})
 	router.GET("/", func(c *Context) {
 		signature += "C"
 		c.Next()
 	})
 	// RUN
-	w := performRequest(router, "GET", "/")
+	w := PerformRequest(router, "GET", "/")
 
 	// TEST
 	assert.Equal(t, http.StatusGone, w.Code)
@@ -216,7 +219,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
 		signature += "C"
 	})
 	// RUN
-	w := performRequest(router, "GET", "/")
+	w := PerformRequest(router, "GET", "/")
 
 	// TEST
 	assert.Equal(t, http.StatusInternalServerError, w.Code)
@@ -243,7 +246,7 @@ func TestMiddlewareWrite(t *testing.T) {
 		})
 	})
 
-	w := performRequest(router, "GET", "/")
+	w := PerformRequest(router, "GET", "/")
 
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 	assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
diff --git a/recovery_test.go b/recovery_test.go
index d164bfa..2c327a6 100644
--- a/recovery_test.go
+++ b/recovery_test.go
@@ -27,7 +27,7 @@ func TestPanicClean(t *testing.T) {
 		panic("Oupps, Houston, we have a problem")
 	})
 	// RUN
-	w := performRequest(router, "GET", "/recovery",
+	w := PerformRequest(router, "GET", "/recovery",
 		header{
 			Key:   "Host",
 			Value: "www.google.com",
@@ -57,7 +57,7 @@ func TestPanicInHandler(t *testing.T) {
 		panic("Oupps, Houston, we have a problem")
 	})
 	// RUN
-	w := performRequest(router, "GET", "/recovery")
+	w := PerformRequest(router, "GET", "/recovery")
 	// TEST
 	assert.Equal(t, http.StatusInternalServerError, w.Code)
 	assert.Contains(t, buffer.String(), "panic recovered")
@@ -68,7 +68,7 @@ func TestPanicInHandler(t *testing.T) {
 	// Debug mode prints the request
 	SetMode(DebugMode)
 	// RUN
-	w = performRequest(router, "GET", "/recovery")
+	w = PerformRequest(router, "GET", "/recovery")
 	// TEST
 	assert.Equal(t, http.StatusInternalServerError, w.Code)
 	assert.Contains(t, buffer.String(), "GET /recovery")
@@ -85,7 +85,7 @@ func TestPanicWithAbort(t *testing.T) {
 		panic("Oupps, Houston, we have a problem")
 	})
 	// RUN
-	w := performRequest(router, "GET", "/recovery")
+	w := PerformRequest(router, "GET", "/recovery")
 	// TEST
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 }
@@ -122,7 +122,6 @@ func TestPanicWithBrokenPipe(t *testing.T) {
 
 	for errno, expectMsg := range expectMsgs {
 		t.Run(expectMsg, func(t *testing.T) {
-
 			var buf bytes.Buffer
 
 			router := New()
@@ -137,7 +136,7 @@ func TestPanicWithBrokenPipe(t *testing.T) {
 				panic(e)
 			})
 			// RUN
-			w := performRequest(router, "GET", "/recovery")
+			w := PerformRequest(router, "GET", "/recovery")
 			// TEST
 			assert.Equal(t, expectCode, w.Code)
 			assert.Contains(t, strings.ToLower(buf.String()), expectMsg)
@@ -149,7 +148,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
 	errBuffer := new(bytes.Buffer)
 	buffer := new(bytes.Buffer)
 	router := New()
-	handleRecovery := func(c *Context, err interface{}) {
+	handleRecovery := func(c *Context, err any) {
 		errBuffer.WriteString(err.(string))
 		c.AbortWithStatus(http.StatusBadRequest)
 	}
@@ -158,7 +157,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
 		panic("Oupps, Houston, we have a problem")
 	})
 	// RUN
-	w := performRequest(router, "GET", "/recovery")
+	w := PerformRequest(router, "GET", "/recovery")
 	// TEST
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 	assert.Contains(t, buffer.String(), "panic recovered")
@@ -169,7 +168,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
 	// Debug mode prints the request
 	SetMode(DebugMode)
 	// RUN
-	w = performRequest(router, "GET", "/recovery")
+	w = PerformRequest(router, "GET", "/recovery")
 	// TEST
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 	assert.Contains(t, buffer.String(), "GET /recovery")
@@ -184,7 +183,7 @@ func TestCustomRecovery(t *testing.T) {
 	buffer := new(bytes.Buffer)
 	router := New()
 	DefaultErrorWriter = buffer
-	handleRecovery := func(c *Context, err interface{}) {
+	handleRecovery := func(c *Context, err any) {
 		errBuffer.WriteString(err.(string))
 		c.AbortWithStatus(http.StatusBadRequest)
 	}
@@ -193,7 +192,7 @@ func TestCustomRecovery(t *testing.T) {
 		panic("Oupps, Houston, we have a problem")
 	})
 	// RUN
-	w := performRequest(router, "GET", "/recovery")
+	w := PerformRequest(router, "GET", "/recovery")
 	// TEST
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 	assert.Contains(t, buffer.String(), "panic recovered")
@@ -204,7 +203,7 @@ func TestCustomRecovery(t *testing.T) {
 	// Debug mode prints the request
 	SetMode(DebugMode)
 	// RUN
-	w = performRequest(router, "GET", "/recovery")
+	w = PerformRequest(router, "GET", "/recovery")
 	// TEST
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 	assert.Contains(t, buffer.String(), "GET /recovery")
@@ -219,7 +218,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
 	buffer := new(bytes.Buffer)
 	router := New()
 	DefaultErrorWriter = buffer
-	handleRecovery := func(c *Context, err interface{}) {
+	handleRecovery := func(c *Context, err any) {
 		errBuffer.WriteString(err.(string))
 		c.AbortWithStatus(http.StatusBadRequest)
 	}
@@ -228,7 +227,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
 		panic("Oupps, Houston, we have a problem")
 	})
 	// RUN
-	w := performRequest(router, "GET", "/recovery")
+	w := PerformRequest(router, "GET", "/recovery")
 	// TEST
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 	assert.Contains(t, buffer.String(), "panic recovered")
@@ -239,7 +238,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
 	// Debug mode prints the request
 	SetMode(DebugMode)
 	// RUN
-	w = performRequest(router, "GET", "/recovery")
+	w = PerformRequest(router, "GET", "/recovery")
 	// TEST
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 	assert.Contains(t, buffer.String(), "GET /recovery")
diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go
index d16cf6e..7b3601a 100644
--- a/render/render_msgpack_test.go
+++ b/render/render_msgpack_test.go
@@ -21,7 +21,7 @@ import (
 
 func TestRenderMsgPack(t *testing.T) {
 	w := httptest.NewRecorder()
-	data := map[string]interface{}{
+	data := map[string]any{
 		"foo": "bar",
 	}
 
diff --git a/render/render_test.go b/render/render_test.go
index 353c82b..8b28dc3 100644
--- a/render/render_test.go
+++ b/render/render_test.go
@@ -14,10 +14,9 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/golang/protobuf/proto"
-	"github.com/stretchr/testify/assert"
-
 	testdata "github.com/gin-gonic/gin/testdata/protoexample"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/protobuf/proto"
 )
 
 // TODO unit tests
@@ -25,7 +24,7 @@ import (
 
 func TestRenderJSON(t *testing.T) {
 	w := httptest.NewRecorder()
-	data := map[string]interface{}{
+	data := map[string]any{
 		"foo":  "bar",
 		"html": "<b>",
 	}
@@ -50,7 +49,7 @@ func TestRenderJSONPanics(t *testing.T) {
 
 func TestRenderIndentedJSON(t *testing.T) {
 	w := httptest.NewRecorder()
-	data := map[string]interface{}{
+	data := map[string]any{
 		"foo": "bar",
 		"bar": "foo",
 	}
@@ -73,7 +72,7 @@ func TestRenderIndentedJSONPanics(t *testing.T) {
 
 func TestRenderSecureJSON(t *testing.T) {
 	w1 := httptest.NewRecorder()
-	data := map[string]interface{}{
+	data := map[string]any{
 		"foo": "bar",
 	}
 
@@ -87,7 +86,7 @@ func TestRenderSecureJSON(t *testing.T) {
 	assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
 
 	w2 := httptest.NewRecorder()
-	datas := []map[string]interface{}{{
+	datas := []map[string]any{{
 		"foo": "bar",
 	}, {
 		"bar": "foo",
@@ -110,7 +109,7 @@ func TestRenderSecureJSONFail(t *testing.T) {
 
 func TestRenderJsonpJSON(t *testing.T) {
 	w1 := httptest.NewRecorder()
-	data := map[string]interface{}{
+	data := map[string]any{
 		"foo": "bar",
 	}
 
@@ -124,7 +123,7 @@ func TestRenderJsonpJSON(t *testing.T) {
 	assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
 
 	w2 := httptest.NewRecorder()
-	datas := []map[string]interface{}{{
+	datas := []map[string]any{{
 		"foo": "bar",
 	}, {
 		"bar": "foo",
@@ -138,7 +137,7 @@ func TestRenderJsonpJSON(t *testing.T) {
 
 func TestRenderJsonpJSONError2(t *testing.T) {
 	w := httptest.NewRecorder()
-	data := map[string]interface{}{
+	data := map[string]any{
 		"foo": "bar",
 	}
 	(JsonpJSON{"", data}).WriteContentType(w)
@@ -162,7 +161,7 @@ func TestRenderJsonpJSONFail(t *testing.T) {
 
 func TestRenderAsciiJSON(t *testing.T) {
 	w1 := httptest.NewRecorder()
-	data1 := map[string]interface{}{
+	data1 := map[string]any{
 		"lang": "GO语言",
 		"tag":  "<br>",
 	}
@@ -191,7 +190,7 @@ func TestRenderAsciiJSONFail(t *testing.T) {
 
 func TestRenderPureJSON(t *testing.T) {
 	w := httptest.NewRecorder()
-	data := map[string]interface{}{
+	data := map[string]any{
 		"foo":  "bar",
 		"html": "<b>",
 	}
@@ -201,7 +200,7 @@ func TestRenderPureJSON(t *testing.T) {
 	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
 }
 
-type xmlmap map[string]interface{}
+type xmlmap map[string]any
 
 // Allows type H to be used with xml.Marshal
 func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
@@ -245,7 +244,7 @@ b:
 type fail struct{}
 
 // Hook MarshalYAML
-func (ft *fail) MarshalYAML() (interface{}, error) {
+func (ft *fail) MarshalYAML() (any, error) {
 	return nil, errors.New("fail")
 }
 
@@ -359,13 +358,13 @@ func TestRenderString(t *testing.T) {
 
 	(String{
 		Format: "hello %s %d",
-		Data:   []interface{}{},
+		Data:   []any{},
 	}).WriteContentType(w)
 	assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
 
 	err := (String{
 		Format: "hola %s %d",
-		Data:   []interface{}{"manu", 2},
+		Data:   []any{"manu", 2},
 	}).Render(w)
 
 	assert.NoError(t, err)
@@ -378,7 +377,7 @@ func TestRenderStringLenZero(t *testing.T) {
 
 	err := (String{
 		Format: "hola %s %d",
-		Data:   []interface{}{},
+		Data:   []any{},
 	}).Render(w)
 
 	assert.NoError(t, err)
@@ -391,7 +390,7 @@ func TestRenderHTMLTemplate(t *testing.T) {
 	templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
 
 	htmlRender := HTMLProduction{Template: templ}
-	instance := htmlRender.Instance("t", map[string]interface{}{
+	instance := htmlRender.Instance("t", map[string]any{
 		"name": "alexandernyquist",
 	})
 
@@ -407,7 +406,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
 	templ := template.Must(template.New("").Parse(`Hello {{.name}}`))
 
 	htmlRender := HTMLProduction{Template: templ}
-	instance := htmlRender.Instance("", map[string]interface{}{
+	instance := htmlRender.Instance("", map[string]any{
 		"name": "alexandernyquist",
 	})
 
@@ -420,12 +419,13 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
 
 func TestRenderHTMLDebugFiles(t *testing.T) {
 	w := httptest.NewRecorder()
-	htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"},
+	htmlRender := HTMLDebug{
+		Files:   []string{"../testdata/template/hello.tmpl"},
 		Glob:    "",
 		Delims:  Delims{Left: "{[{", Right: "}]}"},
 		FuncMap: nil,
 	}
-	instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{
+	instance := htmlRender.Instance("hello.tmpl", map[string]any{
 		"name": "thinkerou",
 	})
 
@@ -438,12 +438,13 @@ func TestRenderHTMLDebugFiles(t *testing.T) {
 
 func TestRenderHTMLDebugGlob(t *testing.T) {
 	w := httptest.NewRecorder()
-	htmlRender := HTMLDebug{Files: nil,
+	htmlRender := HTMLDebug{
+		Files:   nil,
 		Glob:    "../testdata/template/hello*",
 		Delims:  Delims{Left: "{[{", Right: "}]}"},
 		FuncMap: nil,
 	}
-	instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{
+	instance := htmlRender.Instance("hello.tmpl", map[string]any{
 		"name": "thinkerou",
 	})
 
@@ -455,7 +456,8 @@ func TestRenderHTMLDebugGlob(t *testing.T) {
 }
 
 func TestRenderHTMLDebugPanics(t *testing.T) {
-	htmlRender := HTMLDebug{Files: nil,
+	htmlRender := HTMLDebug{
+		Files:   nil,
 		Glob:    "",
 		Delims:  Delims{"{{", "}}"},
 		FuncMap: nil,
diff --git a/response_writer_test.go b/response_writer_test.go
index 1f113e7..9061d02 100644
--- a/response_writer_test.go
+++ b/response_writer_test.go
@@ -17,12 +17,14 @@ import (
 // func (w *responseWriter) CloseNotify() <-chan bool {
 // func (w *responseWriter) Flush() {
 
-var _ ResponseWriter = &responseWriter{}
-var _ http.ResponseWriter = &responseWriter{}
-var _ http.ResponseWriter = ResponseWriter(&responseWriter{})
-var _ http.Hijacker = ResponseWriter(&responseWriter{})
-var _ http.Flusher = ResponseWriter(&responseWriter{})
-var _ http.CloseNotifier = ResponseWriter(&responseWriter{})
+var (
+	_ ResponseWriter      = &responseWriter{}
+	_ http.ResponseWriter = &responseWriter{}
+	_ http.ResponseWriter = ResponseWriter(&responseWriter{})
+	_ http.Hijacker       = ResponseWriter(&responseWriter{})
+	_ http.Flusher        = ResponseWriter(&responseWriter{})
+	_ http.CloseNotifier  = ResponseWriter(&responseWriter{})
+)
 
 func init() {
 	SetMode(TestMode)
diff --git a/routergroup_test.go b/routergroup_test.go
index d6d8b45..c1fad3a 100644
--- a/routergroup_test.go
+++ b/routergroup_test.go
@@ -80,11 +80,11 @@ func performRequestInGroup(t *testing.T, method string) {
 		panic("unknown method")
 	}
 
-	w := performRequest(router, method, "/v1/login/test")
+	w := PerformRequest(router, method, "/v1/login/test")
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 	assert.Equal(t, "the method was "+method+" and index 3", w.Body.String())
 
-	w = performRequest(router, method, "/v1/test")
+	w = PerformRequest(router, method, "/v1/test")
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 	assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
 }
@@ -111,6 +111,17 @@ func TestRouterGroupInvalidStaticFile(t *testing.T) {
 	})
 }
 
+func TestRouterGroupInvalidStaticFileFS(t *testing.T) {
+	router := New()
+	assert.Panics(t, func() {
+		router.StaticFileFS("/path/:param", "favicon.ico", Dir(".", false))
+	})
+
+	assert.Panics(t, func() {
+		router.StaticFileFS("/path/*param", "favicon.ico", Dir(".", false))
+	})
+}
+
 func TestRouterGroupTooManyHandlers(t *testing.T) {
 	const (
 		panicValue = "too many handlers"
@@ -177,6 +188,7 @@ func testRoutesInterface(t *testing.T, r IRoutes) {
 	assert.Equal(t, r, r.HEAD("/", handler))
 
 	assert.Equal(t, r, r.StaticFile("/file", "."))
+	assert.Equal(t, r, r.StaticFileFS("/static2", ".", Dir(".", false)))
 	assert.Equal(t, r, r.Static("/static", "."))
 	assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false)))
 }
diff --git a/routes_test.go b/routes_test.go
index 036fa1c..5643097 100644
--- a/routes_test.go
+++ b/routes_test.go
@@ -21,7 +21,8 @@ type header struct {
 	Value string
 }
 
-func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
+// PerformRequest for testing gin router.
+func PerformRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
 	req := httptest.NewRequest(method, path, nil)
 	for _, h := range headers {
 		req.Header.Add(h.Key, h.Value)
@@ -42,11 +43,11 @@ func testRouteOK(method string, t *testing.T) {
 		passed = true
 	})
 
-	w := performRequest(r, method, "/test")
+	w := PerformRequest(r, method, "/test")
 	assert.True(t, passed)
 	assert.Equal(t, http.StatusOK, w.Code)
 
-	performRequest(r, method, "/test2")
+	PerformRequest(r, method, "/test2")
 	assert.True(t, passedAny)
 }
 
@@ -58,7 +59,7 @@ func testRouteNotOK(method string, t *testing.T) {
 		passed = true
 	})
 
-	w := performRequest(router, method, "/test")
+	w := PerformRequest(router, method, "/test")
 
 	assert.False(t, passed)
 	assert.Equal(t, http.StatusNotFound, w.Code)
@@ -79,7 +80,7 @@ func testRouteNotOK2(method string, t *testing.T) {
 		passed = true
 	})
 
-	w := performRequest(router, method, "/test")
+	w := PerformRequest(router, method, "/test")
 
 	assert.False(t, passed)
 	assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
@@ -99,7 +100,7 @@ func TestRouterMethod(t *testing.T) {
 		c.String(http.StatusOK, "sup3")
 	})
 
-	w := performRequest(router, http.MethodPut, "/hey")
+	w := PerformRequest(router, http.MethodPut, "/hey")
 
 	assert.Equal(t, http.StatusOK, w.Code)
 	assert.Equal(t, "called", w.Body.String())
@@ -150,50 +151,50 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
 	router.POST("/path3", func(c *Context) {})
 	router.PUT("/path4/", func(c *Context) {})
 
-	w := performRequest(router, http.MethodGet, "/path/")
+	w := PerformRequest(router, http.MethodGet, "/path/")
 	assert.Equal(t, "/path", w.Header().Get("Location"))
 	assert.Equal(t, http.StatusMovedPermanently, w.Code)
 
-	w = performRequest(router, http.MethodGet, "/path2")
+	w = PerformRequest(router, http.MethodGet, "/path2")
 	assert.Equal(t, "/path2/", w.Header().Get("Location"))
 	assert.Equal(t, http.StatusMovedPermanently, w.Code)
 
-	w = performRequest(router, http.MethodPost, "/path3/")
+	w = PerformRequest(router, http.MethodPost, "/path3/")
 	assert.Equal(t, "/path3", w.Header().Get("Location"))
 	assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
 
-	w = performRequest(router, http.MethodPut, "/path4")
+	w = PerformRequest(router, http.MethodPut, "/path4")
 	assert.Equal(t, "/path4/", w.Header().Get("Location"))
 	assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
 
-	w = performRequest(router, http.MethodGet, "/path")
+	w = PerformRequest(router, http.MethodGet, "/path")
 	assert.Equal(t, http.StatusOK, w.Code)
 
-	w = performRequest(router, http.MethodGet, "/path2/")
+	w = PerformRequest(router, http.MethodGet, "/path2/")
 	assert.Equal(t, http.StatusOK, w.Code)
 
-	w = performRequest(router, http.MethodPost, "/path3")
+	w = PerformRequest(router, http.MethodPost, "/path3")
 	assert.Equal(t, http.StatusOK, w.Code)
 
-	w = performRequest(router, http.MethodPut, "/path4/")
+	w = PerformRequest(router, http.MethodPut, "/path4/")
 	assert.Equal(t, http.StatusOK, w.Code)
 
-	w = performRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"})
+	w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"})
 	assert.Equal(t, "/api/path2/", w.Header().Get("Location"))
 	assert.Equal(t, 301, w.Code)
 
-	w = performRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
+	w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
 	assert.Equal(t, 200, w.Code)
 
 	router.RedirectTrailingSlash = false
 
-	w = performRequest(router, http.MethodGet, "/path/")
+	w = PerformRequest(router, http.MethodGet, "/path/")
 	assert.Equal(t, http.StatusNotFound, w.Code)
-	w = performRequest(router, http.MethodGet, "/path2")
+	w = PerformRequest(router, http.MethodGet, "/path2")
 	assert.Equal(t, http.StatusNotFound, w.Code)
-	w = performRequest(router, http.MethodPost, "/path3/")
+	w = PerformRequest(router, http.MethodPost, "/path3/")
 	assert.Equal(t, http.StatusNotFound, w.Code)
-	w = performRequest(router, http.MethodPut, "/path4")
+	w = PerformRequest(router, http.MethodPut, "/path4")
 	assert.Equal(t, http.StatusNotFound, w.Code)
 }
 
@@ -207,19 +208,19 @@ func TestRouteRedirectFixedPath(t *testing.T) {
 	router.POST("/PATH3", func(c *Context) {})
 	router.POST("/Path4/", func(c *Context) {})
 
-	w := performRequest(router, http.MethodGet, "/PATH")
+	w := PerformRequest(router, http.MethodGet, "/PATH")
 	assert.Equal(t, "/path", w.Header().Get("Location"))
 	assert.Equal(t, http.StatusMovedPermanently, w.Code)
 
-	w = performRequest(router, http.MethodGet, "/path2")
+	w = PerformRequest(router, http.MethodGet, "/path2")
 	assert.Equal(t, "/Path2", w.Header().Get("Location"))
 	assert.Equal(t, http.StatusMovedPermanently, w.Code)
 
-	w = performRequest(router, http.MethodPost, "/path3")
+	w = PerformRequest(router, http.MethodPost, "/path3")
 	assert.Equal(t, "/PATH3", w.Header().Get("Location"))
 	assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
 
-	w = performRequest(router, http.MethodPost, "/path4")
+	w = PerformRequest(router, http.MethodPost, "/path4")
 	assert.Equal(t, "/Path4/", w.Header().Get("Location"))
 	assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
 }
@@ -248,7 +249,7 @@ func TestRouteParamsByName(t *testing.T) {
 		assert.False(t, ok)
 	})
 
-	w := performRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
+	w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
 
 	assert.Equal(t, http.StatusOK, w.Code)
 	assert.Equal(t, "john", name)
@@ -281,7 +282,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
 		assert.False(t, ok)
 	})
 
-	w := performRequest(router, http.MethodGet, "//test//john//smith//is//super//great")
+	w := PerformRequest(router, http.MethodGet, "//test//john//smith//is//super//great")
 
 	assert.Equal(t, http.StatusOK, w.Code)
 	assert.Equal(t, "john", name)
@@ -309,16 +310,50 @@ func TestRouteStaticFile(t *testing.T) {
 	router.Static("/using_static", dir)
 	router.StaticFile("/result", f.Name())
 
-	w := performRequest(router, http.MethodGet, "/using_static/"+filename)
-	w2 := performRequest(router, http.MethodGet, "/result")
+	w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
+	w2 := PerformRequest(router, http.MethodGet, "/result")
 
 	assert.Equal(t, w, w2)
 	assert.Equal(t, http.StatusOK, w.Code)
 	assert.Equal(t, "Gin Web Framework", w.Body.String())
 	assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
 
-	w3 := performRequest(router, http.MethodHead, "/using_static/"+filename)
-	w4 := performRequest(router, http.MethodHead, "/result")
+	w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
+	w4 := PerformRequest(router, http.MethodHead, "/result")
+
+	assert.Equal(t, w3, w4)
+	assert.Equal(t, http.StatusOK, w3.Code)
+}
+
+// TestHandleStaticFile - ensure the static file handles properly
+func TestRouteStaticFileFS(t *testing.T) {
+	// SETUP file
+	testRoot, _ := os.Getwd()
+	f, err := ioutil.TempFile(testRoot, "")
+	if err != nil {
+		t.Error(err)
+	}
+	defer os.Remove(f.Name())
+	_, err = f.WriteString("Gin Web Framework")
+	assert.NoError(t, err)
+	f.Close()
+
+	dir, filename := filepath.Split(f.Name())
+	// SETUP gin
+	router := New()
+	router.Static("/using_static", dir)
+	router.StaticFileFS("/result_fs", filename, Dir(dir, false))
+
+	w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
+	w2 := PerformRequest(router, http.MethodGet, "/result_fs")
+
+	assert.Equal(t, w, w2)
+	assert.Equal(t, http.StatusOK, w.Code)
+	assert.Equal(t, "Gin Web Framework", w.Body.String())
+	assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
+
+	w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
+	w4 := PerformRequest(router, http.MethodHead, "/result_fs")
 
 	assert.Equal(t, w3, w4)
 	assert.Equal(t, http.StatusOK, w3.Code)
@@ -329,7 +364,7 @@ func TestRouteStaticListingDir(t *testing.T) {
 	router := New()
 	router.StaticFS("/", Dir("./", true))
 
-	w := performRequest(router, http.MethodGet, "/")
+	w := PerformRequest(router, http.MethodGet, "/")
 
 	assert.Equal(t, http.StatusOK, w.Code)
 	assert.Contains(t, w.Body.String(), "gin.go")
@@ -341,7 +376,7 @@ func TestRouteStaticNoListing(t *testing.T) {
 	router := New()
 	router.Static("/", "./")
 
-	w := performRequest(router, http.MethodGet, "/")
+	w := PerformRequest(router, http.MethodGet, "/")
 
 	assert.Equal(t, http.StatusNotFound, w.Code)
 	assert.NotContains(t, w.Body.String(), "gin.go")
@@ -356,7 +391,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
 	})
 	static.Static("/", "./")
 
-	w := performRequest(router, http.MethodGet, "/gin.go")
+	w := PerformRequest(router, http.MethodGet, "/gin.go")
 
 	assert.Equal(t, http.StatusOK, w.Code)
 	assert.Contains(t, w.Body.String(), "package gin")
@@ -372,13 +407,13 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
 	router := New()
 	router.HandleMethodNotAllowed = true
 	router.POST("/path", func(c *Context) {})
-	w := performRequest(router, http.MethodGet, "/path")
+	w := PerformRequest(router, http.MethodGet, "/path")
 	assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
 
 	router.NoMethod(func(c *Context) {
 		c.String(http.StatusTeapot, "responseText")
 	})
-	w = performRequest(router, http.MethodGet, "/path")
+	w = PerformRequest(router, http.MethodGet, "/path")
 	assert.Equal(t, "responseText", w.Body.String())
 	assert.Equal(t, http.StatusTeapot, w.Code)
 }
@@ -389,7 +424,7 @@ func TestRouteNotAllowedEnabled2(t *testing.T) {
 	// add one methodTree to trees
 	router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}})
 	router.GET("/path2", func(c *Context) {})
-	w := performRequest(router, http.MethodPost, "/path2")
+	w := PerformRequest(router, http.MethodPost, "/path2")
 	assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
 }
 
@@ -397,13 +432,13 @@ func TestRouteNotAllowedDisabled(t *testing.T) {
 	router := New()
 	router.HandleMethodNotAllowed = false
 	router.POST("/path", func(c *Context) {})
-	w := performRequest(router, http.MethodGet, "/path")
+	w := PerformRequest(router, http.MethodGet, "/path")
 	assert.Equal(t, http.StatusNotFound, w.Code)
 
 	router.NoMethod(func(c *Context) {
 		c.String(http.StatusTeapot, "responseText")
 	})
-	w = performRequest(router, http.MethodGet, "/path")
+	w = PerformRequest(router, http.MethodGet, "/path")
 	assert.Equal(t, "404 page not found", w.Body.String())
 	assert.Equal(t, http.StatusNotFound, w.Code)
 }
@@ -423,7 +458,7 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {
 		{"/nope", http.StatusNotFound, ""}, // NotFound
 	}
 	for _, tr := range testRoutes {
-		w := performRequest(router, "GET", tr.route)
+		w := PerformRequest(router, "GET", tr.route)
 		assert.Equal(t, tr.code, w.Code)
 		if w.Code != http.StatusNotFound {
 			assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
@@ -453,7 +488,7 @@ func TestRouterNotFound(t *testing.T) {
 		{"/nope", http.StatusNotFound, ""},                 // NotFound
 	}
 	for _, tr := range testRoutes {
-		w := performRequest(router, http.MethodGet, tr.route)
+		w := PerformRequest(router, http.MethodGet, tr.route)
 		assert.Equal(t, tr.code, w.Code)
 		if w.Code != http.StatusNotFound {
 			assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
@@ -466,21 +501,36 @@ func TestRouterNotFound(t *testing.T) {
 		c.AbortWithStatus(http.StatusNotFound)
 		notFound = true
 	})
-	w := performRequest(router, http.MethodGet, "/nope")
+	w := PerformRequest(router, http.MethodGet, "/nope")
 	assert.Equal(t, http.StatusNotFound, w.Code)
 	assert.True(t, notFound)
 
 	// Test other method than GET (want 307 instead of 301)
 	router.PATCH("/path", func(c *Context) {})
-	w = performRequest(router, http.MethodPatch, "/path/")
+	w = PerformRequest(router, http.MethodPatch, "/path/")
 	assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
 	assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
 
 	// Test special case where no node for the prefix "/" exists
 	router = New()
 	router.GET("/a", func(c *Context) {})
-	w = performRequest(router, http.MethodGet, "/")
+	w = PerformRequest(router, http.MethodGet, "/")
 	assert.Equal(t, http.StatusNotFound, w.Code)
+
+	// Reproduction test for the bug of issue #2843
+	router = New()
+	router.NoRoute(func(c *Context) {
+		if c.Request.RequestURI == "/login" {
+			c.String(200, "login")
+		}
+	})
+	router.GET("/logout", func(c *Context) {
+		c.String(200, "logout")
+	})
+	w = PerformRequest(router, http.MethodGet, "/login")
+	assert.Equal(t, "login", w.Body.String())
+	w = PerformRequest(router, http.MethodGet, "/logout")
+	assert.Equal(t, "logout", w.Body.String())
 }
 
 func TestRouterStaticFSNotFound(t *testing.T) {
@@ -490,10 +540,10 @@ func TestRouterStaticFSNotFound(t *testing.T) {
 		c.String(404, "non existent")
 	})
 
-	w := performRequest(router, http.MethodGet, "/nonexistent")
+	w := PerformRequest(router, http.MethodGet, "/nonexistent")
 	assert.Equal(t, "non existent", w.Body.String())
 
-	w = performRequest(router, http.MethodHead, "/nonexistent")
+	w = PerformRequest(router, http.MethodHead, "/nonexistent")
 	assert.Equal(t, "non existent", w.Body.String())
 }
 
@@ -503,7 +553,7 @@ func TestRouterStaticFSFileNotFound(t *testing.T) {
 	router.StaticFS("/", http.FileSystem(http.Dir(".")))
 
 	assert.NotPanics(t, func() {
-		performRequest(router, http.MethodGet, "/nonexistent")
+		PerformRequest(router, http.MethodGet, "/nonexistent")
 	})
 }
 
@@ -520,11 +570,11 @@ func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {
 	router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
 
 	// First access
-	performRequest(router, http.MethodGet, "/nonexistent")
+	PerformRequest(router, http.MethodGet, "/nonexistent")
 	assert.Equal(t, 1, middlewareCalledNum)
 
 	// Second access
-	performRequest(router, http.MethodHead, "/nonexistent")
+	PerformRequest(router, http.MethodHead, "/nonexistent")
 	assert.Equal(t, 2, middlewareCalledNum)
 }
 
@@ -543,7 +593,7 @@ func TestRouteRawPath(t *testing.T) {
 		assert.Equal(t, "222", num)
 	})
 
-	w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222")
+	w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222")
 	assert.Equal(t, http.StatusOK, w.Code)
 }
 
@@ -563,7 +613,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
 		assert.Equal(t, "333", num)
 	})
 
-	w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333")
+	w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333")
 	assert.Equal(t, http.StatusOK, w.Code)
 }
 
@@ -574,7 +624,7 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) {
 		c.Next()
 	})
 
-	w := performRequest(route, http.MethodGet, "/NotFound")
+	w := PerformRequest(route, http.MethodGet, "/NotFound")
 	assert.Equal(t, 421, w.Code)
 	assert.Equal(t, 0, w.Body.Len())
 }
@@ -608,7 +658,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
 	}
 
 	for _, route := range routes {
-		w := performRequest(router, http.MethodGet, route)
+		w := PerformRequest(router, http.MethodGet, route)
 		assert.Equal(t, http.StatusOK, w.Code)
 	}
 
@@ -618,6 +668,6 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
 		assert.Equal(t, "", c.FullPath())
 	})
 
-	w := performRequest(router, http.MethodGet, "/not-found")
+	w := PerformRequest(router, http.MethodGet, "/not-found")
 	assert.Equal(t, http.StatusNotFound, w.Code)
 }
diff --git a/testdata/protoexample/any.go b/testdata/protoexample/any.go
new file mode 100644
index 0000000..f16864c
--- /dev/null
+++ b/testdata/protoexample/any.go
@@ -0,0 +1,10 @@
+// Copyright 2021 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+//go:build !go1.18
+// +build !go1.18
+
+package protoexample
+
+type any = interface{}
diff --git a/testdata/protoexample/test.pb.go b/testdata/protoexample/test.pb.go
index 21997ca..6687aae 100644
--- a/testdata/protoexample/test.pb.go
+++ b/testdata/protoexample/test.pb.go
@@ -1,24 +1,24 @@
-// Code generated by protoc-gen-go.
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.0
+// 	protoc        v3.15.8
 // source: test.proto
-// DO NOT EDIT!
 
-/*
-Package protoexample is a generated protocol buffer package.
-
-It is generated from these files:
-	test.proto
-
-It has these top-level messages:
-	Test
-*/
 package protoexample
 
-import proto "github.com/golang/protobuf/proto"
-import math "math"
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
 
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = math.Inf
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
 
 type FOO int32
 
@@ -26,88 +26,273 @@ const (
 	FOO_X FOO = 17
 )
 
-var FOO_name = map[int32]string{
-	17: "X",
-}
-var FOO_value = map[string]int32{
-	"X": 17,
-}
+// Enum value maps for FOO.
+var (
+	FOO_name = map[int32]string{
+		17: "X",
+	}
+	FOO_value = map[string]int32{
+		"X": 17,
+	}
+)
 
 func (x FOO) Enum() *FOO {
 	p := new(FOO)
 	*p = x
 	return p
 }
+
 func (x FOO) String() string {
-	return proto.EnumName(FOO_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (FOO) Descriptor() protoreflect.EnumDescriptor {
+	return file_test_proto_enumTypes[0].Descriptor()
+}
+
+func (FOO) Type() protoreflect.EnumType {
+	return &file_test_proto_enumTypes[0]
+}
+
+func (x FOO) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
 }
-func (x *FOO) UnmarshalJSON(data []byte) error {
-	value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO")
+
+// Deprecated: Do not use.
+func (x *FOO) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
-	*x = FOO(value)
+	*x = FOO(num)
 	return nil
 }
 
+// Deprecated: Use FOO.Descriptor instead.
+func (FOO) EnumDescriptor() ([]byte, []int) {
+	return file_test_proto_rawDescGZIP(), []int{0}
+}
+
 type Test struct {
-	Label            *string             `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
-	Type             *int32              `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
-	Reps             []int64             `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
-	Optionalgroup    *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
-	XXX_unrecognized []byte              `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Label         *string             `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
+	Type          *int32              `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
+	Reps          []int64             `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
+	Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup,json=optionalgroup" json:"optionalgroup,omitempty"`
+}
+
+// Default values for Test fields.
+const (
+	Default_Test_Type = int32(77)
+)
+
+func (x *Test) Reset() {
+	*x = Test{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_test_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Test) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
 
-func (m *Test) Reset()         { *m = Test{} }
-func (m *Test) String() string { return proto.CompactTextString(m) }
-func (*Test) ProtoMessage()    {}
+func (*Test) ProtoMessage() {}
 
-const Default_Test_Type int32 = 77
+func (x *Test) ProtoReflect() protoreflect.Message {
+	mi := &file_test_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
 
-func (m *Test) GetLabel() string {
-	if m != nil && m.Label != nil {
-		return *m.Label
+// Deprecated: Use Test.ProtoReflect.Descriptor instead.
+func (*Test) Descriptor() ([]byte, []int) {
+	return file_test_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Test) GetLabel() string {
+	if x != nil && x.Label != nil {
+		return *x.Label
 	}
 	return ""
 }
 
-func (m *Test) GetType() int32 {
-	if m != nil && m.Type != nil {
-		return *m.Type
+func (x *Test) GetType() int32 {
+	if x != nil && x.Type != nil {
+		return *x.Type
 	}
 	return Default_Test_Type
 }
 
-func (m *Test) GetReps() []int64 {
-	if m != nil {
-		return m.Reps
+func (x *Test) GetReps() []int64 {
+	if x != nil {
+		return x.Reps
 	}
 	return nil
 }
 
-func (m *Test) GetOptionalgroup() *Test_OptionalGroup {
-	if m != nil {
-		return m.Optionalgroup
+func (x *Test) GetOptionalgroup() *Test_OptionalGroup {
+	if x != nil {
+		return x.Optionalgroup
 	}
 	return nil
 }
 
 type Test_OptionalGroup struct {
-	RequiredField    *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"`
-	XXX_unrecognized []byte  `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	RequiredField *string `protobuf:"bytes,5,req,name=RequiredField" json:"RequiredField,omitempty"`
+}
+
+func (x *Test_OptionalGroup) Reset() {
+	*x = Test_OptionalGroup{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_test_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Test_OptionalGroup) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Test_OptionalGroup) ProtoMessage() {}
+
+func (x *Test_OptionalGroup) ProtoReflect() protoreflect.Message {
+	mi := &file_test_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
 }
 
-func (m *Test_OptionalGroup) Reset()         { *m = Test_OptionalGroup{} }
-func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) }
-func (*Test_OptionalGroup) ProtoMessage()    {}
+// Deprecated: Use Test_OptionalGroup.ProtoReflect.Descriptor instead.
+func (*Test_OptionalGroup) Descriptor() ([]byte, []int) {
+	return file_test_proto_rawDescGZIP(), []int{0, 0}
+}
 
-func (m *Test_OptionalGroup) GetRequiredField() string {
-	if m != nil && m.RequiredField != nil {
-		return *m.RequiredField
+func (x *Test_OptionalGroup) GetRequiredField() string {
+	if x != nil && x.RequiredField != nil {
+		return *x.RequiredField
 	}
 	return ""
 }
 
-func init() {
-	proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value)
+var File_test_proto protoreflect.FileDescriptor
+
+var file_test_proto_rawDesc = []byte{
+	0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x22, 0xc7, 0x01, 0x0a, 0x04, 0x54,
+	0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x02,
+	0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x04, 0x74, 0x79, 0x70,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x3a, 0x02, 0x37, 0x37, 0x52, 0x04, 0x74, 0x79, 0x70,
+	0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52,
+	0x04, 0x72, 0x65, 0x70, 0x73, 0x12, 0x46, 0x0a, 0x0d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61,
+	0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0a, 0x32, 0x20, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74,
+	0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0d,
+	0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x35, 0x0a,
+	0x0d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x24,
+	0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18,
+	0x05, 0x20, 0x02, 0x28, 0x09, 0x52, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46,
+	0x69, 0x65, 0x6c, 0x64, 0x2a, 0x0c, 0x0a, 0x03, 0x46, 0x4f, 0x4f, 0x12, 0x05, 0x0a, 0x01, 0x58,
+	0x10, 0x11,
+}
+
+var (
+	file_test_proto_rawDescOnce sync.Once
+	file_test_proto_rawDescData = file_test_proto_rawDesc
+)
+
+func file_test_proto_rawDescGZIP() []byte {
+	file_test_proto_rawDescOnce.Do(func() {
+		file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
+	})
+	return file_test_proto_rawDescData
+}
+
+var file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_test_proto_goTypes = []any{
+	(FOO)(0),                   // 0: protoexample.FOO
+	(*Test)(nil),               // 1: protoexample.Test
+	(*Test_OptionalGroup)(nil), // 2: protoexample.Test.OptionalGroup
+}
+var file_test_proto_depIdxs = []int32{
+	2, // 0: protoexample.Test.optionalgroup:type_name -> protoexample.Test.OptionalGroup
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_test_proto_init() }
+func file_test_proto_init() {
+	if File_test_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_test_proto_msgTypes[0].Exporter = func(v any, i int) any {
+			switch v := v.(*Test); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_test_proto_msgTypes[1].Exporter = func(v any, i int) any {
+			switch v := v.(*Test_OptionalGroup); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_test_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_test_proto_goTypes,
+		DependencyIndexes: file_test_proto_depIdxs,
+		EnumInfos:         file_test_proto_enumTypes,
+		MessageInfos:      file_test_proto_msgTypes,
+	}.Build()
+	File_test_proto = out.File
+	file_test_proto_rawDesc = nil
+	file_test_proto_goTypes = nil
+	file_test_proto_depIdxs = nil
 }
diff --git a/testdata/template/raw.tmpl b/testdata/template/raw.tmpl
index 8bc7570..f3f530a 100644
--- a/testdata/template/raw.tmpl
+++ b/testdata/template/raw.tmpl
@@ -1,1 +1,1 @@
-Date: {[{.now | formatAsDate}]}
+Date: {[{.now | formatAsDate}]}
\ No newline at end of file
diff --git a/tree_test.go b/tree_test.go
index ea13c30..085b580 100644
--- a/tree_test.go
+++ b/tree_test.go
@@ -33,6 +33,11 @@ func getParams() *Params {
 	return &ps
 }
 
+func getSkippedNodes() *[]skippedNode {
+	ps := make([]skippedNode, 0, 20)
+	return &ps
+}
+
 func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) {
 	unescape := false
 	if len(unescapes) >= 1 {
@@ -40,7 +45,7 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ..
 	}
 
 	for _, request := range requests {
-		value := tree.getValue(request.path, getParams(), unescape)
+		value := tree.getValue(request.path, getParams(), getSkippedNodes(), unescape)
 
 		if value.handlers == nil {
 			if !request.nilHandler {
@@ -157,6 +162,8 @@ func TestTreeWildcard(t *testing.T) {
 		"/aa/*xx",
 		"/ab/*xx",
 		"/:cc",
+		"/c1/:dd/e",
+		"/c1/:dd/e1",
 		"/:cc/cc",
 		"/:cc/:dd/ee",
 		"/:cc/:dd/:ee/ff",
@@ -238,6 +245,9 @@ func TestTreeWildcard(t *testing.T) {
 		{"/alldd", false, "/:cc", Params{Param{Key: "cc", Value: "alldd"}}},
 		{"/all/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "all"}}},
 		{"/a/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "a"}}},
+		{"/c1/d/e", false, "/c1/:dd/e", Params{Param{Key: "dd", Value: "d"}}},
+		{"/c1/d/e1", false, "/c1/:dd/e1", Params{Param{Key: "dd", Value: "d"}}},
+		{"/c1/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c1"}, Param{Key: "dd", Value: "d"}}},
 		{"/cc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "cc"}}},
 		{"/ccc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "ccc"}}},
 		{"/deedwjfs/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "deedwjfs"}}},
@@ -347,7 +357,7 @@ func TestUnescapeParameters(t *testing.T) {
 	checkPriorities(t, tree)
 }
 
-func catchPanic(testFunc func()) (recv interface{}) {
+func catchPanic(testFunc func()) (recv any) {
 	defer func() {
 		recv = recover()
 	}()
@@ -437,7 +447,7 @@ func TestTreeChildConflict(t *testing.T) {
 	testRoutes(t, routes)
 }
 
-func TestTreeDupliatePath(t *testing.T) {
+func TestTreeDuplicatePath(t *testing.T) {
 	tree := &node{}
 
 	routes := [...]string{
@@ -577,7 +587,15 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
 		"/doc/go1.html",
 		"/no/a",
 		"/no/b",
-		"/api/hello/:name",
+		"/api/:page/:name",
+		"/api/hello/:name/bar/",
+		"/api/bar/:name",
+		"/api/baz/foo",
+		"/api/baz/foo/bar",
+		"/blog/:p",
+		"/posts/:b/:c",
+		"/posts/b/:c/d/",
+		"/vendor/:x/*y",
 	}
 	for _, route := range routes {
 		recv := catchPanic(func() {
@@ -603,9 +621,22 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
 		"/admin/config/",
 		"/admin/config/permissions/",
 		"/doc/",
+		"/admin/static/",
+		"/admin/cfg/",
+		"/admin/cfg/users/",
+		"/api/hello/x/bar",
+		"/api/baz/foo/",
+		"/api/baz/bax/",
+		"/api/bar/huh/",
+		"/api/baz/foo/bar/",
+		"/api/world/abc/",
+		"/blog/pp/",
+		"/posts/b/c/d",
+		"/vendor/x",
 	}
+
 	for _, route := range tsrRoutes {
-		value := tree.getValue(route, nil, false)
+		value := tree.getValue(route, nil, getSkippedNodes(), false)
 		if value.handlers != nil {
 			t.Fatalf("non-nil handler for TSR route '%s", route)
 		} else if !value.tsr {
@@ -619,10 +650,14 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
 		"/no/",
 		"/_",
 		"/_/",
-		"/api/world/abc",
+		"/api",
+		"/api/",
+		"/api/hello/x/foo",
+		"/api/baz/foo/bad",
+		"/foo/p/p",
 	}
 	for _, route := range noTsrRoutes {
-		value := tree.getValue(route, nil, false)
+		value := tree.getValue(route, nil, getSkippedNodes(), false)
 		if value.handlers != nil {
 			t.Fatalf("non-nil handler for No-TSR route '%s", route)
 		} else if value.tsr {
@@ -641,7 +676,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
 		t.Fatalf("panic inserting test route: %v", recv)
 	}
 
-	value := tree.getValue("/", nil, false)
+	value := tree.getValue("/", nil, getSkippedNodes(), false)
 	if value.handlers != nil {
 		t.Fatalf("non-nil handler")
 	} else if value.tsr {
@@ -821,7 +856,7 @@ func TestTreeInvalidNodeType(t *testing.T) {
 
 	// normal lookup
 	recv := catchPanic(func() {
-		tree.getValue("/test", nil, false)
+		tree.getValue("/test", nil, getSkippedNodes(), false)
 	})
 	if rs, ok := recv.(string); !ok || rs != panicMsg {
 		t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
@@ -843,10 +878,10 @@ func TestTreeInvalidParamsType(t *testing.T) {
 	tree.children[0].nType = 2
 
 	// set invalid Params type
-	params := make(Params, 0, 0)
+	params := make(Params, 0)
 
 	// try to trigger slice bounds out of range with capacity 0
-	tree.getValue("/test", &params, false)
+	tree.getValue("/test", &params, getSkippedNodes(), false)
 }
 
 func TestTreeWildcardConflictEx(t *testing.T) {
diff --git a/utils_test.go b/utils_test.go
index cc486c3..d2a740b 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -45,11 +45,11 @@ func TestWrap(t *testing.T) {
 		fmt.Fprint(w, "hola!")
 	}))
 
-	w := performRequest(router, "POST", "/path")
+	w := PerformRequest(router, "POST", "/path")
 	assert.Equal(t, http.StatusInternalServerError, w.Code)
 	assert.Equal(t, "hello", w.Body.String())
 
-	w = performRequest(router, "GET", "/path2")
+	w = PerformRequest(router, "GET", "/path2")
 	assert.Equal(t, http.StatusBadRequest, w.Code)
 	assert.Equal(t, "hola!", w.Body.String())
 }
@@ -119,13 +119,13 @@ func TestBindMiddleware(t *testing.T) {
 		called = true
 		value = c.MustGet(BindKey).(*bindTestStruct)
 	})
-	performRequest(router, "GET", "/?foo=hola&bar=10")
+	PerformRequest(router, "GET", "/?foo=hola&bar=10")
 	assert.True(t, called)
 	assert.Equal(t, "hola", value.Foo)
 	assert.Equal(t, 10, value.Bar)
 
 	called = false
-	performRequest(router, "GET", "/?foo=hola&bar=1")
+	PerformRequest(router, "GET", "/?foo=hola&bar=1")
 	assert.False(t, called)
 
 	assert.Panics(t, func() {
@@ -143,3 +143,8 @@ func TestMarshalXMLforH(t *testing.T) {
 	e := h.MarshalXML(enc, x)
 	assert.Error(t, e)
 }
+
+func TestIsASCII(t *testing.T) {
+	assert.Equal(t, isASCII("test"), true)
+	assert.Equal(t, isASCII("🧡💛💚💙💜"), false)
+}