Repository: gin-gonic/gin
Pull request #3455 (2022-12-31 10:25:16)

refactor: use bytes.ReplaceAll directly
Use `bytes.ReplaceAll(name, centerDot, dot)` instead of `bytes.Replace(name, centerDot, dot, -1)`, which looks more concise


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

```diff
@@            Coverage Diff             @@
##           master    #3455      +/-   ##
==========================================
- Coverage   98.63%   98.53%   -0.10%     
==========================================
  Files          42       42              
  Lines        3139     3139              
==========================================
- Hits         3096     3093       -3     
- Misses         29       31       +2     
- Partials       14       15       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
|  | `98.53% <100.00%> (-0.10%)` | :arrow_down: |
| go-1.16 | `?` | |
| go-1.17 | `98.53% <100.00%> (ø)` | |
| go-1.18 | `?` | |
| go-1.19 | `?` | |
| macos-latest | `98.53% <100.00%> (-0.10%)` | :arrow_down: |
| ubuntu-latest | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/gin-gonic/gin/pull/3455?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | Coverage Δ | |
|---|---|---|
| [recovery.go](https://codecov.io/gh/gin-gonic/gin/pull/3455/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-cmVjb3ZlcnkuZ28=) | `98.11% <100.00%> (ø)` | |
| [context.go](https://codecov.io/gh/gin-gonic/gin/pull/3455/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-Y29udGV4dC5nbw==) | `97.45% <0.00%> (-0.51%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic)

appleboy: build fail

Patch:
diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml
index 7a4e61c..5c1504a 100644
--- a/.github/workflows/gin.yml
+++ b/.github/workflows/gin.yml
@@ -18,11 +18,11 @@ jobs:
       - name: Setup go
         uses: actions/setup-go@v3
         with:
-          go-version: '^1.16'
+          go-version: '^1.18'
       - name: Checkout repository
         uses: actions/checkout@v3
       - name: Setup golangci-lint
-        uses: golangci/golangci-lint-action@v3.3.1
+        uses: golangci/golangci-lint-action@v3.4.0
         with:
           version: v1.48.0
           args: --verbose
@@ -31,7 +31,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest]
-        go: [1.16, 1.17, 1.18, 1.19]
+        go: ['1.18', '1.19', '1.20']
         test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json']
         include:
           - os: ubuntu-latest
@@ -73,7 +73,7 @@ jobs:
           flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
 
       - name: Format
-        if: matrix.go-version == '1.19.x'
+        if: matrix.go-version == '1.20.x'
         run: diff -u <(echo -n) <(gofmt -d .)
   notification-gitter:
     needs: test
diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
index 3af3a45..baf02af 100644
--- a/.github/workflows/goreleaser.yml
+++ b/.github/workflows/goreleaser.yml
@@ -21,7 +21,7 @@ jobs:
         name: Set up Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.17
+          go-version: 1.20
       -
         name: Run GoReleaser
         uses: goreleaser/goreleaser-action@v4
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2ab5617..cf24ec2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,13 +1,49 @@
 # Gin ChangeLog
 
+## Gin v1.9.0
+
+### BREAK CHANGES
+
+* Stop useless panicking in context and render [#2150](https://github.com/gin-gonic/gin/pull/2150)
+
+### BUG FIXES
+
+* fix(router): tree bug where loop index is not decremented. [#3460](https://github.com/gin-gonic/gin/pull/3460)
+* fix(context): panic on NegotiateFormat - index out of range [#3397](https://github.com/gin-gonic/gin/pull/3397)
+* Add escape logic for header [#3500](https://github.com/gin-gonic/gin/pull/3500) and [#3503](https://github.com/gin-gonic/gin/pull/3503)
+
+### SECURITY
+
+* Fix the GO-2022-0969 and GO-2022-0288 vulnerabilities [#3333](https://github.com/gin-gonic/gin/pull/3333)
+* fix(security): vulnerability GO-2023-1571 [#3505](https://github.com/gin-gonic/gin/pull/3505)
+
+### ENHANCEMENTS
+
+* feat: add sonic json support [#3184](https://github.com/gin-gonic/gin/pull/3184)
+* chore(file): Creates a directory named path [#3316](https://github.com/gin-gonic/gin/pull/3316)
+* fix: modify interface check way [#3327](https://github.com/gin-gonic/gin/pull/3327)
+* remove deprecated of package io/ioutil [#3395](https://github.com/gin-gonic/gin/pull/3395)
+* refactor: avoid calling strings.ToLower twice [#3343](https://github.com/gin-gonic/gin/pull/3433)
+* console logger HTTP status code bug fixed [#3453](https://github.com/gin-gonic/gin/pull/3453)
+* chore(yaml): upgrade dependency to v3 version [#3456](https://github.com/gin-gonic/gin/pull/3456)
+* chore(router): match method added to routergroup for multiple HTTP methods supporting [#3464](https://github.com/gin-gonic/gin/pull/3464)
+* chore(http): add support for go1.20 http.rwUnwrapper to gin.responseWriter [#3489](https://github.com/gin-gonic/gin/pull/3489)
+
+### DOCS
+
+* docs: update markdown format [#3260](https://github.com/gin-gonic/gin/pull/3260)
+* docs(readme): Add the TOML rendering example [#3400](https://github.com/gin-gonic/gin/pull/3400)
+* docs(readme): move more example to docs/doc.md [#3449](https://github.com/gin-gonic/gin/pull/3449)
+* docs: update markdown format [#3446](https://github.com/gin-gonic/gin/pull/3446)
+
 ## Gin v1.8.2
 
-### Bugs
+### BUG FIXES
 
 * fix(route): redirectSlash bug ([#3227]((https://github.com/gin-gonic/gin/pull/3227)))
 * fix(engine): missing route params for CreateTestContext ([#2778]((https://github.com/gin-gonic/gin/pull/2778))) ([#2803]((https://github.com/gin-gonic/gin/pull/2803)))
 
-### Security
+### SECURITY
 
 * Fix the GO-2022-1144 vulnerability ([#3432]((https://github.com/gin-gonic/gin/pull/3432)))
 
@@ -19,12 +55,12 @@
 
 ## Gin v1.8.0
 
-## Break Changes
+### BREAK CHANGES
 
 * TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967). Please replace `RemoteIP() (net.IP, bool)` with `RemoteIP() net.IP`
 * gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751)
 
-### BUGFIXES
+### BUG FIXES
 
 * Fixed SetOutput() panics on go 1.17 [#2861](https://github.com/gin-gonic/gin/pull/2861)
 * Fix: wrong when wildcard follows named param [#2983](https://github.com/gin-gonic/gin/pull/2983)
@@ -61,7 +97,7 @@
 
 ## Gin v1.7.7
 
-### BUGFIXES
+### BUG FIXES
 
 * 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).
@@ -79,37 +115,37 @@
 
 ## Gin v1.7.6
 
-### BUGFIXES
+### BUG FIXES
 
 * bump new release to fix v1.7.5 release error by using v1.7.4 codes.
 
 ## Gin v1.7.4
 
-### BUGFIXES
+### BUG FIXES
 
 * bump new release to fix checksum mismatch
 
 ## Gin v1.7.3
 
-### BUGFIXES
+### BUG FIXES
 
 * 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
+### BUG FIXES
 
 * Fix conflict between param and exact path [#2706](https://github.com/gin-gonic/gin/issues/2706). Close issue [#2682](https://github.com/gin-gonic/gin/issues/2682) [#2696](https://github.com/gin-gonic/gin/issues/2696).
 
 ## Gin v1.7.1
 
-### BUGFIXES
+### BUG FIXES
 
 * fix: data race with trustedCIDRs from [#2674](https://github.com/gin-gonic/gin/issues/2674)([#2675](https://github.com/gin-gonic/gin/pull/2675))
 
 ## Gin v1.7.0
 
-### BUGFIXES
+### BUG FIXES
 
 * fix compile error from [#2572](https://github.com/gin-gonic/gin/pull/2572) ([#2600](https://github.com/gin-gonic/gin/pull/2600))
 * fix: print headers without Authorization header on broken pipe ([#2528](https://github.com/gin-gonic/gin/pull/2528))
@@ -148,33 +184,44 @@
 
 ## Gin v1.6.2
 
-### BUGFIXES
+### BUG FIXES
+
   * fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305)
+
 ### ENHANCEMENTS
+
   * Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306)
 
 ## Gin v1.6.1
 
-### BUGFIXES
+### BUG FIXES
+
   * Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294)
 
 ## Gin v1.6.0
 
 ### BREAKING
+
   * chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159)
   * drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148)
   * Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615)
+
 ### FEATURES
+
   * add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220)
   * FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112)
-### BUGFIXES
+
+### BUG FIXES
+
   * Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280)
   * Use json marshall in context json to fix breaking new line issue. Fixes #2209 [#2228](https://github.com/gin-gonic/gin/pull/2228)
   * fix accept incoming network connections [#2216](https://github.com/gin-gonic/gin/pull/2216)
   * Fixed a bug in the calculation of the maximum number of parameters [#2166](https://github.com/gin-gonic/gin/pull/2166)
   * [FIX] allow empty headers on DataFromReader [#2121](https://github.com/gin-gonic/gin/pull/2121)
   * Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391)
+
 ### ENHANCEMENTS
+
   * Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277)
   * tree: range over nodes values [#2229](https://github.com/gin-gonic/gin/pull/2229)
   * tree: remove duplicate assignment [#2222](https://github.com/gin-gonic/gin/pull/2222)
@@ -189,7 +236,9 @@
   * upgrade go-validator to v10 [#2149](https://github.com/gin-gonic/gin/pull/2149)
   * Refactor redirect request in gin.go [#1970](https://github.com/gin-gonic/gin/pull/1970)
   * Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852)
+
 ### DOCS
+
   * docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223)
   * Renew README to fit the modification of SetCookie method [#2217](https://github.com/gin-gonic/gin/pull/2217)
   * Fix spelling [#2202](https://github.com/gin-gonic/gin/pull/2202)
@@ -202,7 +251,9 @@
   * Add project to README [#2165](https://github.com/gin-gonic/gin/pull/2165)
   * docs(benchmarks): for gin v1.5 [#2153](https://github.com/gin-gonic/gin/pull/2153)
   * Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122)
+
 ### MISC
+
   * ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262)
   * chore: upgrade depend version [#2231](https://github.com/gin-gonic/gin/pull/2231)
   * Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147)
diff --git a/README.md b/README.md
index 336155a..cba54ab 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ Gin is a web framework written in [Go](https://go.dev/). It features a martini-l
 
 ### Prerequisites
 
-- **[Go](https://go.dev/)**: ~~any one of the **three latest major** [releases](https://go.dev/doc/devel/release)~~ (now version **1.16+** is required).
+- **[Go](https://go.dev/)**: any one of the **three latest major** [releases](https://go.dev/doc/devel/release) (we test it with these).
 
 ### Getting Gin
 
diff --git a/any.go b/any.go
deleted file mode 100644
index 42b1ea4..0000000
--- a/any.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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/binding/any.go b/binding/any.go
deleted file mode 100644
index d8251a7..0000000
--- a/binding/any.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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 a58924e..4094852 100644
--- a/binding/binding.go
+++ b/binding/binding.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build !nomsgpack
-// +build !nomsgpack
 
 package binding
 
diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go
index 7f6a904..93ad8ba 100644
--- a/binding/binding_nomsgpack.go
+++ b/binding/binding_nomsgpack.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build nomsgpack
-// +build nomsgpack
 
 package binding
 
diff --git a/binding/json.go b/binding/json.go
index 36eb27a..e21c2ee 100644
--- a/binding/json.go
+++ b/binding/json.go
@@ -15,7 +15,7 @@ import (
 
 // EnableDecoderUseNumber is used to call the UseNumber method on the JSON
 // Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
-// interface{} as a Number instead of as a float64.
+// any as a Number instead of as a float64.
 var EnableDecoderUseNumber = false
 
 // EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method
diff --git a/binding/msgpack.go b/binding/msgpack.go
index d1f035e..22de9b5 100644
--- a/binding/msgpack.go
+++ b/binding/msgpack.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build !nomsgpack
-// +build !nomsgpack
 
 package binding
 
diff --git a/context.go b/context.go
index 0474252..5716318 100644
--- a/context.go
+++ b/context.go
@@ -652,7 +652,7 @@ func (c *Context) BindYAML(obj any) error {
 }
 
 // BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
-func (c *Context) BindTOML(obj interface{}) error {
+func (c *Context) BindTOML(obj any) error {
 	return c.MustBindWith(obj, binding.TOML)
 }
 
@@ -717,7 +717,7 @@ func (c *Context) ShouldBindYAML(obj any) error {
 }
 
 // ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
-func (c *Context) ShouldBindTOML(obj interface{}) error {
+func (c *Context) ShouldBindTOML(obj any) error {
 	return c.ShouldBindWith(obj, binding.TOML)
 }
 
@@ -924,7 +924,9 @@ func (c *Context) Render(code int, r render.Render) {
 	}
 
 	if err := r.Render(c.Writer); err != nil {
-		panic(err)
+		// Pushing error to c.Errors
+		_ = c.Error(err)
+		c.Abort()
 	}
 }
 
@@ -993,7 +995,7 @@ func (c *Context) YAML(code int, obj any) {
 }
 
 // TOML serializes the given struct as TOML into the response body.
-func (c *Context) TOML(code int, obj interface{}) {
+func (c *Context) TOML(code int, obj any) {
 	c.Render(code, render.TOML{Data: obj})
 }
 
diff --git a/context_appengine.go b/context_appengine.go
index 931313f..96b339c 100644
--- a/context_appengine.go
+++ b/context_appengine.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build appengine
-// +build appengine
 
 package gin
 
diff --git a/debug.go b/debug.go
index cbcedbc..1fc0caf 100644
--- a/debug.go
+++ b/debug.go
@@ -12,7 +12,7 @@ import (
 	"strings"
 )
 
-const ginSupportMinGoVer = 16
+const ginSupportMinGoVer = 18
 
 // IsDebugging returns true if the framework is running in debug mode.
 // Use SetMode(gin.ReleaseMode) to disable debug mode.
@@ -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.16+.
+		debugPrint(`[WARNING] Now Gin requires Go 1.18+.
 
 `)
 	}
diff --git a/deprecated.go b/deprecated.go
index fdad855..9521308 100644
--- a/deprecated.go
+++ b/deprecated.go
@@ -13,7 +13,7 @@ import (
 // BindWith binds the passed struct pointer using the specified binding engine.
 // See the binding package.
 func (c *Context) BindWith(obj any, b binding.Binding) error {
-	log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
+	log.Println(`BindWith(\"any, 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
 	ShouldBindWith() if you need to manage the error.`)
diff --git a/docs/doc.md b/docs/doc.md
index 7cebab5..e48c2ba 100644
--- a/docs/doc.md
+++ b/docs/doc.md
@@ -425,7 +425,7 @@ func main() {
   r.Use(gin.Logger())
 
   // Recovery middleware recovers from any panics and writes a 500 if there was one.
-  r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
+  r.Use(gin.CustomRecovery(func(c *gin.Context, recovered any) {
     if err, ok := recovered.(string); ok {
       c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
     }
@@ -996,7 +996,7 @@ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:
 func main() {
   r := gin.Default()
 
-  // gin.H is a shortcut for map[string]interface{}
+  // gin.H is a shortcut for map[string]any
   r.GET("/someJSON", func(c *gin.Context) {
     c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
   })
@@ -1961,7 +1961,7 @@ func (customerBinding) Name() string {
   return "form"
 }
 
-func (customerBinding) Bind(req *http.Request, obj interface{}) error {
+func (customerBinding) Bind(req *http.Request, obj any) error {
   if err := req.ParseForm(); err != nil {
     return err
   }
@@ -1976,7 +1976,7 @@ func (customerBinding) Bind(req *http.Request, obj interface{}) error {
   return validate(obj)
 }
 
-func validate(obj interface{}) error {
+func validate(obj any) error {
   if binding.Validator == nil {
     return nil
   }
diff --git a/gin.go b/gin.go
index 35159d0..f95e5dd 100644
--- a/gin.go
+++ b/gin.go
@@ -11,6 +11,7 @@ import (
 	"net/http"
 	"os"
 	"path"
+	"regexp"
 	"strings"
 	"sync"
 
@@ -40,6 +41,9 @@ var defaultTrustedCIDRs = []*net.IPNet{
 	},
 }
 
+var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
+var regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
+
 // HandlerFunc defines the handler used by gin middleware as return value.
 type HandlerFunc func(*Context)
 
@@ -668,6 +672,9 @@ func redirectTrailingSlash(c *Context) {
 	req := c.Request
 	p := req.URL.Path
 	if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
+		prefix = regSafePrefix.ReplaceAllString(prefix, "")
+		prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/")
+
 		p = prefix + "/" + req.URL.Path
 	}
 	req.URL.Path = p + "/"
diff --git a/go.mod b/go.mod
index e03001e..484e388 100644
--- a/go.mod
+++ b/go.mod
@@ -3,16 +3,16 @@ module github.com/gin-gonic/gin
 go 1.18
 
 require (
-	github.com/bytedance/sonic v1.7.0
+	github.com/bytedance/sonic v1.8.2
 	github.com/gin-contrib/sse v0.1.0
-	github.com/go-playground/validator/v10 v10.11.1
+	github.com/go-playground/validator/v10 v10.11.2
 	github.com/goccy/go-json v0.10.0
 	github.com/json-iterator/go v1.1.12
 	github.com/mattn/go-isatty v0.0.17
 	github.com/pelletier/go-toml/v2 v2.0.6
-	github.com/stretchr/testify v1.8.1
-	github.com/ugorji/go/codec v1.2.8
-	golang.org/x/net v0.5.0
+	github.com/stretchr/testify v1.8.2
+	github.com/ugorji/go/codec v1.2.10
+	golang.org/x/net v0.7.0
 	google.golang.org/protobuf v1.28.1
 	gopkg.in/yaml.v3 v3.0.1
 )
@@ -20,16 +20,17 @@ require (
 require (
 	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
 	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/klauspost/cpuid/v2 v2.0.14 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+	github.com/kr/text v0.2.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
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
-	golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect
-	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
-	golang.org/x/sys v0.4.0 // indirect
-	golang.org/x/text v0.6.0 // indirect
+	golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
+	golang.org/x/crypto v0.5.0 // indirect
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 4e4e963..6193e00 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,6 @@
 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
-github.com/bytedance/sonic v1.7.0 h1:P7DyGrkLbVDzcuqagPsSFnAwwljjhmB3qVF5wzmHOxE=
-github.com/bytedance/sonic v1.7.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/bytedance/sonic v1.8.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY=
+github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
@@ -10,14 +10,13 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/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.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.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
-github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
+github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
 github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
 github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@@ -26,15 +25,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 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/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8=
-github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
-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=
@@ -47,12 +40,9 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
 github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
-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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -61,45 +51,32 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
-github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0=
-github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ=
+github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
-golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU=
-golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
-golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
+golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
-golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 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.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/internal/json/go_json.go b/internal/json/go_json.go
index 23f7172..47c3559 100644
--- a/internal/json/go_json.go
+++ b/internal/json/go_json.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build go_json
-// +build go_json
 
 package json
 
diff --git a/internal/json/json.go b/internal/json/json.go
index c5f3efc..c7ee83e 100644
--- a/internal/json/json.go
+++ b/internal/json/json.go
@@ -3,9 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64)
-// +build !jsoniter
-// +build !go_json
-// +build !sonic !avx !linux,!windows,!darwin !amd64
 
 package json
 
diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go
index 853b1a9..45ed16b 100644
--- a/internal/json/jsoniter.go
+++ b/internal/json/jsoniter.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build jsoniter
-// +build jsoniter
 
 package json
 
diff --git a/internal/json/sonic.go b/internal/json/sonic.go
index 5a9ca4b..529e16d 100644
--- a/internal/json/sonic.go
+++ b/internal/json/sonic.go
@@ -3,10 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build sonic && avx && (linux || windows || darwin) && amd64
-// +build sonic
-// +build avx
-// +build linux windows darwin
-// +build amd64
 
 package json
 
diff --git a/recovery.go b/recovery.go
index 2955c03..037be51 100644
--- a/recovery.go
+++ b/recovery.go
@@ -164,7 +164,7 @@ func function(pc uintptr) []byte {
 	if period := bytes.Index(name, dot); period >= 0 {
 		name = name[period+1:]
 	}
-	name = bytes.Replace(name, centerDot, dot, -1)
+	name = bytes.ReplaceAll(name, centerDot, dot)
 	return name
 }
 
diff --git a/render/any.go b/render/any.go
deleted file mode 100644
index b19ad45..0000000
--- a/render/any.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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/json.go b/render/json.go
index af678e8..fc8dea4 100644
--- a/render/json.go
+++ b/render/json.go
@@ -53,11 +53,8 @@ var (
 )
 
 // Render (JSON) writes data with custom ContentType.
-func (r JSON) Render(w http.ResponseWriter) (err error) {
-	if err = WriteJSON(w, r.Data); err != nil {
-		panic(err)
-	}
-	return
+func (r JSON) Render(w http.ResponseWriter) error {
+	return WriteJSON(w, r.Data)
 }
 
 // WriteContentType (JSON) writes JSON ContentType.
diff --git a/render/msgpack.go b/render/msgpack.go
index e0f30f7..d1d8e84 100644
--- a/render/msgpack.go
+++ b/render/msgpack.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build !nomsgpack
-// +build !nomsgpack
 
 package render
 
diff --git a/response_writer.go b/response_writer.go
index 5cec1f6..753a0b0 100644
--- a/response_writer.go
+++ b/response_writer.go
@@ -51,6 +51,10 @@ type responseWriter struct {
 
 var _ ResponseWriter = (*responseWriter)(nil)
 
+func (w *responseWriter) Unwrap() http.ResponseWriter {
+	return w.ResponseWriter
+}
+
 func (w *responseWriter) reset(writer http.ResponseWriter) {
 	w.ResponseWriter = writer
 	w.size = noWritten
diff --git a/routergroup.go b/routergroup.go
index dfbdd7b..c833fe8 100644
--- a/routergroup.go
+++ b/routergroup.go
@@ -42,6 +42,7 @@ type IRoutes interface {
 	PUT(string, ...HandlerFunc) IRoutes
 	OPTIONS(string, ...HandlerFunc) IRoutes
 	HEAD(string, ...HandlerFunc) IRoutes
+	Match([]string, string, ...HandlerFunc) IRoutes
 
 	StaticFile(string, string) IRoutes
 	StaticFileFS(string, string, http.FileSystem) IRoutes
@@ -151,6 +152,15 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRou
 	return group.returnObj()
 }
 
+// Match registers a route that matches the specified methods that you declared.
+func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes {
+	for _, method := range methods {
+		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 {
diff --git a/utils.go b/utils.go
index 4021a2a..47106a7 100644
--- a/utils.go
+++ b/utils.go
@@ -50,7 +50,7 @@ func WrapH(h http.Handler) HandlerFunc {
 	}
 }
 
-// H is a shortcut for map[string]interface{}
+// H is a shortcut for map[string]any
 type H map[string]any
 
 // MarshalXML allows type H to be used with xml.Marshal.
diff --git a/version.go b/version.go
index 37e27f2..390da4f 100644
--- a/version.go
+++ b/version.go
@@ -5,4 +5,4 @@
 package gin
 
 // Version is the current gin framework's version.
-const Version = "v1.8.2"
+const Version = "v1.9.0"


Test patch:
diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go
index 04d9407..a6cd6aa 100644
--- a/binding/binding_msgpack_test.go
+++ b/binding/binding_msgpack_test.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build !nomsgpack
-// +build !nomsgpack
 
 package binding
 
diff --git a/binding/msgpack_test.go b/binding/msgpack_test.go
index 11561c8..df386a6 100644
--- a/binding/msgpack_test.go
+++ b/binding/msgpack_test.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build !nomsgpack
-// +build !nomsgpack
 
 package binding
 
diff --git a/context_1.17_test.go b/context_1.17_test.go
deleted file mode 100644
index 0f8527f..0000000
--- a/context_1.17_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// 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"
-	"runtime"
-	"strings"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-type interceptedWriter struct {
-	ResponseWriter
-	b *bytes.Buffer
-}
-
-func (i interceptedWriter) WriteHeader(code int) {
-	i.Header().Del("X-Test")
-	i.ResponseWriter.WriteHeader(code)
-}
-
-func TestContextFormFileFailed17(t *testing.T) {
-	if !isGo117OrGo118() {
-		return
-	}
-	buf := new(bytes.Buffer)
-	mw := multipart.NewWriter(buf)
-	defer func(mw *multipart.Writer) {
-		err := mw.Close()
-		if err != nil {
-			assert.Error(t, err)
-		}
-	}(mw)
-	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)
-	})
-}
-
-func TestInterceptedHeader(t *testing.T) {
-	w := httptest.NewRecorder()
-	c, r := CreateTestContext(w)
-
-	r.Use(func(c *Context) {
-		i := interceptedWriter{
-			ResponseWriter: c.Writer,
-			b:              bytes.NewBuffer(nil),
-		}
-		c.Writer = i
-		c.Next()
-		c.Header("X-Test", "overridden")
-		c.Writer = i.ResponseWriter
-	})
-	r.GET("/", func(c *Context) {
-		c.Header("X-Test", "original")
-		c.Header("X-Test-2", "present")
-		c.String(http.StatusOK, "hello world")
-	})
-	c.Request = httptest.NewRequest("GET", "/", nil)
-	r.HandleContext(c)
-	// Result() has headers frozen when WriteHeaderNow() has been called
-	// Compared to this time, this is when the response headers will be flushed
-	// As response is flushed on c.String, the Header cannot be set by the first
-	// middleware. Assert this
-	assert.Equal(t, "", w.Result().Header.Get("X-Test"))
-	assert.Equal(t, "present", w.Result().Header.Get("X-Test-2"))
-}
-
-func isGo117OrGo118() bool {
-	version := strings.Split(runtime.Version()[2:], ".")
-	if len(version) >= 2 {
-		x := version[0]
-		y := version[1]
-		if x == "1" && (y == "17" || y == "18") {
-			return true
-		}
-	}
-	return false
-}
diff --git a/context_1.16_test.go b/context_1.18_test.go
similarity index 66%
rename from context_1.16_test.go
rename to context_1.18_test.go
index 2676050..6118bea 100644
--- a/context_1.16_test.go
+++ b/context_1.18_test.go
@@ -2,8 +2,7 @@
 // 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
+//go:build !go1.19
 
 package gin
 
@@ -17,15 +16,22 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestContextFormFileFailed16(t *testing.T) {
+func TestContextFormFileFailed18(t *testing.T) {
 	buf := new(bytes.Buffer)
 	mw := multipart.NewWriter(buf)
-	mw.Close()
+	defer func(mw *multipart.Writer) {
+		err := mw.Close()
+		if err != nil {
+			assert.Error(t, err)
+		}
+	}(mw)
 	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)
+	assert.Panics(t, func() {
+		f, err := c.FormFile("file")
+		assert.Error(t, err)
+		assert.Nil(t, f)
+	})
 }
diff --git a/context_1.19_test.go b/context_1.19_test.go
index 4b34ea2..dd75325 100644
--- a/context_1.19_test.go
+++ b/context_1.19_test.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build go1.19
-// +build go1.19
 
 package gin
 
diff --git a/context_test.go b/context_test.go
index 827ee0f..1dec902 100644
--- a/context_test.go
+++ b/context_test.go
@@ -32,10 +32,12 @@ import (
 
 var _ context.Context = (*Context)(nil)
 
+var errTestRender = errors.New("TestRender")
+
 // Unit tests TODO
 // func (c *Context) File(filepath string) {
 // func (c *Context) Negotiate(code int, config Negotiate) {
-// BAD case: func (c *Context) Render(code int, render render.Render, obj ...interface{}) {
+// BAD case: func (c *Context) Render(code int, render render.Render, obj ...any) {
 // test that information is not leaked when reusing Contexts (using the Pool)
 
 func createMultipartRequest() *http.Request {
@@ -643,25 +645,21 @@ func TestContextBodyAllowedForStatus(t *testing.T) {
 	assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
 }
 
-type TestPanicRender struct{}
+type TestRender struct{}
 
-func (*TestPanicRender) Render(http.ResponseWriter) error {
-	return errors.New("TestPanicRender")
+func (*TestRender) Render(http.ResponseWriter) error {
+	return errTestRender
 }
 
-func (*TestPanicRender) WriteContentType(http.ResponseWriter) {}
+func (*TestRender) WriteContentType(http.ResponseWriter) {}
 
-func TestContextRenderPanicIfErr(t *testing.T) {
-	defer func() {
-		r := recover()
-		assert.Equal(t, "TestPanicRender", fmt.Sprint(r))
-	}()
+func TestContextRenderIfErr(t *testing.T) {
 	w := httptest.NewRecorder()
 	c, _ := CreateTestContext(w)
 
-	c.Render(http.StatusOK, &TestPanicRender{})
+	c.Render(http.StatusOK, &TestRender{})
 
-	assert.Fail(t, "Panic not detected")
+	assert.Equal(t, errorMsgs{&Error{Err: errTestRender, Type: 1}}, c.Errors)
 }
 
 // Tests that the response is serialized as JSON
@@ -2376,3 +2374,42 @@ func TestCreateTestContextWithRouteParams(t *testing.T) {
 	assert.Equal(t, http.StatusOK, w.Code)
 	assert.Equal(t, "hello gin", w.Body.String())
 }
+
+type interceptedWriter struct {
+	ResponseWriter
+	b *bytes.Buffer
+}
+
+func (i interceptedWriter) WriteHeader(code int) {
+	i.Header().Del("X-Test")
+	i.ResponseWriter.WriteHeader(code)
+}
+
+func TestInterceptedHeader(t *testing.T) {
+	w := httptest.NewRecorder()
+	c, r := CreateTestContext(w)
+
+	r.Use(func(c *Context) {
+		i := interceptedWriter{
+			ResponseWriter: c.Writer,
+			b:              bytes.NewBuffer(nil),
+		}
+		c.Writer = i
+		c.Next()
+		c.Header("X-Test", "overridden")
+		c.Writer = i.ResponseWriter
+	})
+	r.GET("/", func(c *Context) {
+		c.Header("X-Test", "original")
+		c.Header("X-Test-2", "present")
+		c.String(http.StatusOK, "hello world")
+	})
+	c.Request = httptest.NewRequest("GET", "/", nil)
+	r.HandleContext(c)
+	// Result() has headers frozen when WriteHeaderNow() has been called
+	// Compared to this time, this is when the response headers will be flushed
+	// As response is flushed on c.String, the Header cannot be set by the first
+	// middleware. Assert this
+	assert.Equal(t, "", w.Result().Header.Get("X-Test"))
+	assert.Equal(t, "present", w.Result().Header.Get("X-Test-2"))
+}
diff --git a/debug_test.go b/debug_test.go
index ce8b19d..2d5e9a5 100644
--- a/debug_test.go
+++ b/debug_test.go
@@ -21,7 +21,7 @@ import (
 
 // TODO
 // func debugRoute(httpMethod, absolutePath string, handlers HandlersChain) {
-// func debugPrint(format string, values ...interface{}) {
+// func debugPrint(format string, values ...any) {
 
 func TestIsDebugging(t *testing.T) {
 	SetMode(DebugMode)
@@ -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.16+.\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.18+.\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/render/render_msgpack_test.go b/render/render_msgpack_test.go
index 6421236..db4b71e 100644
--- a/render/render_msgpack_test.go
+++ b/render/render_msgpack_test.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build !nomsgpack
-// +build !nomsgpack
 
 package render
 
diff --git a/render/render_test.go b/render/render_test.go
index ebb7d41..1925525 100644
--- a/render/render_test.go
+++ b/render/render_test.go
@@ -40,12 +40,12 @@ func TestRenderJSON(t *testing.T) {
 	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
 }
 
-func TestRenderJSONPanics(t *testing.T) {
+func TestRenderJSONError(t *testing.T) {
 	w := httptest.NewRecorder()
 	data := make(chan int)
 
 	// json: unsupported type: chan int
-	assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) })
+	assert.Error(t, (JSON{data}).Render(w))
 }
 
 func TestRenderIndentedJSON(t *testing.T) {
diff --git a/response_writer_test.go b/response_writer_test.go
index 6fa5ec7..9fd5e87 100644
--- a/response_writer_test.go
+++ b/response_writer_test.go
@@ -30,6 +30,12 @@ func init() {
 	SetMode(TestMode)
 }
 
+func TestResponseWriterUnwrap(t *testing.T) {
+	testWriter := httptest.NewRecorder()
+	writer := &responseWriter{ResponseWriter: testWriter}
+	assert.Same(t, testWriter, writer.Unwrap())
+}
+
 func TestResponseWriterReset(t *testing.T) {
 	testWriter := httptest.NewRecorder()
 	writer := &responseWriter{}
diff --git a/routergroup_test.go b/routergroup_test.go
index 41f9637..6848063 100644
--- a/routergroup_test.go
+++ b/routergroup_test.go
@@ -186,6 +186,7 @@ func testRoutesInterface(t *testing.T, r IRoutes) {
 	assert.Equal(t, r, r.PUT("/", handler))
 	assert.Equal(t, r, r.OPTIONS("/", handler))
 	assert.Equal(t, r, r.HEAD("/", handler))
+	assert.Equal(t, r, r.Match([]string{http.MethodPut, http.MethodPatch}, "/match", handler))
 
 	assert.Equal(t, r, r.StaticFile("/file", "."))
 	assert.Equal(t, r, r.StaticFileFS("/static2", ".", Dir(".", false)))
diff --git a/routes_test.go b/routes_test.go
index ada8e1e..633c0ab 100644
--- a/routes_test.go
+++ b/routes_test.go
@@ -185,6 +185,54 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
 	w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
 	assert.Equal(t, 200, w.Code)
 
+	w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api#?"})
+	assert.Equal(t, "/api/path", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
+	w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api"})
+	assert.Equal(t, "/api/path", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
+	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"})
+	assert.Equal(t, "/api/path2/", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
+	w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../"})
+	assert.Equal(t, "//path", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
+	w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../../"})
+	assert.Equal(t, "/path", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
+	w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../gin-gonic.com"})
+	assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
+	w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../gin-gonic.com"})
+	assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
+	w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"})
+	assert.Equal(t, "https/gin-goniccom/https/gin-goniccom/path", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
+	w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#api"})
+	assert.Equal(t, "api/api/path", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
+	w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/#?a=1"})
+	assert.Equal(t, "/nor-mal/a1/path", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
+	w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/%2e%2e/"})
+	assert.Equal(t, "/nor-mal/2e2e/path", w.Header().Get("Location"))
+	assert.Equal(t, 301, w.Code)
+
 	router.RedirectTrailingSlash = false
 
 	w = PerformRequest(router, http.MethodGet, "/path/")
diff --git a/testdata/protoexample/any.go b/testdata/protoexample/any.go
deleted file mode 100644
index 2203f33..0000000
--- a/testdata/protoexample/any.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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{}