From e7693e67c23005743502962d3bb9839a354d6688 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 07:25:36 +0800 Subject: [PATCH 01/67] chore(deps): bump actions/setup-go from 5 to 6 (#4351) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 4 ++-- .github/workflows/goreleaser.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 6f0f7c11..63d6d968 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -20,7 +20,7 @@ jobs: with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: "^1" - name: Setup golangci-lint @@ -55,7 +55,7 @@ jobs: GOPROXY: https://proxy.golang.org steps: - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} cache: false diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 3ca5eb20..c87cf2d6 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -17,7 +17,7 @@ jobs: with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: "^1" - name: Run GoReleaser From 46150257b3eec60e3e0bf1cee7c03439099aef83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 07:26:06 +0800 Subject: [PATCH 02/67] chore(deps): bump github.com/stretchr/testify from 1.10.0 to 1.11.1 (#4347) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.10.0 to 1.11.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-version: 1.11.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5e3e7f76..e141a9c5 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 github.com/quic-go/quic-go v0.54.0 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 google.golang.org/protobuf v1.36.6 diff --git a/go.sum b/go.sum index c97f1d95..63974fd4 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= From 28172fa68206b2ced9df3417fad50bcabd6d9eb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 07:26:29 +0800 Subject: [PATCH 03/67] chore(deps): bump google.golang.org/protobuf from 1.36.6 to 1.36.8 (#4346) Bumps google.golang.org/protobuf from 1.36.6 to 1.36.8. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e141a9c5..ec11d2e5 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 - google.golang.org/protobuf v1.36.6 + google.golang.org/protobuf v1.36.8 ) require ( diff --git a/go.sum b/go.sum index 63974fd4..5cf3ce4a 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f9bd00a6b7939b941fde3fdd239367f4a7d6b137 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sun, 14 Sep 2025 07:29:11 +0800 Subject: [PATCH 04/67] perf(context): optimize getMapFromFormData performance (#4339) Co-authored-by: 1911860538 --- context.go | 19 ++++++++--- context_test.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index 842ad2ff..422c2df7 100644 --- a/context.go +++ b/context.go @@ -654,14 +654,23 @@ func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { func getMapFromFormData(m map[string][]string, key string) (map[string]string, bool) { d := make(map[string]string) found := false + keyLen := len(key) + for k, v := range m { - if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { - if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { - found = true - d[k[i+1:][:j]] = v[0] - } + if len(k) < keyLen+3 { // key + "[" + at least one char + "]" + continue + } + + if k[:keyLen] != key || k[keyLen] != '[' { + continue + } + + if j := strings.IndexByte(k[keyLen+1:], ']'); j > 0 { + found = true + d[k[keyLen+1:keyLen+1+j]] = v[0] } } + return d, found } diff --git a/context_test.go b/context_test.go index f51c147f..9b584890 100644 --- a/context_test.go +++ b/context_test.go @@ -3554,3 +3554,88 @@ func TestGetMapFromFormData(t *testing.T) { }) } } + +func BenchmarkGetMapFromFormData(b *testing.B) { + // Test case 1: Small dataset with bracket notation + smallData := map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "names[a]": {"mike"}, + "names[b]": {"maria"}, + } + + // Test case 2: Medium dataset with mixed data + mediumData := map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "ids[c]": {"test"}, + "ids[d]": {"value"}, + "names[a]": {"mike"}, + "names[b]": {"maria"}, + "names[c]": {"john"}, + "names[d]": {"jane"}, + "other[key1]": {"value1"}, + "other[key2]": {"value2"}, + "simple": {"data"}, + "another": {"info"}, + } + + // Test case 3: Large dataset with many bracket keys + largeData := make(map[string][]string) + for i := 0; i < 100; i++ { + key := fmt.Sprintf("ids[%d]", i) + largeData[key] = []string{fmt.Sprintf("value%d", i)} + } + for i := 0; i < 50; i++ { + key := fmt.Sprintf("names[%d]", i) + largeData[key] = []string{fmt.Sprintf("name%d", i)} + } + for i := 0; i < 25; i++ { + key := fmt.Sprintf("other[key%d]", i) + largeData[key] = []string{fmt.Sprintf("other%d", i)} + } + + // Test case 4: Dataset with many non-matching keys (worst case) + worstCaseData := make(map[string][]string) + for i := 0; i < 100; i++ { + key := fmt.Sprintf("nonmatching%d", i) + worstCaseData[key] = []string{fmt.Sprintf("value%d", i)} + } + worstCaseData["ids[a]"] = []string{"hi"} + worstCaseData["ids[b]"] = []string{"3.14"} + + // Test case 5: Dataset with short keys (best case for early exit) + shortKeysData := map[string][]string{ + "a": {"value1"}, + "b": {"value2"}, + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + } + + benchmarks := []struct { + name string + data map[string][]string + key string + }{ + {"Small_Bracket", smallData, "ids"}, + {"Small_Names", smallData, "names"}, + {"Medium_Bracket", mediumData, "ids"}, + {"Medium_Names", mediumData, "names"}, + {"Medium_Other", mediumData, "other"}, + {"Large_Bracket", largeData, "ids"}, + {"Large_Names", largeData, "names"}, + {"Large_Other", largeData, "other"}, + {"WorstCase_Bracket", worstCaseData, "ids"}, + {"ShortKeys_Bracket", shortKeysData, "ids"}, + {"Empty_Key", smallData, "notfound"}, + } + + for _, bm := range benchmarks { + b.Run(bm.name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = getMapFromFormData(bm.data, bm.key) + } + }) + } +} From 9b1e3533e2d17b6152b05efeab8280f450e68e52 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 19 Sep 2025 08:35:34 +0800 Subject: [PATCH 05/67] refactor(tree): replace string(/) with "/" in node.insertChild (#4354) Co-authored-by: 1911860538 --- tree.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tree.go b/tree.go index a298d748..78479b6f 100644 --- a/tree.go +++ b/tree.go @@ -383,7 +383,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) } n.addChild(child) - n.indices = string('/') + n.indices = "/" n = child n.priority++ From cca98d2d266d3797a8bf70f5903c2fbe32e8bf86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 08:35:54 +0800 Subject: [PATCH 06/67] chore(deps): bump google.golang.org/protobuf from 1.36.8 to 1.36.9 (#4356) Bumps google.golang.org/protobuf from 1.36.8 to 1.36.9. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ec11d2e5..c395bfe3 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 - google.golang.org/protobuf v1.36.8 + google.golang.org/protobuf v1.36.9 ) require ( diff --git a/go.sum b/go.sum index 5cf3ce4a..65bd4728 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From e198f6e859220afd35bc2fb2fd5d404d7c0882ca Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 19 Sep 2025 08:39:17 +0800 Subject: [PATCH 07/67] refactor(render): remove headers parameter from writeHeader (#4353) Co-authored-by: 1911860538 --- render/reader.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/render/reader.go b/render/reader.go index 5752d8d8..ae1a7b5e 100644 --- a/render/reader.go +++ b/render/reader.go @@ -27,7 +27,7 @@ func (r Reader) Render(w http.ResponseWriter) (err error) { } r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) } - r.writeHeaders(w, r.Headers) + r.writeHeaders(w) _, err = io.Copy(w, r.Reader) return } @@ -37,10 +37,10 @@ func (r Reader) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } -// writeHeaders writes custom Header. -func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { +// writeHeaders writes headers from r.Headers into response. +func (r Reader) writeHeaders(w http.ResponseWriter) { header := w.Header() - for k, v := range headers { + for k, v := range r.Headers { if header.Get(k) == "" { header.Set(k, v) } From da372fc77840b3badf4efef5ec2d203cdc73f1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Fri, 19 Sep 2025 08:40:33 +0800 Subject: [PATCH 08/67] build(deps): upgrade github.com/bytedance/sonic from v1.13.2 to v1.14.0 (#4342) * build(deps): upgrade github.com/bytedance/sonic from v1.13.2 to v1.14.0 Signed-off-by: Flc * build(deps): upgrade github.com/bytedance/sonic from v1.13.2 to v1.14.0 Signed-off-by: Flc * test: update expected status code for request too large test Signed-off-by: Flc --------- Signed-off-by: Flc --- context_test.go | 5 ++--- go.mod | 12 ++++++------ go.sum | 29 ++++++++++++----------------- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/context_test.go b/context_test.go index 9b584890..fbc13879 100644 --- a/context_test.go +++ b/context_test.go @@ -1991,13 +1991,12 @@ func TestContextContentType(t *testing.T) { } func TestContextBindRequestTooLarge(t *testing.T) { - // When using sonic or go-json as JSON encoder, they do not propagate the http.MaxBytesError error + // When using go-json as JSON encoder, they do not propagate the http.MaxBytesError error // The response will fail with a generic 400 instead of 413 // https://github.com/goccy/go-json/issues/485 - // https://github.com/bytedance/sonic/issues/800 var expectedCode int switch json.Package { - case "github.com/goccy/go-json", "github.com/bytedance/sonic": + case "github.com/goccy/go-json": expectedCode = http.StatusBadRequest default: expectedCode = http.StatusRequestEntityTooLarge diff --git a/go.mod b/go.mod index c395bfe3..dd7d99bd 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.23.0 require ( - github.com/bytedance/sonic v1.13.2 + github.com/bytedance/sonic v1.14.0 github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.27.0 github.com/goccy/go-json v0.10.2 @@ -20,24 +20,24 @@ require ( ) require ( - github.com/bytedance/sonic/loader v0.2.4 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // 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/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.5.0 // indirect - golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/arch v0.20.0 // indirect golang.org/x/crypto v0.40.0 // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect + golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.27.0 // indirect golang.org/x/tools v0.34.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 65bd4728..75ecb7c1 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,9 @@ -github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= -github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= -github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= 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= @@ -30,9 +28,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX 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/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -53,7 +50,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -65,8 +61,8 @@ github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= -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.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= @@ -76,8 +72,8 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= @@ -89,4 +85,3 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= From 2119046230f0119c7c88f86a6b441d9d3aaad03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Fri, 19 Sep 2025 09:44:22 +0800 Subject: [PATCH 09/67] ci: support Go 1.25 (#4341) - Update GitHub Actions workflow to include Go 1.25 in the test matrix - This change expands the range of Go versions tested for the project Signed-off-by: Flc --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 63d6d968..e049048c 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.23", "1.24"] + go: ["1.23", "1.24", "1.25"] test-tags: [ "", From cb000f570c127a503535fa5be9c0237823ea7e4d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 20 Sep 2025 19:24:57 +0800 Subject: [PATCH 10/67] ci: integrate Trivy vulnerability scanning into CI workflow (#4359) - Add a GitHub Actions job for vulnerability scanning using Trivy - Configure Trivy to scan the repository for vulnerabilities of severity critical, high, and medium - Ensure the workflow fails if vulnerabilities are found Signed-off-by: appleboy --- .github/workflows/gin.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index e049048c..17b54ab3 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -81,3 +81,19 @@ jobs: uses: codecov/codecov-action@v4 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} + + vulnerability-scanning: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Run Trivy vulnerability scanner in repo mode + uses: aquasecurity/trivy-action@0.33.1 + with: + scan-type: 'fs' + ignore-unfixed: true + format: 'table' + exit-code: '1' + severity: 'CRITICAL,HIGH,MEDIUM' From 7858527c8c2a15bddf27ea71162f8f70c82f2cdf Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 20 Sep 2025 19:32:43 +0800 Subject: [PATCH 11/67] docs(changelog): update release notes for Gin v1.10.1 (#4360) - Add release notes for Gin v1.10.1, including new features, enhancements, and build process updates Signed-off-by: appleboy --- CHANGELOG.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5648902d..37dd0e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Gin ChangeLog +## Gin v1.10.1 + +### Features + +* refactor: strengthen HTTPS security and improve code organization +* feat(binding): Support custom BindUnmarshaler for binding. (#3933) + +### Enhancements + +* chore(deps): bump github.com/bytedance/sonic from 1.11.3 to 1.11.6 (#3940) +* chore(deps): bump golangci/golangci-lint-action from 4 to 5 (#3941) +* chore: update external dependencies to latest versions (#3950) +* chore: update various Go dependencies to latest versions (#3901) +* chore: refactor configuration files for better readability (#3951) +* chore: update changelog categories and improve documentation (#3917) +* feat: update version constant to v1.10.0 (#3952) + +### Build process updates + +* ci(release): refactor changelog regex patterns and exclusions (#3914) +* ci(Makefile): vet command add .PHONY (#3915) + ## Gin v1.10.0 ### Features @@ -26,7 +48,7 @@ * fix(uri): query binding bug (#3236) (@illiafox) * fix: Add pointer support for url query params (#3659) (#3666) (@omkar-foss) * fix: protect Context.Keys map when call Copy method (#3873) (@kingcanfish) - + ### Enhancements * chore(CI): update release args (#3595) (@qloog) From 6ad6205e9c94a4b8a320219e28c37c29d22a7a2c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 20 Sep 2025 19:38:42 +0800 Subject: [PATCH 12/67] docs(changelog): upgrade Gin to v1.11.0 and add release notes (#4361) - Add release notes for Gin v1.11.0, detailing new features, enhancements, bug fixes, CI/build improvements, dependency updates, and documentation changes - Update Gin framework version to v1.11.0 ref: https://github.com/gin-gonic/gin/issues/4325 Signed-off-by: appleboy --- CHANGELOG.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ version.go | 2 +- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37dd0e4a..9451db39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,77 @@ # Gin ChangeLog +## Gin v1.11.0 + +### Features + +* feat(gin): Experimental support for HTTP/3 using quic-go/quic-go ([#3210](https://github.com/gin-gonic/gin/pull/3210)) +* feat(form): add array collection format in form binding ([#3986](https://github.com/gin-gonic/gin/pull/3986)), add custom string slice for form tag unmarshal ([#3970](https://github.com/gin-gonic/gin/pull/3970)) +* feat(binding): add BindPlain ([#3904](https://github.com/gin-gonic/gin/pull/3904)) +* feat(fs): Export, test and document OnlyFilesFS ([#3939](https://github.com/gin-gonic/gin/pull/3939)) +* feat(binding): add support for unixMilli and unixMicro ([#4190](https://github.com/gin-gonic/gin/pull/4190)) +* feat(form): Support default values for collections in form binding ([#4048](https://github.com/gin-gonic/gin/pull/4048)) +* feat(context): GetXxx added support for more go native types ([#3633](https://github.com/gin-gonic/gin/pull/3633)) + +### Enhancements + +* perf(context): optimize getMapFromFormData performance ([#4339](https://github.com/gin-gonic/gin/pull/4339)) +* refactor(tree): replace string(/) with "/" in node.insertChild ([#4354](https://github.com/gin-gonic/gin/pull/4354)) +* refactor(render): remove headers parameter from writeHeader ([#4353](https://github.com/gin-gonic/gin/pull/4353)) +* refactor(context): simplify "GetType()" functions ([#4080](https://github.com/gin-gonic/gin/pull/4080)) +* refactor(slice): simplify SliceValidationError Error method ([#3910](https://github.com/gin-gonic/gin/pull/3910)) +* refactor(context):Avoid using filepath.Dir twice in SaveUploadedFile ([#4181](https://github.com/gin-gonic/gin/pull/4181)) +* refactor(context): refactor context handling and improve test robustness ([#4066](https://github.com/gin-gonic/gin/pull/4066)) +* refactor(binding): use strings.Cut to replace strings.Index ([#3522](https://github.com/gin-gonic/gin/pull/3522)) +* refactor(context): add an optional permission parameter to SaveUploadedFile ([#4068](https://github.com/gin-gonic/gin/pull/4068)) +* refactor(context): verify URL is Non-nil in initQueryCache() ([#3969](https://github.com/gin-gonic/gin/pull/3969)) +* refactor(context): YAML judgment logic in Negotiate ([#3966](https://github.com/gin-gonic/gin/pull/3966)) +* tree: replace the self-defined 'min' to official one ([#3975](https://github.com/gin-gonic/gin/pull/3975)) +* context: Remove redundant filepath.Dir usage ([#4181](https://github.com/gin-gonic/gin/pull/4181)) + +### Bug Fixes + +* fix: prevent middleware re-entry issue in HandleContext ([#3987](https://github.com/gin-gonic/gin/pull/3987)) +* fix(binding): prevent duplicate decoding and add validation in decodeToml ([#4193](https://github.com/gin-gonic/gin/pull/4193)) +* fix(gin): Do not panic when handling method not allowed on empty tree ([#4003](https://github.com/gin-gonic/gin/pull/4003)) +* fix(gin): data race warning for gin mode ([#1580](https://github.com/gin-gonic/gin/pull/1580)) +* fix(context): verify URL is Non-nil in initQueryCache() ([#3969](https://github.com/gin-gonic/gin/pull/3969)) +* fix(context): YAML judgment logic in Negotiate ([#3966](https://github.com/gin-gonic/gin/pull/3966)) +* fix(context): check handler is nil ([#3413](https://github.com/gin-gonic/gin/pull/3413)) +* fix(readme): fix broken link to English documentation ([#4222](https://github.com/gin-gonic/gin/pull/4222)) +* fix(tree): Keep panic infos consistent when wildcard type build faild ([#4077](https://github.com/gin-gonic/gin/pull/4077)) + +### Build process updates / CI + +* ci: integrate Trivy vulnerability scanning into CI workflow ([#4359](https://github.com/gin-gonic/gin/pull/4359)) +* ci: support Go 1.25 in CI/CD ([#4341](https://github.com/gin-gonic/gin/pull/4341)) +* build(deps): upgrade github.com/bytedance/sonic from v1.13.2 to v1.14.0 ([#4342](https://github.com/gin-gonic/gin/pull/4342)) +* ci: add Go version 1.24 to GitHub Actions ([#4154](https://github.com/gin-gonic/gin/pull/4154)) +* build: update Gin minimum Go version to 1.21 ([#3960](https://github.com/gin-gonic/gin/pull/3960)) +* ci(lint): enable new linters (testifylint, usestdlibvars, perfsprint, etc.) ([#4010](https://github.com/gin-gonic/gin/pull/4010), [#4091](https://github.com/gin-gonic/gin/pull/4091), [#4090](https://github.com/gin-gonic/gin/pull/4090)) +* ci(lint): update workflows and improve test request consistency ([#4126](https://github.com/gin-gonic/gin/pull/4126)) + +### Dependency updates + +* chore(deps): bump google.golang.org/protobuf from 1.36.6 to 1.36.9 ([#4346](https://github.com/gin-gonic/gin/pull/4346), [#4356](https://github.com/gin-gonic/gin/pull/4356)) +* chore(deps): bump github.com/stretchr/testify from 1.10.0 to 1.11.1 ([#4347](https://github.com/gin-gonic/gin/pull/4347)) +* chore(deps): bump actions/setup-go from 5 to 6 ([#4351](https://github.com/gin-gonic/gin/pull/4351)) +* chore(deps): bump github.com/quic-go/quic-go from 0.53.0 to 0.54.0 ([#4328](https://github.com/gin-gonic/gin/pull/4328)) +* chore(deps): bump golang.org/x/net from 0.33.0 to 0.38.0 ([#4178](https://github.com/gin-gonic/gin/pull/4178), [#4221](https://github.com/gin-gonic/gin/pull/4221)) +* chore(deps): bump github.com/go-playground/validator/v10 from 10.20.0 to 10.22.1 ([#4052](https://github.com/gin-gonic/gin/pull/4052)) + +### Documentation updates + +* docs(changelog): update release notes for Gin v1.10.1 ([#4360](https://github.com/gin-gonic/gin/pull/4360)) +* docs: Fixing English grammar mistakes and awkward sentence structure in doc/doc.md ([#4207](https://github.com/gin-gonic/gin/pull/4207)) +* docs: update documentation and release notes for Gin v1.10.0 ([#3953](https://github.com/gin-gonic/gin/pull/3953)) +* docs: fix typo in Gin Quick Start ([#3997](https://github.com/gin-gonic/gin/pull/3997)) +* docs: fix comment and link issues ([#4205](https://github.com/gin-gonic/gin/pull/4205), [#3938](https://github.com/gin-gonic/gin/pull/3938)) +* docs: fix route group example code ([#4020](https://github.com/gin-gonic/gin/pull/4020)) +* docs(readme): add Portuguese documentation ([#4078](https://github.com/gin-gonic/gin/pull/4078)) +* docs(context): fix some function names in comment ([#4079](https://github.com/gin-gonic/gin/pull/4079)) + +--- + ## Gin v1.10.1 ### Features diff --git a/version.go b/version.go index 93ad9654..8049058c 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.10.0" +const Version = "v1.11.0" From 4dd00f81b1124d1a72e3d0fe050a224ff0ffcb88 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 20 Sep 2025 20:58:46 +0800 Subject: [PATCH 13/67] docs(readme): revamp and expand documentation for clarity and completeness (#4362) - Update contributing header to single hash style - Remove deprecated badge and improve project summary wording - Reorganize and clarify feature descriptions and benefits - Restructure getting started and installation instructions for clarity - Expand and annotate the first example application walkthrough - Detail the steps for running the sample application and expected output - Improve guidance on learning resources and example projects - Enhance API reference, documentation links, and tutorial references - Add a clear performance benchmarks section comparing Gin to other frameworks - Expand middleware section with ecosystem highlights and usage details - Create a production usage section listing notable projects using Gin - Revamp contribution section with clearer procedure and encouragement for new contributors Signed-off-by: appleboy --- CONTRIBUTING.md | 2 +- README.md | 180 ++++++++++++++++++++++++++++++------------------ 2 files changed, 113 insertions(+), 69 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1c723c6..623665ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -## Contributing +# Contributing - With issues: - Use the search tool before opening a new issue. diff --git a/README.md b/README.md index 94e08a78..a24b349f 100644 --- a/README.md +++ b/README.md @@ -9,46 +9,48 @@ [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) -[![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/gin-gonic/gin)](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin) -Gin is a web framework written in [Go](https://go.dev/). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). -If you need performance and good productivity, you will love Gin. +Gin is a high-performance HTTP web framework written in [Go](https://go.dev/). It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to [httprouter](https://github.com/julienschmidt/httprouter). Gin is designed for building REST APIs, web applications, and microservices where speed and developer productivity are essential. -**Gin's key features are:** +**Why choose Gin?** -- Zero allocation router -- Speed -- Middleware support -- Crash-free -- JSON validation -- Route grouping -- Error management -- Built-in rendering -- Extensible +Gin combines the simplicity of Express.js-style routing with Go's performance characteristics, making it ideal for: -## Getting started +- Building high-throughput REST APIs +- Developing microservices that need to handle many concurrent requests +- Creating web applications that require fast response times +- Prototyping web services quickly with minimal boilerplate + +**Gin's key features:** + +- **Zero allocation router** - Extremely memory-efficient routing with no heap allocations +- **High performance** - Benchmarks show superior speed compared to other Go web frameworks +- **Middleware support** - Extensible middleware system for authentication, logging, CORS, etc. +- **Crash-free** - Built-in recovery middleware prevents panics from crashing your server +- **JSON validation** - Automatic request/response JSON binding and validation +- **Route grouping** - Organize related routes and apply common middleware +- **Error management** - Centralized error handling and logging +- **Built-in rendering** - Support for JSON, XML, HTML templates, and more +- **Extensible** - Large ecosystem of community middleware and plugins + +## Getting Started ### Prerequisites -Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above. +- **Go version**: Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above +- **Basic Go knowledge**: Familiarity with Go syntax and package management is helpful -### Getting Gin +### Installation -With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), `go [build|run|test]` automatically fetches the necessary dependencies when you add the import in your code: +With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), simply import Gin in your code and Go will automatically fetch it during build: -```sh +```go import "github.com/gin-gonic/gin" ``` -Alternatively, use `go get`: +### Your First Gin Application -```sh -go get -u github.com/gin-gonic/gin -``` - -### Running Gin - -A basic example: +Here's a complete example that demonstrates Gin's simplicity: ```go package main @@ -60,59 +62,80 @@ import ( ) func main() { + // Create a Gin router with default middleware (logger and recovery) r := gin.Default() + + // Define a simple GET endpoint r.GET("/ping", func(c *gin.Context) { + // Return JSON response c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) - r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") + + // Start server on port 8080 (default) + // Server will listen on 0.0.0.0:8080 (localhost:8080 on Windows) + r.Run() } ``` -To run the code, use the `go run` command, like: +**Running the application:** -```sh -go run example.go -``` +1. Save the code above as `main.go` +2. Run the application: -Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response! + ```sh + go run main.go + ``` -### See more examples +3. Open your browser and visit [`http://localhost:8080/ping`](http://localhost:8080/ping) +4. You should see: `{"message":"pong"}` -#### Quick Start +**What this example demonstrates:** -Learn and practice with the [Gin Quick Start](docs/doc.md), which includes API examples and builds tag. +- Creating a Gin router with default middleware +- Defining HTTP endpoints with simple handler functions +- Returning JSON responses +- Starting an HTTP server -#### Examples +### Next Steps -A number of ready-to-run examples demonstrating various use cases of Gin are available in the [Gin examples](https://github.com/gin-gonic/examples) repository. +After running your first Gin application, explore these resources to learn more: -## Documentation +#### 📚 Learning Resources -See the [API documentation on go.dev](https://pkg.go.dev/github.com/gin-gonic/gin). +- **[Gin Quick Start Guide](docs/doc.md)** - Comprehensive tutorial with API examples and build configurations +- **[Example Repository](https://github.com/gin-gonic/examples)** - Ready-to-run examples demonstrating various Gin use cases: + - REST API development + - Authentication & middleware + - File uploads and downloads + - WebSocket connections + - Template rendering -The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages: +## 📖 Documentation -- [English](https://gin-gonic.com/en/docs/) -- [简体中文](https://gin-gonic.com/zh-cn/docs/) -- [繁體中文](https://gin-gonic.com/zh-tw/docs/) -- [日本語](https://gin-gonic.com/ja/docs/) -- [Español](https://gin-gonic.com/es/docs/) -- [한국어](https://gin-gonic.com/ko-kr/docs/) -- [Turkish](https://gin-gonic.com/tr/docs/) -- [Persian](https://gin-gonic.com/fa/docs/) -- [Português](https://gin-gonic.com/pt/docs/) -- [Russian](https://gin-gonic.com/ru/docs/) -- [Indonesian](https://gin-gonic.com/id/docs/) +### API Reference -### Articles +- **[Go.dev API Documentation](https://pkg.go.dev/github.com/gin-gonic/gin)** - Complete API reference with examples -- [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin) +### User Guides -## Benchmarks +The comprehensive documentation is available on [gin-gonic.com](https://gin-gonic.com) in multiple languages: -Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks](/BENCHMARKS.md). +- [English](https://gin-gonic.com/en/docs/) | [简体中文](https://gin-gonic.com/zh-cn/docs/) | [繁體中文](https://gin-gonic.com/zh-tw/docs/) +- [日本語](https://gin-gonic.com/ja/docs/) | [한국어](https://gin-gonic.com/ko-kr/docs/) | [Español](https://gin-gonic.com/es/docs/) +- [Turkish](https://gin-gonic.com/tr/docs/) | [Persian](https://gin-gonic.com/fa/docs/) | [Português](https://gin-gonic.com/pt/docs/) +- [Russian](https://gin-gonic.com/ru/docs/) | [Indonesian](https://gin-gonic.com/id/docs/) + +### Official Tutorials + +- [Go.dev Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin) + +## ⚡ Performance Benchmarks + +Gin demonstrates exceptional performance compared to other Go web frameworks. It uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) for maximum efficiency. [View detailed benchmarks →](/BENCHMARKS.md) + +**Gin vs. Other Go Frameworks** (GitHub API routing benchmark): | Benchmark name | (1) | (2) | (3) | (4) | | ------------------------------ | --------: | --------------: | -----------: | --------------: | @@ -152,23 +175,44 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better -## Middleware +## 🔌 Middleware Ecosystem -You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib) and [gin-gonic/contrib](https://github.com/gin-gonic/contrib). +Gin has a rich ecosystem of middleware for common web development needs. Explore community-contributed middleware: -## Uses +- **[gin-contrib](https://github.com/gin-contrib)** - Official middleware collection including: + - Authentication (JWT, Basic Auth, Sessions) + - CORS, Rate limiting, Compression + - Logging, Metrics, Tracing + - Static file serving, Template engines + +- **[gin-gonic/contrib](https://github.com/gin-gonic/contrib)** - Additional community middleware -Here are some awesome projects that are using the [Gin](https://github.com/gin-gonic/gin) web framework. +## 🏢 Production Usage -- [gorush](https://github.com/appleboy/gorush): A push notification server. -- [fnproject](https://github.com/fnproject/fn): A container native, cloud agnostic serverless platform. -- [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Google TensorFlow. -- [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middleware. -- [picfit](https://github.com/thoas/picfit): An image resizing server. -- [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. +Gin powers many high-traffic applications and services in production: -## Contributing +- **[gorush](https://github.com/appleboy/gorush)** - High-performance push notification server +- **[fnproject](https://github.com/fnproject/fn)** - Container-native, serverless platform +- **[photoprism](https://github.com/photoprism/photoprism)** - AI-powered personal photo management +- **[lura](https://github.com/luraproject/lura)** - Ultra-performant API Gateway framework +- **[picfit](https://github.com/thoas/picfit)** - Real-time image processing server +- **[dkron](https://github.com/distribworks/dkron)** - Distributed job scheduling system -Gin is the work of hundreds of contributors. We appreciate your help! +## 🤝 Contributing -Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. +Gin is the work of hundreds of contributors from around the world. We welcome and appreciate your contributions! + +### How to Contribute + +- 🐛 **Report bugs** - Help us identify and fix issues +- 💡 **Suggest features** - Share your ideas for improvements +- 📝 **Improve documentation** - Help make our docs clearer +- 🔧 **Submit code** - Fix bugs or implement new features +- 🧪 **Write tests** - Improve our test coverage + +### Getting Started with Contributing + +1. Check out our [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines +2. Join our community discussions and ask questions + +**All contributions are valued and help make Gin better for everyone!** From 1bbbec0baf6370bfb74e07a9060292939534290d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 21 Sep 2025 10:50:09 +0800 Subject: [PATCH 14/67] docs: announce Gin 1.11.0 release with blog link (#4363) - Add a new section announcing Gin 1.11.0 and link to its release blog post Signed-off-by: appleboy --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index a24b349f..8343a55b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) +## 📰 [Announcing Gin 1.11.0!](https://gin-gonic.com/en/blog/news/gin-1-11-0-release-announcement/) + +Read about the latest features and improvements in Gin 1.11.0 on our official blog. + +--- + Gin is a high-performance HTTP web framework written in [Go](https://go.dev/). It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to [httprouter](https://github.com/julienschmidt/httprouter). Gin is designed for building REST APIs, web applications, and microservices where speed and developer productivity are essential. **Why choose Gin?** From 792541470403dac0487b6213a22c7c2491084d83 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 21 Sep 2025 12:48:19 +0800 Subject: [PATCH 15/67] docs: revamp GitHub contribution and support templates (#4364) - Replace the old issue template with new, structured YAML templates for bug reports and feature requests - Add a configuration file that directs users to relevant documentation and support links - Update the pull request template to use a checklist format and clarify documentation requirements Signed-off-by: appleboy --- .github/ISSUE_TEMPLATE.md | 49 ----------------- .github/ISSUE_TEMPLATE/bug-report.yaml | 60 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 11 ++++ .github/ISSUE_TEMPLATE/feature-request.yaml | 18 +++++++ .github/PULL_REQUEST_TEMPLATE.md | 15 +++--- 5 files changed, 98 insertions(+), 55 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yaml diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 864787ca..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,49 +0,0 @@ -- With issues: - - Use the search tool before opening a new issue. - - Please provide source code and commit sha if you found a bug. - - Review existing issues and provide feedback or react to them. - -## Description - - - -## How to reproduce - - -``` -package main - -import ( - "github.com/gin-gonic/gin" -) - -func main() { - g := gin.Default() - g.GET("/hello/:name", func(c *gin.Context) { - c.String(200, "Hello %s", c.Param("name")) - }) - g.Run(":9000") -} -``` - -## Expectations - - -``` -$ curl http://localhost:9000/hello/world -Hello world -``` - -## Actual result - - -``` -$ curl -i http://localhost:9000/hello/world - -``` - -## Environment - -- go version: -- gin version (or commit ref): -- operating system: diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml new file mode 100644 index 00000000..2cf2f362 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -0,0 +1,60 @@ +name: Bug Report +description: Found something you weren't expecting? Report it here! +labels: ["type/bug"] +body: + - type: markdown + attributes: + value: | + NOTE: If your issue is a security concern, please send an email to appleboy.tw@gmail.com instead of opening a public issue. + - type: markdown + attributes: + value: | + 1. Please speak English, this is the language all maintainers can speak and write. + 2. Please ask questions problems on our Discussions Forum (https://github.com/gin-gonic/gin/discussions). + 3. Make sure you are using the latest release and + take a moment to check that your issue hasn't been reported before. + - type: textarea + id: description + attributes: + label: Description + description: | + Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below) + - type: input + id: gin-ver + attributes: + label: Gin Version + description: Gin version (or commit reference) of your instance + validations: + required: true + - type: dropdown + id: can-reproduce + attributes: + label: Can you reproduce the bug? + description: | + If so, please write the steps to reproduce the bug. + options: + - "Yes" + - "No" + validations: + required: true + - type: markdown + attributes: + value: | + It's really important to provide pertinent logs + Please read https://docs.gitea.com/administration/logging-config#collecting-logs-for-help + In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini + - type: textarea + id: source-code + attributes: + label: Source Code + description: If this issue involves source code, please provide a minimal reproducible example + - type: input + id: go-ver + attributes: + label: Go Version + description: The version of Go running on the server + - type: input + id: os-ver + attributes: + label: Operating System + description: The operating system you are using to run Gin diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..ceff9fe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Go.dev API Documentation + url: https://pkg.go.dev/github.com/gin-gonic/gin + about: Comprehensive API documentation for Gin. + - name: Gin User Guides + url: https://gin-gonic.com/ + about: In-depth user guides and tutorials for using Gin. + - name: Discussions Forum + url: https://github.com/gin-gonic/gin/discussions + about: Questions and configuration or deployment problems can also be discussed. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml new file mode 100644 index 00000000..a40215aa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -0,0 +1,18 @@ +name: Feature Request +description: Got an idea for a feature that Gin doesn't have currently? Submit your idea here! +labels: ["type/proposal"] +body: + - type: markdown + attributes: + value: | + 1. Please speak English, this is the language all maintainers can speak and write. + 2. Please ask questions problems on our Discussions Forum (https://github.com/gin-gonic/gin/discussions). + 3. Please take a moment to check that your feature hasn't already been suggested. + - type: textarea + id: description + attributes: + label: Feature Description + placeholder: | + I think it would be great if Gin had... + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 96e70bba..846c04fb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,10 @@ -- 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. +# Pull Request Checklist +Please ensure your pull request meets the following requirements: + +- [ ] Open your pull request against the `master` branch. +- [ ] All tests pass in available continuous integration systems (e.g., GitHub Actions). +- [ ] Tests are added or modified as needed to cover code changes. +- [ ] If the pull request introduces a new feature, the feature is documented in the `docs/doc.md`. + +Thank you for contributing! From 6a1d1218c3dbfc11427abc1ba39b86e81dff1e54 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 21 Sep 2025 17:39:33 +0800 Subject: [PATCH 16/67] docs: revamp contributing guidelines with comprehensive instructions (#4365) - Rewrite and expand the contributing guidelines for clarity and thoroughness - Add distinct sections for Issues and Pull Requests with step-by-step instructions - Include links to documentation, user guides, and the discussions forum - Provide advice for reporting bugs and making feature requests - Specify requirements for pull requests, including branch, commit count, and test coverage - Clarify documentation expectations for new features and reference the pull request checklist - Add guidance for security-related bug reports and communication language Signed-off-by: appleboy --- CONTRIBUTING.md | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 623665ec..6f556b9b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,41 @@ # Contributing -- With issues: - - Use the search tool before opening a new issue. - - Please provide source code and commit sha if you found a bug. - - Review existing issues and provide feedback or react to them. +We welcome both issue reports and pull requests! Please follow these guidelines to help maintainers respond effectively. -- 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. +## Issues + +- **Before opening a new issue:** + - Use the search tool to check for existing issues or feature requests. + - Review existing issues and provide feedback or react to them. + - Use English for all communications — it is the language all maintainers read and write. + - For questions, configuration or deployment problems, please use the [Discussions Forum](https://github.com/gin-gonic/gin/discussions). + - For bug reports involving sensitive security issues, email instead of posting publicly. + +- **Reporting a bug:** + - Please provide a clear description of your issue, and a minimal reproducible code example if possible. + - Include the Gin version (or commit reference), Go version, and operating system. + - Indicate whether you can reproduce the bug and describe steps to do so. + - Attach relevant logs per [Logging Documentation](https://docs.gitea.com/administration/logging-config#collecting-logs-for-help). + +- **Feature requests:** + - Before opening a request, check that a similar idea hasn’t already been suggested. + - Clearly describe your proposed feature and its benefits. + +_For API Documentation, User Guides, and more, see:_ + +- [Go.dev API Documentation](https://pkg.go.dev/github.com/gin-gonic/gin) +- [Gin User Guides](https://gin-gonic.com/) +- [Discussions Forum](https://github.com/gin-gonic/gin/discussions) + +## Pull Requests + +Please ensure your pull request meets the following requirements: + +- Open your pull request against the `master` branch. +- Your pull request should have no more than two commits — squash them if necessary. +- All tests pass in available continuous integration systems (e.g., GitHub Actions). +- Add or modify tests to cover your code changes. +- If your pull request introduces a new feature, document it in [`docs/doc.md`](docs/doc.md:1), not in the README. +- Follow the checklist in the [Pull Request Template](.github/PULL_REQUEST_TEMPLATE.md:1). + +Thank you for contributing! From 59e9d4a794f12c4f9a6c7bed441b9644e5f6d99b Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sun, 21 Sep 2025 17:41:54 +0800 Subject: [PATCH 17/67] refactor(ginS): use sync.OnceValue to simplify engine function (#4314) Co-authored-by: 1911860538 --- ginS/gins.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/ginS/gins.go b/ginS/gins.go index 40088172..7918ce3a 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -12,17 +12,9 @@ import ( "github.com/gin-gonic/gin" ) -var ( - once sync.Once - internalEngine *gin.Engine -) - -func engine() *gin.Engine { - once.Do(func() { - internalEngine = gin.Default() - }) - return internalEngine -} +var engine = sync.OnceValue(func() *gin.Engine { + return gin.Default() +}) // LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob. func LoadHTMLGlob(pattern string) { From 414de60574449457f3192a7a1d5528940db2836d Mon Sep 17 00:00:00 2001 From: cui Date: Sun, 21 Sep 2025 17:46:17 +0800 Subject: [PATCH 18/67] refactor(context): using maps.Clone (#4333) ref: https://go-review.googlesource.com/c/go/+/471400 --- context.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/context.go b/context.go index 422c2df7..d7280c5d 100644 --- a/context.go +++ b/context.go @@ -10,6 +10,7 @@ import ( "io" "io/fs" "log" + "maps" "math" "mime/multipart" "net" @@ -130,11 +131,8 @@ func (c *Context) Copy() *Context { cp.fullPath = c.fullPath cKeys := c.Keys - cp.Keys = make(map[any]any, len(cKeys)) c.mu.RLock() - for k, v := range cKeys { - cp.Keys[k] = v - } + cp.Keys = maps.Clone(cKeys) c.mu.RUnlock() cParams := c.Params From f3a5e787199f9ee1821fda15e93aec76737631ed Mon Sep 17 00:00:00 2001 From: appleboy Date: Sun, 21 Sep 2025 17:48:39 +0800 Subject: [PATCH 19/67] docs: update feature documentation instructions for broken doc link - Fix a broken link to docs/doc.md in the feature documentation instructions Signed-off-by: appleboy --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f556b9b..9703d6b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ Please ensure your pull request meets the following requirements: - Your pull request should have no more than two commits — squash them if necessary. - All tests pass in available continuous integration systems (e.g., GitHub Actions). - Add or modify tests to cover your code changes. -- If your pull request introduces a new feature, document it in [`docs/doc.md`](docs/doc.md:1), not in the README. +- If your pull request introduces a new feature, document it in [`docs/doc.md`](docs/doc.md), not in the README. - Follow the checklist in the [Pull Request Template](.github/PULL_REQUEST_TEMPLATE.md:1). Thank you for contributing! From 61b67de522a189b568aced4c5c16917c558e3387 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 21 Sep 2025 21:11:11 +0800 Subject: [PATCH 20/67] ci(bot): increase frequency and group updates for dependencies (#4367) - Change the update schedule for both gomod and GitHub Actions dependencies from weekly to daily - Add grouping for GitHub Actions updates using a catch-all pattern Signed-off-by: appleboy --- .github/dependabot.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 632e8eb2..ab644980 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,14 @@ version: 2 updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - package-ecosystem: gomod directory: / schedule: - interval: weekly + interval: daily + - package-ecosystem: github-actions + directory: / + groups: + actions: + patterns: + - "*" + schedule: + interval: daily From 048f6fb8849b35be6a4e655076a2247f29f7c284 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Sep 2025 21:18:25 +0800 Subject: [PATCH 21/67] chore(deps): bump the actions group with 2 updates (#4368) Bumps the actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [codecov/codecov-action](https://github.com/codecov/codecov-action). Updates `actions/checkout` from 4 to 5 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) Updates `codecov/codecov-action` from 4 to 5 - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: codecov/codecov-action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 2 +- .github/workflows/gin.yml | 6 +++--- .github/workflows/goreleaser.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9a4c40d7..bd4c52c2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 17b54ab3..e0210214 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Set up Go @@ -61,7 +61,7 @@ jobs: cache: false - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ github.ref }} @@ -78,7 +78,7 @@ jobs: run: make test - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index c87cf2d6..37dfb5bb 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Set up Go From df2753778e7bc5c2dd559361cf0c97b2b313e9bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:10:02 +0800 Subject: [PATCH 22/67] chore(deps): bump github.com/quic-go/quic-go from 0.54.0 to 0.54.1 (#4379) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.54.0 to 0.54.1. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.54.0...v0.54.1) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.54.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dd7d99bd..7eeca3ef 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.54.0 + github.com/quic-go/quic-go v0.54.1 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 diff --git a/go.sum b/go.sum index 75ecb7c1..97bab5dd 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg= +github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= 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= From 234a6d4c00cb77af9852aca0b8289745d5529b4b Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 26 Sep 2025 08:13:39 +0800 Subject: [PATCH 23/67] fix(response): refine hijack behavior for response lifecycle (#4373) * feat: refine hijack behavior for response lifecycle and add tests - Clarify the error message for attempted hijack after response body data is written - Modify hijack behavior: allow hijacking after headers are written (for better websocket compatibility), but block hijacking after any body data is sent - Add comprehensive tests to validate allowed hijack after header write and disallowed hijack after body write fix https://github.com/gin-gonic/gin/issues/4372 Signed-off-by: appleboy * test: use require for immediate test failure on errors - Replace assert with require for error checks to ensure test failures immediately halt execution Signed-off-by: appleboy * Update response_writer.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Signed-off-by: appleboy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- response_writer.go | 6 +++-- response_writer_test.go | 58 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/response_writer.go b/response_writer.go index ab2f5fec..6907e514 100644 --- a/response_writer.go +++ b/response_writer.go @@ -17,7 +17,7 @@ const ( defaultStatus = http.StatusOK ) -var errHijackAlreadyWritten = errors.New("gin: response already written") +var errHijackAlreadyWritten = errors.New("gin: response body already written") // ResponseWriter ... type ResponseWriter interface { @@ -109,7 +109,9 @@ func (w *responseWriter) Written() bool { // Hijack implements the http.Hijacker interface. func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - if w.Written() { + // Allow hijacking before any data is written (size == -1) or after headers are written (size == 0), + // but not after body data is written (size > 0). For compatibility with websocket libraries (e.g., github.com/coder/websocket) + if w.size > 0 { return nil, nil, errHijackAlreadyWritten } if w.size < 0 { diff --git a/response_writer_test.go b/response_writer_test.go index ef198418..dfc1d2c6 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -194,6 +194,64 @@ func TestResponseWriterHijackAfterWrite(t *testing.T) { } } +// Test: WebSocket compatibility - allow hijack after WriteHeaderNow(), but block after body data. +func TestResponseWriterHijackAfterWriteHeaderNow(t *testing.T) { + tests := []struct { + name string + action func(w ResponseWriter) error + expectWrittenBeforeHijack bool + expectHijackSuccess bool + expectWrittenAfterHijack bool + expectError error + }{ + { + name: "hijack after WriteHeaderNow only should succeed (websocket pattern)", + action: func(w ResponseWriter) error { + w.WriteHeaderNow() // Simulate websocket.Accept() behavior + return nil + }, + expectWrittenBeforeHijack: true, + expectHijackSuccess: true, // NEW BEHAVIOR: allow hijack after just header write + expectWrittenAfterHijack: true, + expectError: nil, + }, + { + name: "hijack after WriteHeaderNow + Write should fail", + action: func(w ResponseWriter) error { + w.WriteHeaderNow() + _, err := w.Write([]byte("test")) + return err + }, + expectWrittenBeforeHijack: true, + expectHijackSuccess: false, + expectWrittenAfterHijack: true, + expectError: errHijackAlreadyWritten, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + hijacker := &mockHijacker{ResponseRecorder: httptest.NewRecorder()} + writer := &responseWriter{} + writer.reset(hijacker) + w := ResponseWriter(writer) + + require.NoError(t, tc.action(w), "unexpected error during pre-hijack action") + + assert.Equal(t, tc.expectWrittenBeforeHijack, w.Written(), "unexpected w.Written() state before hijack") + + _, _, hijackErr := w.Hijack() + + if tc.expectError == nil { + require.NoError(t, hijackErr, "expected hijack to succeed") + } else { + require.ErrorIs(t, hijackErr, tc.expectError, "unexpected error from Hijack()") + } + assert.Equal(t, tc.expectHijackSuccess, hijacker.hijacked, "unexpected hijacker.hijacked state") + assert.Equal(t, tc.expectWrittenAfterHijack, w.Written(), "unexpected w.Written() state after hijack") + }) + } +} + func TestResponseWriterFlush(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writer := &responseWriter{} From ed150e72544949a96e7eb0b7d1151cf1907068fb Mon Sep 17 00:00:00 2001 From: Meng Xun <30499307+mengxunQAQ@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:15:35 +0800 Subject: [PATCH 24/67] test(benchmarks): fix the incorrect function name (#4375) Signed-off-by: mengxun --- benchmarks_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks_test.go b/benchmarks_test.go index 3a8d53f3..ca504ecb 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -87,7 +87,7 @@ func BenchmarkOneRouteString(B *testing.B) { runRequest(B, router, http.MethodGet, "/text") } -func BenchmarkManyRoutesFist(B *testing.B) { +func BenchmarkManyRoutesFirst(B *testing.B) { router := New() router.Any("/ping", func(c *Context) {}) runRequest(B, router, http.MethodGet, "/ping") From 39858a0859c914bd26948fa950477e11bd8d3823 Mon Sep 17 00:00:00 2001 From: russcoss Date: Fri, 26 Sep 2025 23:03:59 -0400 Subject: [PATCH 25/67] refactor(binding): use maps.Copy for cleaner map handling (#4352) Signed-off-by: russcoss --- binding/form_mapping.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 45a39e15..9cf56527 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,6 +7,7 @@ package binding import ( "errors" "fmt" + "maps" "mime/multipart" "reflect" "strconv" @@ -489,9 +490,7 @@ func setFormMap(ptr any, form map[string][]string) error { if !ok { return ErrConvertMapStringSlice } - for k, v := range form { - ptrMap[k] = v - } + maps.Copy(ptrMap, form) return nil } From 8ca975441f077e30c5dac73e51bad99ad00f0d4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:25:35 +0800 Subject: [PATCH 26/67] chore(deps): bump google.golang.org/protobuf from 1.36.9 to 1.36.10 (#4383) Bumps google.golang.org/protobuf from 1.36.9 to 1.36.10. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7eeca3ef..e673de56 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 - google.golang.org/protobuf v1.36.9 + google.golang.org/protobuf v1.36.10 ) require ( diff --git a/go.sum b/go.sum index 97bab5dd..f3de6b21 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 731374fb3682af61cb153dabeec89aae401f1ede Mon Sep 17 00:00:00 2001 From: goldlinker Date: Fri, 3 Oct 2025 21:26:47 +0800 Subject: [PATCH 27/67] docs(context): fix wrong function name in comment (#4382) Signed-off-by: goldlinker --- context_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context_test.go b/context_test.go index fbc13879..08ffc3f6 100644 --- a/context_test.go +++ b/context_test.go @@ -1233,7 +1233,7 @@ func TestContextRenderNoContentHTML(t *testing.T) { assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } -// TestContextXML tests that the response is serialized as XML +// TestContextRenderXML tests that the response is serialized as XML // and Content-Type is set to application/xml func TestContextRenderXML(t *testing.T) { w := httptest.NewRecorder() From 4dec17afdff48e8018c83618fbbe69fceeb2b41d Mon Sep 17 00:00:00 2001 From: ljz <641390597@qq.com> Date: Sun, 5 Oct 2025 11:23:57 +0800 Subject: [PATCH 28/67] feat(logger): color latency (#4146) Co-authored-by: lizhao --- logger.go | 38 +++++++++++++++++++++++++++++++++----- logger_test.go | 25 +++++++++++++++++++++---- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/logger.go b/logger.go index 47827787..6441f7ea 100644 --- a/logger.go +++ b/logger.go @@ -103,6 +103,27 @@ func (p *LogFormatterParams) StatusCodeColor() string { } } +// LatencyColor is the ANSI color for latency +func (p *LogFormatterParams) LatencyColor() string { + latency := p.Latency + switch { + case latency < time.Millisecond*100: + return white + case latency < time.Millisecond*200: + return green + case latency < time.Millisecond*300: + return cyan + case latency < time.Millisecond*500: + return blue + case latency < time.Second: + return yellow + case latency < time.Second*2: + return magenta + default: + return red + } +} + // MethodColor is the ANSI color for appropriately logging http method to a terminal. func (p *LogFormatterParams) MethodColor() string { method := p.Method @@ -139,20 +160,27 @@ func (p *LogFormatterParams) IsOutputColor() bool { // defaultLogFormatter is the default log format function Logger middleware uses. var defaultLogFormatter = func(param LogFormatterParams) string { - var statusColor, methodColor, resetColor string + var statusColor, methodColor, resetColor, latencyColor string if param.IsOutputColor() { statusColor = param.StatusCodeColor() methodColor = param.MethodColor() resetColor = param.ResetColor() + latencyColor = param.LatencyColor() } - if param.Latency > time.Minute { - param.Latency = param.Latency.Truncate(time.Second) + switch { + case param.Latency > time.Minute: + param.Latency = param.Latency.Truncate(time.Second * 10) + case param.Latency > time.Second: + param.Latency = param.Latency.Truncate(time.Millisecond * 10) + case param.Latency > time.Millisecond: + param.Latency = param.Latency.Truncate(time.Microsecond * 10) } - return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s", + + return fmt.Sprintf("[GIN] %v |%s %3d %s|%s %8v %s| %15s |%s %-7s %s %#v\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), statusColor, param.StatusCode, resetColor, - param.Latency, + latencyColor, param.Latency, resetColor, param.ClientIP, methodColor, param.Method, resetColor, param.Path, diff --git a/logger_test.go b/logger_test.go index 8a542e97..335b0e31 100644 --- a/logger_test.go +++ b/logger_test.go @@ -277,11 +277,11 @@ func TestDefaultLogFormatter(t *testing.T) { isTerm: false, } - assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseParam)) - assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m3s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseLongDurationParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m0s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseLongDurationParam)) - 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)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|\x1b[97;41m 5s \x1b[0m| 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|\x1b[97;41m 2743h29m0s \x1b[0m| 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam)) } func TestColorForMethod(t *testing.T) { @@ -317,6 +317,23 @@ func TestColorForStatus(t *testing.T) { assert.Equal(t, red, colorForStatus(2), "other things should be red") } +func TestColorForLatency(t *testing.T) { + colorForLantency := func(latency time.Duration) string { + p := LogFormatterParams{ + Latency: latency, + } + return p.LatencyColor() + } + + assert.Equal(t, white, colorForLantency(time.Duration(0)), "0 should be white") + assert.Equal(t, white, colorForLantency(time.Millisecond*20), "20ms should be white") + assert.Equal(t, green, colorForLantency(time.Millisecond*150), "150ms should be green") + assert.Equal(t, cyan, colorForLantency(time.Millisecond*250), "250ms should be cyan") + assert.Equal(t, yellow, colorForLantency(time.Millisecond*600), "600ms should be yellow") + assert.Equal(t, magenta, colorForLantency(time.Millisecond*1500), "1.5s should be magenta") + assert.Equal(t, red, colorForLantency(time.Second*3), "other things should be red") +} + func TestResetColor(t *testing.T) { p := LogFormatterParams{} assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor()) From 0bd10a84f9d49ded6dd043d108b78c6b08e86cdf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:12:52 +0800 Subject: [PATCH 29/67] chore(deps): bump github/codeql-action from 3 to 4 in the actions group (#4387) Bumps the actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3 to 4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bd4c52c2..9ec3700e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -46,4 +46,4 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 From 48a5dca087085e64fbfe757a4cf9de1c3b583dda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:13:29 +0800 Subject: [PATCH 30/67] chore(deps): bump github.com/go-playground/validator/v10 (#4385) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.27.0 to 10.28.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.27.0...v10.28.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-version: 10.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 20 ++++++++++---------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index e673de56..b1d1e9e5 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/gin-gonic/gin -go 1.23.0 +go 1.24.0 require ( github.com/bytedance/sonic v1.14.0 github.com/gin-contrib/sse v1.1.0 - github.com/go-playground/validator/v10 v10.27.0 + github.com/go-playground/validator/v10 v10.28.0 github.com/goccy/go-json v0.10.2 github.com/goccy/go-yaml v1.18.0 github.com/json-iterator/go v1.1.12 @@ -15,7 +15,7 @@ require ( github.com/quic-go/quic-go v0.54.1 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 - golang.org/x/net v0.42.0 + golang.org/x/net v0.43.0 google.golang.org/protobuf v1.36.10 ) @@ -23,7 +23,7 @@ require ( github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gabriel-vasile/mimetype v1.4.10 // 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.3.0 // indirect @@ -34,11 +34,11 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/mod v0.25.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.27.0 // indirect - golang.org/x/tools v0.34.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/tools v0.36.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f3de6b21..5c8ce990 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gE github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= +github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -17,8 +17,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o 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.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= -github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= +github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= @@ -63,21 +63,21 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 5dd833f1f26de0eb30eae47b17e05ced2482dc41 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 8 Oct 2025 08:30:45 +0800 Subject: [PATCH 31/67] chore: bump minimum Go version to 1.24 and update workflows (#4388) - Update minimum required Go version from 1.23 to 1.24 throughout documentation, warnings, and tests - Remove Go 1.23 from the GitHub Actions workflow matrix - Change single quotes to double quotes for consistency in workflow configuration Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 10 +++++----- README.md | 2 +- context_test.go | 8 ++++---- debug.go | 2 +- debug_test.go | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index e0210214..f61c6486 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.23", "1.24", "1.25"] + go: ["1.24", "1.25"] test-tags: [ "", @@ -92,8 +92,8 @@ jobs: - name: Run Trivy vulnerability scanner in repo mode uses: aquasecurity/trivy-action@0.33.1 with: - scan-type: 'fs' + scan-type: "fs" ignore-unfixed: true - format: 'table' - exit-code: '1' - severity: 'CRITICAL,HIGH,MEDIUM' + format: "table" + exit-code: "1" + severity: "CRITICAL,HIGH,MEDIUM" diff --git a/README.md b/README.md index 8343a55b..629cb98d 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Gin combines the simplicity of Express.js-style routing with Go's performance ch ### Prerequisites -- **Go version**: Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above +- **Go version**: Gin requires [Go](https://go.dev/) version [1.24](https://go.dev/doc/devel/release#go1.24.0) or above - **Basic Go knowledge**: Familiarity with Go syntax and package management is helpful ### Installation diff --git a/context_test.go b/context_test.go index 08ffc3f6..cc066ef8 100644 --- a/context_test.go +++ b/context_test.go @@ -3320,7 +3320,7 @@ func TestContextSetCookieData(t *testing.T) { assert.Contains(t, setCookie, "Max-Age=1") assert.Contains(t, setCookie, "HttpOnly") assert.Contains(t, setCookie, "Secure") - // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // SameSite=Lax might be omitted in Go 1.24+ as it's the default // assert.Contains(t, setCookie, "SameSite=Lax") // Test that when Path is empty, "/" is automatically set @@ -3341,7 +3341,7 @@ func TestContextSetCookieData(t *testing.T) { assert.Contains(t, setCookie, "Max-Age=1") assert.Contains(t, setCookie, "HttpOnly") assert.Contains(t, setCookie, "Secure") - // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // SameSite=Lax might be omitted in Go 1.24+ as it's the default // assert.Contains(t, setCookie, "SameSite=Lax") // Test additional cookie attributes (Expires) @@ -3364,7 +3364,7 @@ func TestContextSetCookieData(t *testing.T) { assert.Contains(t, setCookie, "Domain=localhost") assert.Contains(t, setCookie, "HttpOnly") assert.Contains(t, setCookie, "Secure") - // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // SameSite=Lax might be omitted in Go 1.24+ as it's the default // assert.Contains(t, setCookie, "SameSite=Lax") // Test for Partitioned attribute (Go 1.18+) @@ -3384,7 +3384,7 @@ func TestContextSetCookieData(t *testing.T) { assert.Contains(t, setCookie, "Domain=localhost") assert.Contains(t, setCookie, "HttpOnly") assert.Contains(t, setCookie, "Secure") - // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // SameSite=Lax might be omitted in Go 1.24+ as it's the default // assert.Contains(t, setCookie, "SameSite=Lax") // Not testing for Partitioned attribute as it may not be supported in all Go versions diff --git a/debug.go b/debug.go index 7fe2762e..f22dfd87 100644 --- a/debug.go +++ b/debug.go @@ -78,7 +78,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.23+. + debugPrint(`[WARNING] Now Gin requires Go 1.24+. `) } diff --git a/debug_test.go b/debug_test.go index 59b61beb..e9d8fe01 100644 --- a/debug_test.go +++ b/debug_test.go @@ -106,7 +106,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.23+.\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.24+.\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) } From 0d085ed9fe2053e3c35971aa1870ebfebf90b2ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:32:58 +0800 Subject: [PATCH 32/67] chore(deps): bump golang.org/x/net from 0.43.0 to 0.46.0 (#4391) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.43.0 to 0.46.0. - [Commits](https://github.com/golang/net/compare/v0.43.0...v0.46.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.46.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index b1d1e9e5..3701deac 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/quic-go/quic-go v0.54.1 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 - golang.org/x/net v0.43.0 + golang.org/x/net v0.46.0 google.golang.org/protobuf v1.36.10 ) @@ -34,11 +34,11 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/mod v0.27.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.28.0 // indirect golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.37.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5c8ce990..aff5f21b 100644 --- a/go.sum +++ b/go.sum @@ -63,21 +63,21 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 053e5765fd6b31093928a2208e2a09ec1049653b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:33:36 +0800 Subject: [PATCH 33/67] chore(deps): bump github.com/quic-go/quic-go from 0.54.1 to 0.55.0 (#4384) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.54.1 to 0.55.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.54.1...v0.55.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.55.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 3 +-- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 3701deac..961916f0 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.54.1 + github.com/quic-go/quic-go v0.55.0 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.46.0 @@ -32,7 +32,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/mod v0.28.0 // indirect diff --git a/go.sum b/go.sum index aff5f21b..2dfb4d75 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg= -github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= +github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= 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= @@ -59,8 +59,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= From 9968c4bf9d5a99edc3eee2c068a4c9160ece8915 Mon Sep 17 00:00:00 2001 From: reddaisyy Date: Thu, 9 Oct 2025 11:36:56 +0800 Subject: [PATCH 34/67] refactor: use b.Loop() to simplify the code and improve performance (#4389) Signed-off-by: reddaisyy --- internal/bytesconv/bytesconv_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go index ff26e35e..4972ae70 100644 --- a/internal/bytesconv/bytesconv_test.go +++ b/internal/bytesconv/bytesconv_test.go @@ -81,25 +81,25 @@ func TestStringToBytes(t *testing.T) { // go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true func BenchmarkBytesConvBytesToStrRaw(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { rawBytesToStr(testBytes) } } func BenchmarkBytesConvBytesToStr(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { BytesToString(testBytes) } } func BenchmarkBytesConvStrToBytesRaw(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { rawStrToBytes(testString) } } func BenchmarkBytesConvStrToBytes(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { StringToBytes(testString) } } From c3d1092b3b48addf6f9cd00fe274ec3bd14650eb Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sat, 11 Oct 2025 19:20:41 +0800 Subject: [PATCH 35/67] fix(binding): improve empty slice/array handling in form binding (#4380) Co-authored-by: huangzw --- binding/form_mapping.go | 14 +++++-- binding/form_mapping_test.go | 80 ++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 9cf56527..1244b522 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -231,9 +231,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ switch value.Kind() { case reflect.Slice: - if !ok { - vs = []string{opt.defaultValue} + if len(vs) == 0 { + if !opt.isDefaultExists { + return false, nil + } + vs = []string{opt.defaultValue} // pre-process the default value for multi if present cfTag := field.Tag.Get("collection_format") if cfTag == "" || cfTag == "multi" { @@ -251,9 +254,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ return true, setSlice(vs, value, field) case reflect.Array: - if !ok { - vs = []string{opt.defaultValue} + if len(vs) == 0 { + if !opt.isDefaultExists { + return false, nil + } + vs = []string{opt.defaultValue} // pre-process the default value for multi if present cfTag := field.Tag.Get("collection_format") if cfTag == "" || cfTag == "multi" { diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 55b967a3..006eddf1 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -635,3 +635,83 @@ func TestMappingCustomArrayForm(t *testing.T) { expected, _ := convertTo(val) assert.Equal(t, expected, s.FileData) } + +func TestMappingEmptyValues(t *testing.T) { + t.Run("slice with default", func(t *testing.T) { + var s struct { + Slice []int `form:"slice,default=5"` + } + + // field not present + err := mappingByPtr(&s, formSource{}, "form") + require.NoError(t, err) + assert.Equal(t, []int{5}, s.Slice) + + // field present but empty + err = mappingByPtr(&s, formSource{"slice": {}}, "form") + require.NoError(t, err) + assert.Equal(t, []int{5}, s.Slice) + + // field present with values + err = mappingByPtr(&s, formSource{"slice": {"1", "2", "3"}}, "form") + require.NoError(t, err) + assert.Equal(t, []int{1, 2, 3}, s.Slice) + }) + + t.Run("array with default", func(t *testing.T) { + var s struct { + Array [1]int `form:"array,default=5"` + } + + // field not present + err := mappingByPtr(&s, formSource{}, "form") + require.NoError(t, err) + assert.Equal(t, [1]int{5}, s.Array) + + // field present but empty + err = mappingByPtr(&s, formSource{"array": {}}, "form") + require.NoError(t, err) + assert.Equal(t, [1]int{5}, s.Array) + }) + + t.Run("slice without default", func(t *testing.T) { + var s struct { + Slice []int `form:"slice"` + } + + // field present but empty + err := mappingByPtr(&s, formSource{"slice": {}}, "form") + require.NoError(t, err) + assert.Equal(t, []int(nil), s.Slice) + }) + + t.Run("array without default", func(t *testing.T) { + var s struct { + Array [1]int `form:"array"` + } + + // field present but empty + err := mappingByPtr(&s, formSource{"array": {}}, "form") + require.NoError(t, err) + assert.Equal(t, [1]int{0}, s.Array) + }) + + t.Run("slice with collection format", func(t *testing.T) { + var s struct { + SliceMulti []int `form:"slice_multi,default=1;2;3" collection_format:"multi"` + SliceCsv []int `form:"slice_csv,default=1;2;3" collection_format:"csv"` + } + + // field not present + err := mappingByPtr(&s, formSource{}, "form") + require.NoError(t, err) + assert.Equal(t, []int{1, 2, 3}, s.SliceMulti) + assert.Equal(t, []int{1, 2, 3}, s.SliceCsv) + + // field present but empty + err = mappingByPtr(&s, formSource{"slice_multi": {}, "slice_csv": {}}, "form") + require.NoError(t, err) + assert.Equal(t, []int{1, 2, 3}, s.SliceMulti) + assert.Equal(t, []int{1, 2, 3}, s.SliceCsv) + }) +} From c221133ee80c46e3a6c50717ca6f1b41d4ab7711 Mon Sep 17 00:00:00 2001 From: letreturn Date: Tue, 14 Oct 2025 22:37:07 +0800 Subject: [PATCH 36/67] docs(context): fix some comments (#4396) Signed-off-by: letreturn --- context.go | 2 +- logger_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index d7280c5d..8e39875d 100644 --- a/context.go +++ b/context.go @@ -270,7 +270,7 @@ 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. +// It also lazy initializes c.Keys if it was not used previously. func (c *Context) Set(key any, value any) { c.mu.Lock() defer c.mu.Unlock() diff --git a/logger_test.go b/logger_test.go index 335b0e31..53d0df95 100644 --- a/logger_test.go +++ b/logger_test.go @@ -39,7 +39,7 @@ func TestLogger(t *testing.T) { // I wrote these first (extending the above) but then realized they are more // like integration tests because they test the whole logging process rather - // than individual functions. Im not sure where these should go. + // than individual functions. I'm not sure where these should go. buffer.Reset() PerformRequest(router, http.MethodPost, "/example") assert.Contains(t, buffer.String(), "200") @@ -103,7 +103,7 @@ func TestLoggerWithConfig(t *testing.T) { // I wrote these first (extending the above) but then realized they are more // like integration tests because they test the whole logging process rather - // than individual functions. Im not sure where these should go. + // than individual functions. I'm not sure where these should go. buffer.Reset() PerformRequest(router, http.MethodPost, "/example") assert.Contains(t, buffer.String(), "200") From 38e765119241d990705169bedb5002a29ae0cbd1 Mon Sep 17 00:00:00 2001 From: Spyder01 <45194214+Spyder01@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:51:34 +0530 Subject: [PATCH 37/67] feat(context): implemented Delete method Co-authored-by: suhan --- context.go | 10 ++++++++++ context_test.go | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/context.go b/context.go index 8e39875d..c2309d31 100644 --- a/context.go +++ b/context.go @@ -465,6 +465,16 @@ func (c *Context) GetStringMapStringSlice(key any) (smss map[string][]string) { return getTyped[map[string][]string](c, key) } +// Delete deletes the key from the Context's Key map, if it exists. +// This operation is safe to be used by concurrent go-routines +func (c *Context) Delete(key any) { + c.mu.Lock() + defer c.mu.Unlock() + if c.Keys != nil { + delete(c.Keys, key) + } +} + /************************************/ /************ INPUT DATA ************/ /************************************/ diff --git a/context_test.go b/context_test.go index cc066ef8..e6b7519e 100644 --- a/context_test.go +++ b/context_test.go @@ -404,6 +404,19 @@ func TestContextSetGetBool(t *testing.T) { assert.True(t, c.GetBool("bool")) } +func TestSetGetDelete(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "example-key" + value := "example-value" + c.Set(key, value) + val, exists := c.Get(key) + assert.True(t, exists) + assert.Equal(t, val, value) + c.Delete(key) + _, exists = c.Get(key) + assert.False(t, exists) +} + func TestContextGetInt(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("int", 1) From c0048f645ee945c4db30593afdea10123e2c30a6 Mon Sep 17 00:00:00 2001 From: wanghaolong613 Date: Fri, 17 Oct 2025 11:39:49 +0800 Subject: [PATCH 38/67] refactor(context): omit the return value names (#4395) --- context.go | 64 +++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/context.go b/context.go index c2309d31..e64c7953 100644 --- a/context.go +++ b/context.go @@ -306,162 +306,162 @@ func getTyped[T any](c *Context, key any) (res T) { } // GetString returns the value associated with the key as a string. -func (c *Context) GetString(key any) (s string) { +func (c *Context) GetString(key any) string { return getTyped[string](c, key) } // GetBool returns the value associated with the key as a boolean. -func (c *Context) GetBool(key any) (b bool) { +func (c *Context) GetBool(key any) bool { return getTyped[bool](c, key) } // GetInt returns the value associated with the key as an integer. -func (c *Context) GetInt(key any) (i int) { +func (c *Context) GetInt(key any) int { return getTyped[int](c, key) } // GetInt8 returns the value associated with the key as an integer 8. -func (c *Context) GetInt8(key any) (i8 int8) { +func (c *Context) GetInt8(key any) int8 { return getTyped[int8](c, key) } // GetInt16 returns the value associated with the key as an integer 16. -func (c *Context) GetInt16(key any) (i16 int16) { +func (c *Context) GetInt16(key any) int16 { return getTyped[int16](c, key) } // GetInt32 returns the value associated with the key as an integer 32. -func (c *Context) GetInt32(key any) (i32 int32) { +func (c *Context) GetInt32(key any) int32 { return getTyped[int32](c, key) } // GetInt64 returns the value associated with the key as an integer 64. -func (c *Context) GetInt64(key any) (i64 int64) { +func (c *Context) GetInt64(key any) int64 { return getTyped[int64](c, key) } // GetUint returns the value associated with the key as an unsigned integer. -func (c *Context) GetUint(key any) (ui uint) { +func (c *Context) GetUint(key any) uint { return getTyped[uint](c, key) } // GetUint8 returns the value associated with the key as an unsigned integer 8. -func (c *Context) GetUint8(key any) (ui8 uint8) { +func (c *Context) GetUint8(key any) uint8 { return getTyped[uint8](c, key) } // GetUint16 returns the value associated with the key as an unsigned integer 16. -func (c *Context) GetUint16(key any) (ui16 uint16) { +func (c *Context) GetUint16(key any) uint16 { return getTyped[uint16](c, key) } // GetUint32 returns the value associated with the key as an unsigned integer 32. -func (c *Context) GetUint32(key any) (ui32 uint32) { +func (c *Context) GetUint32(key any) uint32 { return getTyped[uint32](c, key) } // GetUint64 returns the value associated with the key as an unsigned integer 64. -func (c *Context) GetUint64(key any) (ui64 uint64) { +func (c *Context) GetUint64(key any) uint64 { return getTyped[uint64](c, key) } // GetFloat32 returns the value associated with the key as a float32. -func (c *Context) GetFloat32(key any) (f32 float32) { +func (c *Context) GetFloat32(key any) float32 { return getTyped[float32](c, key) } // GetFloat64 returns the value associated with the key as a float64. -func (c *Context) GetFloat64(key any) (f64 float64) { +func (c *Context) GetFloat64(key any) float64 { return getTyped[float64](c, key) } // GetTime returns the value associated with the key as time. -func (c *Context) GetTime(key any) (t time.Time) { +func (c *Context) GetTime(key any) time.Time { return getTyped[time.Time](c, key) } // GetDuration returns the value associated with the key as a duration. -func (c *Context) GetDuration(key any) (d time.Duration) { +func (c *Context) GetDuration(key any) time.Duration { return getTyped[time.Duration](c, key) } // GetIntSlice returns the value associated with the key as a slice of integers. -func (c *Context) GetIntSlice(key any) (is []int) { +func (c *Context) GetIntSlice(key any) []int { return getTyped[[]int](c, key) } // GetInt8Slice returns the value associated with the key as a slice of int8 integers. -func (c *Context) GetInt8Slice(key any) (i8s []int8) { +func (c *Context) GetInt8Slice(key any) []int8 { return getTyped[[]int8](c, key) } // GetInt16Slice returns the value associated with the key as a slice of int16 integers. -func (c *Context) GetInt16Slice(key any) (i16s []int16) { +func (c *Context) GetInt16Slice(key any) []int16 { return getTyped[[]int16](c, key) } // GetInt32Slice returns the value associated with the key as a slice of int32 integers. -func (c *Context) GetInt32Slice(key any) (i32s []int32) { +func (c *Context) GetInt32Slice(key any) []int32 { return getTyped[[]int32](c, key) } // GetInt64Slice returns the value associated with the key as a slice of int64 integers. -func (c *Context) GetInt64Slice(key any) (i64s []int64) { +func (c *Context) GetInt64Slice(key any) []int64 { return getTyped[[]int64](c, key) } // GetUintSlice returns the value associated with the key as a slice of unsigned integers. -func (c *Context) GetUintSlice(key any) (uis []uint) { +func (c *Context) GetUintSlice(key any) []uint { return getTyped[[]uint](c, key) } // GetUint8Slice returns the value associated with the key as a slice of uint8 integers. -func (c *Context) GetUint8Slice(key any) (ui8s []uint8) { +func (c *Context) GetUint8Slice(key any) []uint8 { return getTyped[[]uint8](c, key) } // GetUint16Slice returns the value associated with the key as a slice of uint16 integers. -func (c *Context) GetUint16Slice(key any) (ui16s []uint16) { +func (c *Context) GetUint16Slice(key any) []uint16 { return getTyped[[]uint16](c, key) } // GetUint32Slice returns the value associated with the key as a slice of uint32 integers. -func (c *Context) GetUint32Slice(key any) (ui32s []uint32) { +func (c *Context) GetUint32Slice(key any) []uint32 { return getTyped[[]uint32](c, key) } // GetUint64Slice returns the value associated with the key as a slice of uint64 integers. -func (c *Context) GetUint64Slice(key any) (ui64s []uint64) { +func (c *Context) GetUint64Slice(key any) []uint64 { return getTyped[[]uint64](c, key) } // GetFloat32Slice returns the value associated with the key as a slice of float32 numbers. -func (c *Context) GetFloat32Slice(key any) (f32s []float32) { +func (c *Context) GetFloat32Slice(key any) []float32 { return getTyped[[]float32](c, key) } // GetFloat64Slice returns the value associated with the key as a slice of float64 numbers. -func (c *Context) GetFloat64Slice(key any) (f64s []float64) { +func (c *Context) GetFloat64Slice(key any) []float64 { return getTyped[[]float64](c, key) } // GetStringSlice returns the value associated with the key as a slice of strings. -func (c *Context) GetStringSlice(key any) (ss []string) { +func (c *Context) GetStringSlice(key any) []string { return getTyped[[]string](c, key) } // GetStringMap returns the value associated with the key as a map of interfaces. -func (c *Context) GetStringMap(key any) (sm map[string]any) { +func (c *Context) GetStringMap(key any) map[string]any { return getTyped[map[string]any](c, key) } // GetStringMapString returns the value associated with the key as a map of strings. -func (c *Context) GetStringMapString(key any) (sms map[string]string) { +func (c *Context) GetStringMapString(key any) map[string]string { return getTyped[map[string]string](c, key) } // GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. -func (c *Context) GetStringMapStringSlice(key any) (smss map[string][]string) { +func (c *Context) GetStringMapStringSlice(key any) map[string][]string { return getTyped[map[string][]string](c, key) } From 87c207a14093666fae281e9ebabe3ce6dd0b5ecd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:02:56 +0800 Subject: [PATCH 39/67] chore(deps): bump github.com/bytedance/sonic from 1.14.0 to 1.14.2 (#4410) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.14.0 to 1.14.2. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.14.0...v1.14.2) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-version: 1.14.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 5 +++-- go.sum | 14 +++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 961916f0..e3a9ed25 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.24.0 require ( - github.com/bytedance/sonic v1.14.0 + github.com/bytedance/sonic v1.14.2 github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.28.0 github.com/goccy/go-json v0.10.2 @@ -20,7 +20,8 @@ require ( ) require ( - github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect diff --git a/go.sum b/go.sum index 2dfb4d75..31702f3a 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ -github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= -github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= -github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= -github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= +github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= +github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= +github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -49,10 +51,12 @@ github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= From 52f70cf18a61939ab25696fa335ecb8934512fb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:03:29 +0800 Subject: [PATCH 40/67] chore(deps): bump github.com/ugorji/go/codec from 1.3.0 to 1.3.1 (#4409) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.3.0 to 1.3.1. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/codec/v1.3.0...codec/v1.3.1) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec dependency-version: 1.3.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e3a9ed25..beabc954 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 github.com/quic-go/quic-go v0.55.0 github.com/stretchr/testify v1.11.1 - github.com/ugorji/go/codec v1.3.0 + github.com/ugorji/go/codec v1.3.1 golang.org/x/net v0.46.0 google.golang.org/protobuf v1.36.10 ) diff --git a/go.sum b/go.sum index 31702f3a..ed1361a9 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= -github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= From 2e22e5085960205fbb11c25776f6ea76b8053253 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 31 Oct 2025 22:09:07 +0800 Subject: [PATCH 41/67] perf(tree): optimize path parsing using strings.Count (#4246) Co-authored-by: 1911860538 --- tree.go | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/tree.go b/tree.go index 78479b6f..bcc83502 100644 --- a/tree.go +++ b/tree.go @@ -5,7 +5,6 @@ package gin import ( - "bytes" "net/url" "strings" "unicode" @@ -14,12 +13,6 @@ import ( "github.com/gin-gonic/gin/internal/bytesconv" ) -var ( - strColon = []byte(":") - strStar = []byte("*") - strSlash = []byte("/") -) - // Param is a single URL parameter, consisting of a key and a value. type Param struct { Key string @@ -85,16 +78,13 @@ func (n *node) addChild(child *node) { } func countParams(path string) uint16 { - var n uint16 - s := bytesconv.StringToBytes(path) - n += uint16(bytes.Count(s, strColon)) - n += uint16(bytes.Count(s, strStar)) - return n + colons := strings.Count(path, ":") + stars := strings.Count(path, "*") + return uint16(colons + stars) } func countSections(path string) uint16 { - s := bytesconv.StringToBytes(path) - return uint16(bytes.Count(s, strSlash)) + return uint16(strings.Count(path, "/")) } type nodeType uint8 From 5e5ff3ace496a31b138b0820136a146bfb5de0ef Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 6 Nov 2025 14:15:50 +0800 Subject: [PATCH 42/67] ci: replace vulnerability scanning workflow with Trivy integration (#4421) - Remove the vulnerability-scanning job from the gin workflow - Add a dedicated Trivy security scan workflow with scheduled, push, pull request, and manual triggers - Improve Trivy scan output by uploading SARIF results to the GitHub Security tab and logging table output Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 16 --------- .github/workflows/trivy-scan.yml | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/trivy-scan.yml diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index f61c6486..eb0d7c26 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -81,19 +81,3 @@ jobs: uses: codecov/codecov-action@v5 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - - vulnerability-scanning: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Run Trivy vulnerability scanner in repo mode - uses: aquasecurity/trivy-action@0.33.1 - with: - scan-type: "fs" - ignore-unfixed: true - format: "table" - exit-code: "1" - severity: "CRITICAL,HIGH,MEDIUM" diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml new file mode 100644 index 00000000..c2e29f07 --- /dev/null +++ b/.github/workflows/trivy-scan.yml @@ -0,0 +1,57 @@ +name: Trivy Security Scan + +on: + push: + branches: + - master + pull_request: + branches: + - master + schedule: + # Run every 3 months (quarterly) on the 1st day at 00:00 UTC + # Months: January (1), April (4), July (7), October (10) + - cron: '0 0 1 1,4,7,10 *' + workflow_dispatch: # Allow manual trigger + +permissions: + contents: read + security-events: write # Required for uploading SARIF results + +jobs: + trivy-scan: + name: Trivy Security Scan + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Run Trivy vulnerability scanner (source code) + uses: aquasecurity/trivy-action@0.33.1 + with: + scan-type: 'fs' + scan-ref: '.' + scanners: 'vuln,secret,misconfig' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH,MEDIUM' + ignore-unfixed: true + + - name: Upload Trivy results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + - name: Run Trivy scanner (table output for logs) + uses: aquasecurity/trivy-action@0.33.1 + if: always() + with: + scan-type: 'fs' + scan-ref: '.' + scanners: 'vuln,secret,misconfig' + format: 'table' + severity: 'CRITICAL,HIGH,MEDIUM' + ignore-unfixed: true + exit-code: '1' From dceb61e6e76337b388109f6c553b026d3b6ff026 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 7 Nov 2025 11:57:12 +0800 Subject: [PATCH 43/67] docs(README): add a Trivy security scan badge (#4426) - Add a Trivy security scan badge to the documentation - Import the log package in the example code - Improve error handling for server startup in the example code Signed-off-by: Bo-Yi Wu --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 629cb98d..1b9ab808 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Build Status](https://github.com/gin-gonic/gin/actions/workflows/gin.yml/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions/workflows/gin.yml) +[![Trivy Security Scan](https://github.com/gin-gonic/gin/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/gin-gonic/gin/actions/workflows/trivy-scan.yml) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![Go Reference](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) @@ -62,6 +63,7 @@ Here's a complete example that demonstrates Gin's simplicity: package main import ( + "log" "net/http" "github.com/gin-gonic/gin" @@ -70,7 +72,7 @@ import ( func main() { // Create a Gin router with default middleware (logger and recovery) r := gin.Default() - + // Define a simple GET endpoint r.GET("/ping", func(c *gin.Context) { // Return JSON response @@ -78,10 +80,12 @@ func main() { "message": "pong", }) }) - + // Start server on port 8080 (default) // Server will listen on 0.0.0.0:8080 (localhost:8080 on Windows) - r.Run() + if err := r.Run(); err != nil { + log.Fatalf("failed to run server: %v", err) + } } ``` @@ -190,7 +194,6 @@ Gin has a rich ecosystem of middleware for common web development needs. Explore - CORS, Rate limiting, Compression - Logging, Metrics, Tracing - Static file serving, Template engines - - **[gin-gonic/contrib](https://github.com/gin-gonic/contrib)** - Additional community middleware ## 🏢 Production Usage From 0c0e99d2538609d38c757b0a32f708b4dcf424c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:57:41 +0800 Subject: [PATCH 44/67] chore(deps): bump github/codeql-action from 3 to 4 in the actions group (#4425) Bumps the actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3 to 4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/trivy-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index c2e29f07..12830633 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -39,7 +39,7 @@ jobs: ignore-unfixed: true - name: Upload Trivy results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 if: always() with: sarif_file: 'trivy-results.sarif' From acc55e049e33b401e810dbd8c0d6dcb6b3ba2b05 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 7 Nov 2025 11:59:58 +0800 Subject: [PATCH 45/67] feat(context): add Protocol Buffers support to content negotiation (#4423) Co-authored-by: 1911860538 --- context.go | 22 ++++++++++++++-------- context_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/context.go b/context.go index e64c7953..d5ef8b81 100644 --- a/context.go +++ b/context.go @@ -39,6 +39,7 @@ const ( MIMEYAML = binding.MIMEYAML MIMEYAML2 = binding.MIMEYAML2 MIMETOML = binding.MIMETOML + MIMEPROTOBUF = binding.MIMEPROTOBUF ) // BodyBytesKey indicates a default body bytes key. @@ -1280,14 +1281,15 @@ func (c *Context) Stream(step func(w io.Writer) bool) bool { // Negotiate contains all negotiations data. type Negotiate struct { - Offered []string - HTMLName string - HTMLData any - JSONData any - XMLData any - YAMLData any - Data any - TOMLData any + Offered []string + HTMLName string + HTMLData any + JSONData any + XMLData any + YAMLData any + Data any + TOMLData any + PROTOBUFData any } // Negotiate calls different Render according to acceptable Accept format. @@ -1313,6 +1315,10 @@ func (c *Context) Negotiate(code int, config Negotiate) { data := chooseData(config.TOMLData, config.Data) c.TOML(code, data) + case binding.MIMEPROTOBUF: + data := chooseData(config.PROTOBUFData, config.Data) + c.ProtoBuf(code, data) + default: c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck } diff --git a/context_test.go b/context_test.go index e6b7519e..26106129 100644 --- a/context_test.go +++ b/context_test.go @@ -1628,6 +1628,32 @@ func TestContextNegotiationWithHTML(t *testing.T) { assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } +func TestContextNegotiationWithPROTOBUF(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodPost, "/", nil) + + reps := []int64{int64(1), int64(2)} + label := "test" + data := &testdata.Test{ + Label: &label, + Reps: reps, + } + + c.Negotiate(http.StatusCreated, Negotiate{ + Offered: []string{MIMEPROTOBUF, MIMEJSON, MIMEXML}, + Data: data, + }) + + // Marshal original data for comparison + protoData, err := proto.Marshal(data) + require.NoError(t, err) + + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, string(protoData), w.Body.String()) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) +} + func TestContextNegotiationNotSupport(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From c3d5a28ed6d3849da820195b6774d212bcc038a9 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 7 Nov 2025 12:01:19 +0800 Subject: [PATCH 46/67] fix(gin): close os.File in RunFd to prevent resource leak (#4422) Co-authored-by: 1911860538 --- gin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gin.go b/gin.go index 1965a429..38361a4b 100644 --- a/gin.go +++ b/gin.go @@ -593,6 +593,7 @@ func (engine *Engine) RunFd(fd int) (err error) { } f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) + defer f.Close() listener, err := net.FileListener(f) if err != nil { return From d1bcabc7ee4cbd3631c71f5a25da14bf1b84a0d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:32:23 +0800 Subject: [PATCH 47/67] chore(deps): bump golangci/golangci-lint-action in the actions group (#4431) Bumps the actions group with 1 update: [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action). Updates `golangci/golangci-lint-action` from 8 to 9 - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v8...v9) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: '9' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index eb0d7c26..d74a8bb4 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,7 +24,7 @@ jobs: with: go-version: "^1" - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v8 + uses: golangci/golangci-lint-action@v9 with: version: v2.1.6 args: --verbose From a9401cd238378d6ecaf4fe90f7c825f624bd8ea1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:32:48 +0800 Subject: [PATCH 48/67] chore(deps): bump github.com/quic-go/quic-go from 0.55.0 to 0.56.0 (#4430) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.55.0 to 0.56.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.55.0...v0.56.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.56.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 ++---- go.sum | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index beabc954..ab2fc86a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.55.0 + github.com/quic-go/quic-go v0.56.0 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 golang.org/x/net v0.46.0 @@ -28,6 +28,7 @@ require ( 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.3.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -35,10 +36,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/crypto v0.43.0 // indirect - golang.org/x/mod v0.28.0 // indirect - golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect - golang.org/x/tools v0.37.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ed1361a9..5650d5c9 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,7 @@ github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2N github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +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= @@ -32,6 +33,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -46,8 +51,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= -github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= +github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY= +github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 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= @@ -69,23 +76,20 @@ golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= From 19c2d5c0d1d096e1014fb7be62116ee9025d0f56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:42:25 +0800 Subject: [PATCH 49/67] chore(deps): bump golang.org/x/net from 0.46.0 to 0.47.0 (#4433) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.46.0 to 0.47.0. - [Commits](https://github.com/golang/net/compare/v0.46.0...v0.47.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.47.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index ab2fc86a..c756803a 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/quic-go/quic-go v0.56.0 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 - golang.org/x/net v0.46.0 + golang.org/x/net v0.47.0 google.golang.org/protobuf v1.36.10 ) @@ -35,8 +35,8 @@ require ( github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.43.0 // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/text v0.30.0 // indirect + golang.org/x/crypto v0.44.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5650d5c9..1ef1ad18 100644 --- a/go.sum +++ b/go.sum @@ -74,15 +74,15 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= From fb27ef26c2fdfe25344b4c039d8a53551f9e912c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 15 Nov 2025 19:21:42 +0800 Subject: [PATCH 50/67] ci(lint): refactor test assertions and linter configuration (#4436) - Update golangci-lint GitHub Action version from v2.1.6 to v2.6 - Remove the gci formatter and exclusions for third_party, builtin, and examples from the linter config - Fix argument order for assert.EqualValues and assert.Exactly in context tests for clarity - Refactor integration tests to build response strings using strings.Builder instead of direct concatenation for improved performance and readability Signed-off-by: appleboy --- .github/workflows/gin.yml | 2 +- .golangci.yml | 4 ---- context_test.go | 4 ++-- gin_integration_test.go | 16 ++++++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index d74a8bb4..8bca364d 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -26,7 +26,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v9 with: - version: v2.1.6 + version: v2.6 args: --verbose test: needs: lint diff --git a/.golangci.yml b/.golangci.yml index d8887062..318eb811 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -68,7 +68,6 @@ linters: - examples$ formatters: enable: - - gci - gofmt - gofumpt - goimports @@ -80,7 +79,4 @@ formatters: exclusions: generated: lax paths: - - third_party$ - - builtin$ - - examples$ - gin.go diff --git a/context_test.go b/context_test.go index 26106129..126646fc 100644 --- a/context_test.go +++ b/context_test.go @@ -292,7 +292,7 @@ func TestContextReset(t *testing.T) { assert.Empty(t, c.Errors.Errors()) assert.Empty(t, c.Errors.ByType(ErrorTypeAny)) assert.Empty(t, c.Params) - assert.EqualValues(t, c.index, -1) + assert.EqualValues(t, -1, c.index) assert.Equal(t, c.Writer.(*responseWriter), &c.writermem) } @@ -384,7 +384,7 @@ func TestContextSetGetValues(t *testing.T) { c.Set("intInterface", a) assert.Exactly(t, "this is a string", c.MustGet("string").(string)) - assert.Exactly(t, c.MustGet("int32").(int32), int32(-42)) + assert.Exactly(t, int32(-42), c.MustGet("int32").(int32)) assert.Exactly(t, int64(42424242424242), c.MustGet("int64").(int64)) assert.Exactly(t, uint64(42), c.MustGet("uint64").(uint64)) assert.InDelta(t, float32(4.2), c.MustGet("float32").(float32), 0.01) diff --git a/gin_integration_test.go b/gin_integration_test.go index c032d837..e040993a 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -16,6 +16,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "sync" "testing" "time" @@ -261,10 +262,11 @@ func TestUnixSocket(t *testing.T) { fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) - var response string + var responseBuilder strings.Builder for scanner.Scan() { - response += scanner.Text() + responseBuilder.WriteString(scanner.Text()) } + response := responseBuilder.String() assert.Contains(t, response, "HTTP/1.0 200", "should get a 200") assert.Contains(t, response, "it worked", "resp body should match") } @@ -322,10 +324,11 @@ func TestFileDescriptor(t *testing.T) { fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) - var response string + var responseBuilder strings.Builder for scanner.Scan() { - response += scanner.Text() + responseBuilder.WriteString(scanner.Text()) } + response := responseBuilder.String() assert.Contains(t, response, "HTTP/1.0 200", "should get a 200") assert.Contains(t, response, "it worked", "resp body should match") } @@ -354,10 +357,11 @@ func TestListener(t *testing.T) { fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) - var response string + var responseBuilder strings.Builder for scanner.Scan() { - response += scanner.Text() + responseBuilder.WriteString(scanner.Text()) } + response := responseBuilder.String() assert.Contains(t, response, "HTTP/1.0 200", "should get a 200") assert.Contains(t, response, "it worked", "resp body should match") } From a85ef5ce4d0cda8834c59c855068ed48b51192d1 Mon Sep 17 00:00:00 2001 From: efcking Date: Sat, 15 Nov 2025 19:22:18 +0800 Subject: [PATCH 51/67] refactor: use b.Loop() to simplify the code and improve performance (#4432) Signed-off-by: efcking --- binding/default_validator_benchmark_test.go | 3 +-- binding/form_mapping_benchmark_test.go | 4 ++-- path_test.go | 6 +++--- utils_test.go | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/binding/default_validator_benchmark_test.go b/binding/default_validator_benchmark_test.go index 44547412..a7b22696 100644 --- a/binding/default_validator_benchmark_test.go +++ b/binding/default_validator_benchmark_test.go @@ -18,9 +18,8 @@ func BenchmarkSliceValidationError(b *testing.B) { } b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { if len(e.Error()) == 0 { b.Errorf("error") } diff --git a/binding/form_mapping_benchmark_test.go b/binding/form_mapping_benchmark_test.go index 5788133f..d40699e9 100644 --- a/binding/form_mapping_benchmark_test.go +++ b/binding/form_mapping_benchmark_test.go @@ -31,7 +31,7 @@ type structFull struct { func BenchmarkMapFormFull(b *testing.B) { var s structFull - for i := 0; i < b.N; i++ { + for b.Loop() { err := mapForm(&s, form) if err != nil { b.Fatalf("Error on a form mapping") @@ -54,7 +54,7 @@ type structName struct { func BenchmarkMapFormName(b *testing.B) { var s structName - for i := 0; i < b.N; i++ { + for b.Loop() { err := mapForm(&s, form) if err != nil { b.Fatalf("Error on a form mapping") diff --git a/path_test.go b/path_test.go index 2269b78e..7d86086f 100644 --- a/path_test.go +++ b/path_test.go @@ -94,7 +94,7 @@ func TestPathCleanMallocs(t *testing.T) { func BenchmarkPathClean(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { for _, test := range cleanTests { cleanPath(test.path) } @@ -134,10 +134,10 @@ func TestPathCleanLong(t *testing.T) { func BenchmarkPathCleanLong(b *testing.B) { cleanTests := genLongPaths() - b.ResetTimer() + b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { for _, test := range cleanTests { cleanPath(test.path) } diff --git a/utils_test.go b/utils_test.go index dc9886d7..8bcf00e4 100644 --- a/utils_test.go +++ b/utils_test.go @@ -19,7 +19,7 @@ func init() { } func BenchmarkParseAccept(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8") } } From 58135f06cf206a9ff713eb14150ef04a05b031d4 Mon Sep 17 00:00:00 2001 From: AtoriUzawa <110576658+AtoriUzawa@users.noreply.github.com> Date: Sat, 15 Nov 2025 19:46:45 +0800 Subject: [PATCH 52/67] docs(context): add example comments for ShouldBind* methods (#4428) - Added detailed example for ShouldBindJSON - Added consistent descriptive comments for ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindTOML, ShouldBindPlain, ShouldBindHeader, ShouldBindUri - Makes binding method usage clearer for new users --- context.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/context.go b/context.go index d5ef8b81..059e85a8 100644 --- a/context.go +++ b/context.go @@ -830,41 +830,71 @@ func (c *Context) ShouldBind(obj any) error { } // ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON). +// +// Example: +// +// POST /user +// Content-Type: application/json +// +// Request Body: +// { +// "name": "Manu", +// "age": 20 +// } +// +// type User struct { +// Name string `json:"name"` +// Age int `json:"age"` +// } +// +// var user User +// if err := c.ShouldBindJSON(&user); err != nil { +// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) +// return +// } +// c.JSON(http.StatusOK, user) func (c *Context) ShouldBindJSON(obj any) error { return c.ShouldBindWith(obj, binding.JSON) } // ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML). +// It works like ShouldBindJSON but binds the request body as XML data. func (c *Context) ShouldBindXML(obj any) error { return c.ShouldBindWith(obj, binding.XML) } // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). +// It works like ShouldBindJSON but binds query parameters from the URL. func (c *Context) ShouldBindQuery(obj any) error { return c.ShouldBindWith(obj, binding.Query) } // ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML). +// It works like ShouldBindJSON but binds the request body as YAML data. func (c *Context) ShouldBindYAML(obj any) error { return c.ShouldBindWith(obj, binding.YAML) } // ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML). +// It works like ShouldBindJSON but binds the request body as TOML data. func (c *Context) ShouldBindTOML(obj any) error { return c.ShouldBindWith(obj, binding.TOML) } // ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain). +// It works like ShouldBindJSON but binds plain text data from the request body. func (c *Context) ShouldBindPlain(obj any) error { return c.ShouldBindWith(obj, binding.Plain) } // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). +// It works like ShouldBindJSON but binds values from HTTP headers. func (c *Context) ShouldBindHeader(obj any) error { return c.ShouldBindWith(obj, binding.Header) } // ShouldBindUri binds the passed struct pointer using the specified binding engine. +// It works like ShouldBindJSON but binds parameters from the URI. func (c *Context) ShouldBindUri(obj any) error { m := make(map[string][]string, len(c.Params)) for _, v := range c.Params { From 93ff771e6dbf10e432864b30f3719ac5c84a4d4a Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 15 Nov 2025 23:03:32 +0800 Subject: [PATCH 53/67] ci(sec): improve type safety and server organization in HTTP middleware (#4437) - Update linting configuration to exclude G115 gosec check instead of including specific checks - Add the safeInt8 helper for safer type conversions and use it to prevent int8 overflow in middleware handler execution - Group related constants and variables together for better organization in gin.go - Refactor HTTP server instantiation to use a dedicated http.Server object for all Run methods - Add the safeUint16 helper and use it to safely handle conversions in tree node functions to prevent uint16 overflow Signed-off-by: appleboy --- .golangci.yml | 11 ++--------- context.go | 10 +++++++++- gin.go | 38 ++++++++++++++++++++++++++++---------- tree.go | 13 +++++++++++-- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 318eb811..f0898565 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,15 +18,8 @@ linters: - wastedassign settings: gosec: - includes: - - G102 - - G106 - - G108 - - G109 - - G111 - - G112 - - G201 - - G203 + excludes: + - G115 perfsprint: int-conversion: true err-error: true diff --git a/context.go b/context.go index 059e85a8..112f0ee0 100644 --- a/context.go +++ b/context.go @@ -55,6 +55,14 @@ const ContextRequestKey ContextKeyType = 0 // abortIndex represents a typical value used in abort functions. const abortIndex int8 = math.MaxInt8 >> 1 +// safeInt8 converts int to int8 safely, capping at math.MaxInt8 +func safeInt8(n int) int8 { + if n > math.MaxInt8 { + return math.MaxInt8 + } + return int8(n) +} + // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. type Context struct { @@ -186,7 +194,7 @@ func (c *Context) FullPath() string { // See example in GitHub. func (c *Context) Next() { c.index++ - for c.index < int8(len(c.handlers)) { + for c.index < safeInt8(len(c.handlers)) { if c.handlers[c.index] != nil { c.handlers[c.index](c) } diff --git a/gin.go b/gin.go index 38361a4b..4d0c7ec0 100644 --- a/gin.go +++ b/gin.go @@ -23,10 +23,12 @@ import ( "golang.org/x/net/http2/h2c" ) -const defaultMultipartMemory = 32 << 20 // 32 MB -const escapedColon = "\\:" -const colon = ":" -const backslash = "\\" +const ( + defaultMultipartMemory = 32 << 20 // 32 MB + escapedColon = "\\:" + colon = ":" + backslash = "\\" +) var ( default404Body = []byte("404 page not found") @@ -46,8 +48,10 @@ var defaultTrustedCIDRs = []*net.IPNet{ }, } -var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") -var regRemoveRepeatedChar = regexp.MustCompile("/{2,}") +var ( + regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") + regRemoveRepeatedChar = regexp.MustCompile("/{2,}") +) // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) @@ -537,7 +541,11 @@ func (engine *Engine) Run(addr ...string) (err error) { engine.updateRouteTrees() address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) - err = http.ListenAndServe(address, engine.Handler()) + server := &http.Server{ // #nosec G112 + Addr: address, + Handler: engine.Handler(), + } + err = server.ListenAndServe() return } @@ -553,7 +561,11 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } - err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler()) + server := &http.Server{ // #nosec G112 + Addr: addr, + Handler: engine.Handler(), + } + err = server.ListenAndServeTLS(certFile, keyFile) return } @@ -576,7 +588,10 @@ func (engine *Engine) RunUnix(file string) (err error) { defer listener.Close() defer os.Remove(file) - err = http.Serve(listener, engine.Handler()) + server := &http.Server{ // #nosec G112 + Handler: engine.Handler(), + } + err = server.Serve(listener) return } @@ -630,7 +645,10 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) { "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } - err = http.Serve(listener, engine.Handler()) + server := &http.Server{ // #nosec G112 + Handler: engine.Handler(), + } + err = server.Serve(listener) return } diff --git a/tree.go b/tree.go index bcc83502..eff07734 100644 --- a/tree.go +++ b/tree.go @@ -5,6 +5,7 @@ package gin import ( + "math" "net/url" "strings" "unicode" @@ -77,14 +78,22 @@ func (n *node) addChild(child *node) { } } +// safeUint16 converts int to uint16 safely, capping at math.MaxUint16 +func safeUint16(n int) uint16 { + if n > math.MaxUint16 { + return math.MaxUint16 + } + return uint16(n) +} + func countParams(path string) uint16 { colons := strings.Count(path, ":") stars := strings.Count(path, "*") - return uint16(colons + stars) + return safeUint16(colons + stars) } func countSections(path string) uint16 { - return uint16(strings.Count(path, "/")) + return safeUint16(strings.Count(path, "/")) } type nodeType uint8 From 5fad976b372e381312f8de69f0969f1284d229d3 Mon Sep 17 00:00:00 2001 From: Pawan Kalyan <91543630+pawannn@users.noreply.github.com> Date: Sun, 16 Nov 2025 06:52:07 +0530 Subject: [PATCH 54/67] fix(gin): literal colon routes not working with engine.Handler() (#4415) * fix: call updateRouteTrees in ServeHTTP using sync.Once to support literal colon routes in all usage scenarios (#4413) * chore: fixed golangci-lint issue in test cases for literal colon * fix: gofumpt formatting issue * fix: gofumpt issue in gin.go * chore: updated routeTreesUpdated comments * chore: removed unused variable and updated TestUpdateRouteTreesCalledOnce testcase * chore: moved tests from literal_colon_test.go into gin_test.go --------- Co-authored-by: pawannn --- gin.go | 8 +++++ gin_test.go | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/gin.go b/gin.go index 4d0c7ec0..d71086d1 100644 --- a/gin.go +++ b/gin.go @@ -98,6 +98,10 @@ const ( type Engine struct { RouterGroup + // routeTreesUpdated ensures that the initialization or update of the route trees + // (used for routing HTTP requests) happens only once, even if called multiple times concurrently. + routeTreesUpdated sync.Once + // 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 @@ -654,6 +658,10 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) { // ServeHTTP conforms to the http.Handler interface. func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { + engine.routeTreesUpdated.Do(func() { + engine.updateRouteTrees() + }) + c := engine.pool.Get().(*Context) c.writermem.reset(w) c.Request = req diff --git a/gin_test.go b/gin_test.go index be076537..cee1f3cc 100644 --- a/gin_test.go +++ b/gin_test.go @@ -913,3 +913,102 @@ func TestMethodNotAllowedNoRoute(t *testing.T) { assert.NotPanics(t, func() { g.ServeHTTP(resp, req) }) assert.Equal(t, http.StatusNotFound, resp.Code) } + +// Test the fix for https://github.com/gin-gonic/gin/pull/4415 +func TestLiteralColonWithRun(t *testing.T) { + SetMode(TestMode) + router := New() + + router.GET(`/test\:action`, func(c *Context) { + c.JSON(http.StatusOK, H{"path": "literal_colon"}) + }) + + router.updateRouteTrees() + + w := httptest.NewRecorder() + + req, _ := http.NewRequest(http.MethodGet, "/test:action", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "literal_colon") +} + +func TestLiteralColonWithDirectServeHTTP(t *testing.T) { + SetMode(TestMode) + router := New() + + router.GET(`/test\:action`, func(c *Context) { + c.JSON(http.StatusOK, H{"path": "literal_colon"}) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test:action", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "literal_colon") +} + +func TestLiteralColonWithHandler(t *testing.T) { + SetMode(TestMode) + router := New() + + router.GET(`/test\:action`, func(c *Context) { + c.JSON(http.StatusOK, H{"path": "literal_colon"}) + }) + + handler := router.Handler() + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test:action", nil) + handler.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "literal_colon") +} + +func TestLiteralColonWithHTTPServer(t *testing.T) { + SetMode(TestMode) + router := New() + + router.GET(`/test\:action`, func(c *Context) { + c.JSON(http.StatusOK, H{"path": "literal_colon"}) + }) + + router.GET("/test/:param", func(c *Context) { + c.JSON(http.StatusOK, H{"param": c.Param("param")}) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test:action", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "literal_colon") + + w2 := httptest.NewRecorder() + req2, _ := http.NewRequest(http.MethodGet, "/test/foo", nil) + router.ServeHTTP(w2, req2) + + assert.Equal(t, http.StatusOK, w2.Code) + assert.Contains(t, w2.Body.String(), "foo") +} + +// Test that updateRouteTrees is called only once +func TestUpdateRouteTreesCalledOnce(t *testing.T) { + SetMode(TestMode) + router := New() + + router.GET(`/test\:action`, func(c *Context) { + c.String(http.StatusOK, "ok") + }) + + for range 5 { + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test:action", nil) + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "ok", w.Body.String()) + } +} From e88fc8927a52b74f55bec0351604a56ac0aa1c51 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 18 Nov 2025 23:05:54 +0800 Subject: [PATCH 55/67] ci(sec): schedule Trivy security scans to run daily at midnight UTC (#4439) - Change Trivy scan schedule from quarterly to daily runs at 00:00 UTC Signed-off-by: appleboy --- .github/workflows/trivy-scan.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 12830633..da31dd59 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -8,9 +8,8 @@ on: branches: - master schedule: - # Run every 3 months (quarterly) on the 1st day at 00:00 UTC - # Months: January (1), April (4), July (7), October (10) - - cron: '0 0 1 1,4,7,10 *' + # Run daily at 00:00 UTC + - cron: '0 0 * * *' workflow_dispatch: # Allow manual trigger permissions: From ecb3f7b5e2f3915bf1db240ed5eee572f8dbea36 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 23 Nov 2025 11:46:13 +0800 Subject: [PATCH 56/67] chore(deps): upgrade golang.org/x/crypto to v0.45.0 (#4449) - Update golang.org/x/crypto dependency to version 0.45.0 1. https://avd.aquasec.com/nvd/cve-2025-47914 2. https://avd.aquasec.com/nvd/cve-2025-58181 Signed-off-by: appleboy --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c756803a..628ab4c5 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 1ef1ad18..90d5e526 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 440eb14ab8ed503d4a31dfecc9946a90cd73b955 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Wed, 26 Nov 2025 23:32:18 +0800 Subject: [PATCH 57/67] perf(path): replace regex with custom functions in redirectTrailingSlash (#4414) * perf: replace regex with custom functions in redirectTrailingSlash * perf: use more efficient removeRepeatedChar for path slash handling --------- Co-authored-by: 1911860538 --- gin.go | 21 ++++++++++++-------- path.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++- path_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 9 deletions(-) diff --git a/gin.go b/gin.go index d71086d1..16067e55 100644 --- a/gin.go +++ b/gin.go @@ -11,7 +11,6 @@ import ( "net/http" "os" "path" - "regexp" "strings" "sync" @@ -48,11 +47,6 @@ var defaultTrustedCIDRs = []*net.IPNet{ }, } -var ( - regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") - regRemoveRepeatedChar = regexp.MustCompile("/{2,}") -) - // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) @@ -776,8 +770,8 @@ 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, "/") + prefix = sanitizePathChars(prefix) + prefix = removeRepeatedChar(prefix, '/') p = prefix + "/" + req.URL.Path } @@ -788,6 +782,17 @@ func redirectTrailingSlash(c *Context) { redirectRequest(c) } +// sanitizePathChars removes unsafe characters from path strings, +// keeping only ASCII letters, ASCII numbers, forward slashes, and hyphens. +func sanitizePathChars(s string) string { + return strings.Map(func(r rune) rune { + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '/' || r == '-' { + return r + } + return -1 + }, s) +} + func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { req := c.Request rPath := req.URL.Path diff --git a/path.go b/path.go index 82438c13..3b67caa9 100644 --- a/path.go +++ b/path.go @@ -5,6 +5,8 @@ package gin +const stackBufSize = 128 + // cleanPath is the URL version of path.Clean, it returns a canonical URL path // for p, eliminating . and .. elements. // @@ -19,7 +21,6 @@ package gin // // If the result of this process is an empty string, "/" is returned. func cleanPath(p string) string { - const stackBufSize = 128 // Turn empty string into "/" if p == "" { return "/" @@ -148,3 +149,55 @@ func bufApp(buf *[]byte, s string, w int, c byte) { } b[w] = c } + +// removeRepeatedChar removes multiple consecutive 'char's from a string. +// if s == "/a//b///c////" && char == '/', it returns "/a/b/c/" +func removeRepeatedChar(s string, char byte) string { + // Check if there are any consecutive chars + hasRepeatedChar := false + for i := 1; i < len(s); i++ { + if s[i] == char && s[i-1] == char { + hasRepeatedChar = true + break + } + } + if !hasRepeatedChar { + return s + } + + // Reasonably sized buffer on stack to avoid allocations in the common case. + buf := make([]byte, 0, stackBufSize) + + // Invariants: + // reading from s; r is index of next byte to process. + // writing to buf; w is index of next byte to write. + r := 0 + w := 0 + + for n := len(s); r < n; { + if s[r] == char { + // Write the first char + bufApp(&buf, s, w, char) + w++ + r++ + + // Skip all consecutive chars + for r < n && s[r] == char { + r++ + } + } else { + // Copy non-char character + bufApp(&buf, s, w, s[r]) + w++ + r++ + } + } + + // If the original string was not modified (or only shortened at the end), + // return the respective substring of the original string. + // Otherwise, return a new string from the buffer. + if len(buf) == 0 { + return s[:w] + } + return string(buf[:w]) +} diff --git a/path_test.go b/path_test.go index 7d86086f..eba1be08 100644 --- a/path_test.go +++ b/path_test.go @@ -143,3 +143,50 @@ func BenchmarkPathCleanLong(b *testing.B) { } } } + +func TestRemoveRepeatedChar(t *testing.T) { + testCases := []struct { + name string + str string + char byte + want string + }{ + { + name: "empty", + str: "", + char: 'a', + want: "", + }, + { + name: "noSlash", + str: "abc", + char: ',', + want: "abc", + }, + { + name: "withSlash", + str: "/a/b/c/", + char: '/', + want: "/a/b/c/", + }, + { + name: "withRepeatedSlashes", + str: "/a//b///c////", + char: '/', + want: "/a/b/c/", + }, + { + name: "threeSlashes", + str: "///", + char: '/', + want: "/", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res := removeRepeatedChar(tc.str, tc.char) + assert.Equal(t, tc.want, res) + }) + } +} From 52ecf029bd2e9b4d2652f96dd2b753f8bc6b6e95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 23:33:08 +0800 Subject: [PATCH 58/67] chore(deps): bump actions/checkout from 5 to 6 in the actions group (#4446) Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 5 to 6 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bo-Yi Wu --- .github/workflows/codeql.yml | 2 +- .github/workflows/gin.yml | 4 ++-- .github/workflows/goreleaser.yml | 2 +- .github/workflows/trivy-scan.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9ec3700e..f287c265 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 8bca364d..4e3b8753 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Go @@ -61,7 +61,7 @@ jobs: cache: false - name: Checkout Code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.ref }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 37dfb5bb..0098b952 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Go diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index da31dd59..b86aed7f 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 From 771dcc6476d7bc6abb9ec0235ecefa4d38fe6fb0 Mon Sep 17 00:00:00 2001 From: Aeddis Desauw <89919264+ldesauw@users.noreply.github.com> Date: Thu, 27 Nov 2025 10:55:34 +0100 Subject: [PATCH 59/67] feat(gin): add option to use escaped path (#4420) Co-authored-by: Bo-Yi Wu --- gin.go | 16 ++++++++++++++-- gin_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index 16067e55..2e033bf3 100644 --- a/gin.go +++ b/gin.go @@ -135,10 +135,16 @@ type Engine struct { AppEngine bool // UseRawPath if enabled, the url.RawPath will be used to find parameters. + // The RawPath is only a hint, EscapedPath() should be use instead. (https://pkg.go.dev/net/url@master#URL) + // Only use RawPath if you know what you are doing. UseRawPath bool + // UseEscapedPath if enable, the url.EscapedPath() will be used to find parameters + // It overrides UseRawPath + UseEscapedPath bool + // UnescapePathValues if true, the path value will be unescaped. - // If UseRawPath is false (by default), the UnescapePathValues effectively is true, + // If UseRawPath and UseEscapedPath are false (by default), the UnescapePathValues effectively is true, // as url.Path gonna be used, which is already unescaped. UnescapePathValues bool @@ -191,6 +197,7 @@ var _ IRouter = (*Engine)(nil) // - HandleMethodNotAllowed: false // - ForwardedByClientIP: true // - UseRawPath: false +// - UseEscapedPath: false // - UnescapePathValues: true func New(opts ...OptionFunc) *Engine { debugPrintWARNINGNew() @@ -208,6 +215,7 @@ func New(opts ...OptionFunc) *Engine { RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"}, TrustedPlatform: defaultPlatform, UseRawPath: false, + UseEscapedPath: false, RemoveExtraSlash: false, UnescapePathValues: true, MaxMultipartMemory: defaultMultipartMemory, @@ -683,7 +691,11 @@ func (engine *Engine) handleHTTPRequest(c *Context) { httpMethod := c.Request.Method rPath := c.Request.URL.Path unescape := false - if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { + + if engine.UseEscapedPath { + rPath = c.Request.URL.EscapedPath() + unescape = engine.UnescapePathValues + } else if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } diff --git a/gin_test.go b/gin_test.go index cee1f3cc..21bf71d8 100644 --- a/gin_test.go +++ b/gin_test.go @@ -720,6 +720,55 @@ func TestEngineHandleContextPreventsMiddlewareReEntry(t *testing.T) { assert.Equal(t, int64(1), handlerCounterV2) } +func TestEngineHandleContextUseEscapedPathPercentEncoded(t *testing.T) { + r := New() + r.UseEscapedPath = true + r.UnescapePathValues = false + + r.GET("/v1/:path", func(c *Context) { + // Path is Escaped, the %25 is not interpreted as % + assert.Equal(t, "foo%252Fbar", c.Param("path")) + c.Status(http.StatusOK) + }) + + req := httptest.NewRequest(http.MethodGet, "/v1/foo%252Fbar", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) +} + +func TestEngineHandleContextUseRawPathPercentEncoded(t *testing.T) { + r := New() + r.UseRawPath = true + r.UnescapePathValues = false + + r.GET("/v1/:path", func(c *Context) { + // Path is used, the %25 is interpreted as % + assert.Equal(t, "foo%2Fbar", c.Param("path")) + c.Status(http.StatusOK) + }) + + req := httptest.NewRequest(http.MethodGet, "/v1/foo%252Fbar", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) +} + +func TestEngineHandleContextUseEscapedPathOverride(t *testing.T) { + r := New() + r.UseEscapedPath = true + r.UseRawPath = true + r.UnescapePathValues = false + + r.GET("/v1/:path", func(c *Context) { + assert.Equal(t, "foo%25bar", c.Param("path")) + c.Status(http.StatusOK) + }) + + assert.NotPanics(t, func() { + w := PerformRequest(r, http.MethodGet, "/v1/foo%25bar") + assert.Equal(t, 200, w.Code) + }) +} + func TestPrepareTrustedCIRDsWith(t *testing.T) { r := New() From c358d5656d0feb8b310d4ec379bccde46ccc8cc7 Mon Sep 17 00:00:00 2001 From: Milad Date: Thu, 27 Nov 2025 18:31:57 +0330 Subject: [PATCH 60/67] test(gin): Add comprehensive test coverage for ginS package (#4442) * test(ginS): add comprehensive test coverage for ginS package Improve test coverage for ginS package by adding 18 test functions covering HTTP methods, routing, middleware, static files, and templates. * use http.Method* constants instead of raw strings in gins_test.go * copyright updated in gins_test.go --------- Co-authored-by: Bo-Yi Wu --- ginS/gins_test.go | 246 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 ginS/gins_test.go diff --git a/ginS/gins_test.go b/ginS/gins_test.go new file mode 100644 index 00000000..ffde85d2 --- /dev/null +++ b/ginS/gins_test.go @@ -0,0 +1,246 @@ +// Copyright 2025 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. + +package ginS + +import ( + "html/template" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func init() { + gin.SetMode(gin.TestMode) +} + +func TestGET(t *testing.T) { + GET("/test", func(c *gin.Context) { + c.String(http.StatusOK, "test") + }) + + req := httptest.NewRequest(http.MethodGet, "/test", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "test", w.Body.String()) +} + +func TestPOST(t *testing.T) { + POST("/post", func(c *gin.Context) { + c.String(http.StatusCreated, "created") + }) + + req := httptest.NewRequest(http.MethodPost, "/post", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, "created", w.Body.String()) +} + +func TestPUT(t *testing.T) { + PUT("/put", func(c *gin.Context) { + c.String(http.StatusOK, "updated") + }) + + req := httptest.NewRequest(http.MethodPut, "/put", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "updated", w.Body.String()) +} + +func TestDELETE(t *testing.T) { + DELETE("/delete", func(c *gin.Context) { + c.String(http.StatusOK, "deleted") + }) + + req := httptest.NewRequest(http.MethodDelete, "/delete", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "deleted", w.Body.String()) +} + +func TestPATCH(t *testing.T) { + PATCH("/patch", func(c *gin.Context) { + c.String(http.StatusOK, "patched") + }) + + req := httptest.NewRequest(http.MethodPatch, "/patch", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "patched", w.Body.String()) +} + +func TestOPTIONS(t *testing.T) { + OPTIONS("/options", func(c *gin.Context) { + c.String(http.StatusOK, "options") + }) + + req := httptest.NewRequest(http.MethodOptions, "/options", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "options", w.Body.String()) +} + +func TestHEAD(t *testing.T) { + HEAD("/head", func(c *gin.Context) { + c.String(http.StatusOK, "head") + }) + + req := httptest.NewRequest(http.MethodHead, "/head", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestAny(t *testing.T) { + Any("/any", func(c *gin.Context) { + c.String(http.StatusOK, "any") + }) + + req := httptest.NewRequest(http.MethodGet, "/any", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "any", w.Body.String()) +} + +func TestHandle(t *testing.T) { + Handle(http.MethodGet, "/handle", func(c *gin.Context) { + c.String(http.StatusOK, "handle") + }) + + req := httptest.NewRequest(http.MethodGet, "/handle", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "handle", w.Body.String()) +} + +func TestGroup(t *testing.T) { + group := Group("/group") + group.GET("/test", func(c *gin.Context) { + c.String(http.StatusOK, "group test") + }) + + req := httptest.NewRequest(http.MethodGet, "/group/test", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "group test", w.Body.String()) +} + +func TestUse(t *testing.T) { + var middlewareExecuted bool + Use(func(c *gin.Context) { + middlewareExecuted = true + c.Next() + }) + + GET("/middleware-test", func(c *gin.Context) { + c.String(http.StatusOK, "ok") + }) + + req := httptest.NewRequest(http.MethodGet, "/middleware-test", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.True(t, middlewareExecuted) + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestNoRoute(t *testing.T) { + NoRoute(func(c *gin.Context) { + c.String(http.StatusNotFound, "custom 404") + }) + + req := httptest.NewRequest(http.MethodGet, "/nonexistent", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusNotFound, w.Code) + assert.Equal(t, "custom 404", w.Body.String()) +} + +func TestNoMethod(t *testing.T) { + NoMethod(func(c *gin.Context) { + c.String(http.StatusMethodNotAllowed, "method not allowed") + }) + + // This just verifies that NoMethod is callable + // Testing the actual behavior would require a separate engine instance + assert.NotNil(t, engine()) +} + +func TestRoutes(t *testing.T) { + GET("/routes-test", func(c *gin.Context) {}) + + routes := Routes() + assert.NotEmpty(t, routes) + + found := false + for _, route := range routes { + if route.Path == "/routes-test" && route.Method == http.MethodGet { + found = true + break + } + } + assert.True(t, found) +} + +func TestSetHTMLTemplate(t *testing.T) { + tmpl := template.Must(template.New("test").Parse("Hello {{.}}")) + SetHTMLTemplate(tmpl) + + // Verify engine has template set + assert.NotNil(t, engine()) +} + +func TestStaticFile(t *testing.T) { + StaticFile("/static-file", "../testdata/test_file.txt") + + req := httptest.NewRequest(http.MethodGet, "/static-file", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestStatic(t *testing.T) { + Static("/static-dir", "../testdata") + + req := httptest.NewRequest(http.MethodGet, "/static-dir/test_file.txt", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestStaticFS(t *testing.T) { + fs := http.Dir("../testdata") + StaticFS("/static-fs", fs) + + req := httptest.NewRequest(http.MethodGet, "/static-fs/test_file.txt", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} From 63dd3e60cab89c27fb66bce1423bd268d52abad1 Mon Sep 17 00:00:00 2001 From: Yilong Li Date: Thu, 27 Nov 2025 23:20:52 +0800 Subject: [PATCH 61/67] fix(recover): suppress http.ErrAbortHandler in recover (#4336) Co-authored-by: Bo-Yi Wu --- recovery.go | 3 +++ recovery_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/recovery.go b/recovery.go index fdd463f3..e79e118a 100644 --- a/recovery.go +++ b/recovery.go @@ -68,6 +68,9 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } } } + if e, ok := err.(error); ok && errors.Is(e, http.ErrAbortHandler) { + brokenPipe = true + } if logger != nil { const stackSkip = 3 if brokenPipe { diff --git a/recovery_test.go b/recovery_test.go index 8a9e3475..073f4858 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -142,6 +142,30 @@ func TestPanicWithBrokenPipe(t *testing.T) { } } +// TestPanicWithAbortHandler asserts that recovery handles http.ErrAbortHandler as broken pipe +func TestPanicWithAbortHandler(t *testing.T) { + const expectCode = 204 + + var buf strings.Builder + router := New() + router.Use(RecoveryWithWriter(&buf)) + router.GET("/recovery", func(c *Context) { + // Start writing response + c.Header("X-Test", "Value") + c.Status(expectCode) + + // Panic with ErrAbortHandler which should be treated as broken pipe + panic(http.ErrAbortHandler) + }) + // RUN + w := PerformRequest(router, http.MethodGet, "/recovery") + // TEST + assert.Equal(t, expectCode, w.Code) + out := buf.String() + assert.Contains(t, out, "net/http: abort Handler") + assert.NotContains(t, out, "panic recovered") +} + func TestCustomRecoveryWithWriter(t *testing.T) { errBuffer := new(strings.Builder) buffer := new(strings.Builder) From af6e8b70b8261bb0c99ad094fe552ab92991620a Mon Sep 17 00:00:00 2001 From: appleboy Date: Sun, 30 Nov 2025 11:52:25 +0800 Subject: [PATCH 62/67] chore(deps): upgrade quic-go to v0.57.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix CVE-2025-59530 vulnerability (quic-go Crash Due to Premature HANDSHAKE_DONE Frame) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 628ab4c5..58ec6fc9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.56.0 + github.com/quic-go/quic-go v0.57.1 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 golang.org/x/net v0.47.0 @@ -32,7 +32,7 @@ require ( github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/qpack v0.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/crypto v0.45.0 // indirect diff --git a/go.sum b/go.sum index 90d5e526..bcdb4493 100644 --- a/go.sum +++ b/go.sum @@ -49,10 +49,10 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 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/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY= -github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= +github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 583db590ec2a488ebcf7f8dc6232d11c7db62eac Mon Sep 17 00:00:00 2001 From: Milad Date: Sun, 30 Nov 2025 10:55:46 +0330 Subject: [PATCH 63/67] test(bytesconv): add tests for empty/nil cases (#4454) Co-authored-by: Bo-Yi Wu --- internal/bytesconv/bytesconv_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go index 4972ae70..60e28fb4 100644 --- a/internal/bytesconv/bytesconv_test.go +++ b/internal/bytesconv/bytesconv_test.go @@ -41,6 +41,15 @@ func TestBytesToString(t *testing.T) { } } +func TestBytesToStringEmpty(t *testing.T) { + if got := BytesToString([]byte{}); got != "" { + t.Fatalf("BytesToString([]byte{}) = %q; want empty string", got) + } + if got := BytesToString(nil); got != "" { + t.Fatalf("BytesToString(nil) = %q; want empty string", got) + } +} + const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( letterIdxBits = 6 // 6 bits to represent a letter index @@ -78,6 +87,16 @@ func TestStringToBytes(t *testing.T) { } } +func TestStringToBytesEmpty(t *testing.T) { + b := StringToBytes("") + if len(b) != 0 { + t.Fatalf(`StringToBytes("") length = %d; want 0`, len(b)) + } + if !bytes.Equal(b, []byte("")) { + t.Fatalf(`StringToBytes("") = %v; want []byte("")`, b) + } +} + // go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true func BenchmarkBytesConvBytesToStrRaw(b *testing.B) { From f416d1e594a027063e73f66ac873a82113036fd8 Mon Sep 17 00:00:00 2001 From: Wayne Aki <111057868+Planckbaka@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:38:07 +0800 Subject: [PATCH 64/67] test(gin): resolve race conditions in integration tests (#4453) - Implement TestRebuild404Handlers to verify 404 handler chain rebuilding when global middleware is added via Use() - Add waitForServerReady helper with exponential backoff to replace unreliable time.Sleep() calls in integration tests - Fix race conditions in TestRunEmpty, TestRunEmptyWithEnv, and TestRunWithPort by using proper server readiness checks - All tests now pass consistently with -race flag This addresses the empty test function and eliminates flaky test failures caused by insufficient wait times for server startup. Co-authored-by: Bo-Yi Wu --- gin_integration_test.go | 21 ++++++++++++--------- gin_test.go | 23 +++++++++++++++++++++++ test_helpers.go | 31 ++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index e040993a..3ea5fe2f 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -70,9 +70,10 @@ func TestRunEmpty(t *testing.T) { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.Run()) }() - // have to wait for the goroutine to start and run the server - // otherwise the main thread will complete - time.Sleep(5 * time.Millisecond) + + // Wait for server to be ready with exponential backoff + err := waitForServerReady("http://localhost:8080/example", 10) + require.NoError(t, err, "server should start successfully") require.Error(t, router.Run(":8080")) testRequest(t, "http://localhost:8080/example") @@ -213,9 +214,10 @@ func TestRunEmptyWithEnv(t *testing.T) { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.Run()) }() - // have to wait for the goroutine to start and run the server - // otherwise the main thread will complete - time.Sleep(5 * time.Millisecond) + + // Wait for server to be ready with exponential backoff + err := waitForServerReady("http://localhost:3123/example", 10) + require.NoError(t, err, "server should start successfully") require.Error(t, router.Run(":3123")) testRequest(t, "http://localhost:3123/example") @@ -234,9 +236,10 @@ func TestRunWithPort(t *testing.T) { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.Run(":5150")) }() - // have to wait for the goroutine to start and run the server - // otherwise the main thread will complete - time.Sleep(5 * time.Millisecond) + + // Wait for server to be ready with exponential backoff + err := waitForServerReady("http://localhost:5150/example", 10) + require.NoError(t, err, "server should start successfully") require.Error(t, router.Run(":5150")) testRequest(t, "http://localhost:5150/example") diff --git a/gin_test.go b/gin_test.go index 21bf71d8..81343d88 100644 --- a/gin_test.go +++ b/gin_test.go @@ -545,6 +545,29 @@ func TestNoMethodWithoutGlobalHandlers(t *testing.T) { } func TestRebuild404Handlers(t *testing.T) { + var middleware0 HandlerFunc = func(c *Context) {} + var middleware1 HandlerFunc = func(c *Context) {} + + router := New() + + // Initially, allNoRoute should be nil + assert.Nil(t, router.allNoRoute) + + // Set NoRoute handlers + router.NoRoute(middleware0) + assert.Len(t, router.allNoRoute, 1) + assert.Len(t, router.noRoute, 1) + compareFunc(t, router.allNoRoute[0], middleware0) + + // Add Use middleware should trigger rebuild404Handlers + router.Use(middleware1) + assert.Len(t, router.allNoRoute, 2) + assert.Len(t, router.Handlers, 1) + assert.Len(t, router.noRoute, 1) + + // Global middleware should come first + compareFunc(t, router.allNoRoute[0], middleware1) + compareFunc(t, router.allNoRoute[1], middleware0) } func TestNoMethodWithGlobalHandlers(t *testing.T) { diff --git a/test_helpers.go b/test_helpers.go index a1a7c562..20d20032 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -4,7 +4,11 @@ package gin -import "net/http" +import ( + "fmt" + "net/http" + "time" +) // CreateTestContext returns a fresh Engine and a Context associated with it. // This is useful for tests that need to set up a new Gin engine instance @@ -29,3 +33,28 @@ func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) { c.writermem.reset(w) return } + +// waitForServerReady waits for a server to be ready by making HTTP requests +// with exponential backoff. This is more reliable than time.Sleep() for testing. +func waitForServerReady(url string, maxAttempts int) error { + client := &http.Client{ + Timeout: 100 * time.Millisecond, + } + + for i := 0; i < maxAttempts; i++ { + resp, err := client.Get(url) + if err == nil { + resp.Body.Close() + return nil + } + + // Exponential backoff: 10ms, 20ms, 40ms, 80ms, 160ms... + backoff := time.Duration(10*(1< 500*time.Millisecond { + backoff = 500 * time.Millisecond + } + time.Sleep(backoff) + } + + return fmt.Errorf("server at %s did not become ready after %d attempts", url, maxAttempts) +} From fad706f1216e6d12bdd51d28d5a40ec27e6c6453 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:09:41 +0800 Subject: [PATCH 65/67] chore(deps): bump github.com/goccy/go-yaml from 1.18.0 to 1.19.0 (#4458) Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.18.0 to 1.19.0. - [Release notes](https://github.com/goccy/go-yaml/releases) - [Changelog](https://github.com/goccy/go-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-yaml/compare/v1.18.0...v1.19.0) --- updated-dependencies: - dependency-name: github.com/goccy/go-yaml dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 58ec6fc9..3a2b2bf2 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.28.0 github.com/goccy/go-json v0.10.2 - github.com/goccy/go-yaml v1.18.0 + github.com/goccy/go-yaml v1.19.0 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 diff --git a/go.sum b/go.sum index bcdb4493..a487aaaf 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0 github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE= +github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= From b917b14ff9d189f16a7492be79d123a47806ee19 Mon Sep 17 00:00:00 2001 From: guonaihong Date: Wed, 3 Dec 2025 19:18:10 +0800 Subject: [PATCH 66/67] fix(binding): empty value error (#2169) * fix empty value error Here is the code that can report an error ```go package main import ( "fmt" "github.com/gin-gonic/gin" "io" "net/http" "os" "time" ) type header struct { Duration time.Duration `header:"duration"` CreateTime time.Time `header:"createTime" time_format:"unix"` } func needFix1() { g := gin.Default() g.GET("/", func(c *gin.Context) { h := header{} err := c.ShouldBindHeader(&h) if err != nil { c.JSON(500, fmt.Sprintf("fail:%s\n", err)) return } c.JSON(200, h) }) g.Run(":8081") } func needFix2() { g := gin.Default() g.GET("/", func(c *gin.Context) { h := header{} err := c.ShouldBindHeader(&h) if err != nil { c.JSON(500, fmt.Sprintf("fail:%s\n", err)) return } c.JSON(200, h) }) g.Run(":8082") } func sendNeedFix1() { // send to needFix1 sendBadData("http://127.0.0.1:8081", "duration") } func sendNeedFix2() { // send to needFix2 sendBadData("http://127.0.0.1:8082", "createTime") } func sendBadData(url, key string) { req, err := http.NewRequest("GET", "http://127.0.0.1:8081", nil) if err != nil { fmt.Printf("err:%s\n", err) return } // Only the key and no value can cause an error req.Header.Add(key, "") rsp, err := http.DefaultClient.Do(req) if err != nil { return } io.Copy(os.Stdout, rsp.Body) rsp.Body.Close() } func main() { go needFix1() go needFix2() time.Sleep(time.Second / 1000 * 200) // 200ms sendNeedFix1() sendNeedFix2() } ``` * modify code * add comment * test(binding): use 'any' alias and require.NoError in form mapping tests - Replace 'interface{}' with 'any' alias in bindTestData struct - Change assert.NoError to require.NoError in TestMappingTimeUnixNano and TestMappingTimeDuration to fail fast on mapping errors --------- Co-authored-by: Bo-Yi Wu --- binding/form_mapping.go | 19 +++++++++++++----- binding/form_mapping_test.go | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 1244b522..e76e7510 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -300,6 +300,11 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ } func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { + // If it is a string type, no spaces are removed, and the user data is not modified here + if value.Kind() != reflect.String { + val = strings.TrimSpace(val) + } + switch value.Kind() { case reflect.Int: return setIntField(val, 0, value) @@ -404,6 +409,11 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val timeFormat = time.RFC3339 } + if val == "" { + value.Set(reflect.ValueOf(time.Time{})) + return nil + } + switch tf := strings.ToLower(timeFormat); tf { case "unix", "unixmilli", "unixmicro", "unixnano": tv, err := strconv.ParseInt(val, 10, 64) @@ -427,11 +437,6 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val return nil } - if val == "" { - value.Set(reflect.ValueOf(time.Time{})) - return nil - } - l := time.Local if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { l = time.UTC @@ -475,6 +480,10 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err } func setTimeDuration(val string, value reflect.Value) error { + if val == "" { + val = "0" + } + d, err := time.ParseDuration(val) if err != nil { return err diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 006eddf1..e007573c 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -226,7 +226,35 @@ func TestMappingTime(t *testing.T) { require.Error(t, err) } +type bindTestData struct { + need any + got any + in map[string][]string +} + +func TestMappingTimeUnixNano(t *testing.T) { + type needFixUnixNanoEmpty struct { + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + } + + // ok + tests := []bindTestData{ + {need: &needFixUnixNanoEmpty{}, got: &needFixUnixNanoEmpty{}, in: formSource{"createTime": []string{" "}}}, + {need: &needFixUnixNanoEmpty{}, got: &needFixUnixNanoEmpty{}, in: formSource{"createTime": []string{}}}, + } + + for _, v := range tests { + err := mapForm(v.got, v.in) + require.NoError(t, err) + assert.Equal(t, v.need, v.got) + } +} + func TestMappingTimeDuration(t *testing.T) { + type needFixDurationEmpty struct { + Duration time.Duration `form:"duration"` + } + var s struct { D time.Duration } @@ -236,6 +264,17 @@ func TestMappingTimeDuration(t *testing.T) { require.NoError(t, err) assert.Equal(t, 5*time.Second, s.D) + // ok + tests := []bindTestData{ + {need: &needFixDurationEmpty{}, got: &needFixDurationEmpty{}, in: formSource{"duration": []string{" "}}}, + {need: &needFixDurationEmpty{}, got: &needFixDurationEmpty{}, in: formSource{"duration": []string{}}}, + } + + for _, v := range tests { + err := mapForm(v.got, v.in) + require.NoError(t, err) + assert.Equal(t, v.need, v.got) + } // error err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form") require.Error(t, err) From 2a794cd0b0faa7d829291375b27a3467ea972b0d Mon Sep 17 00:00:00 2001 From: OHZEKI Naoki <0h23k1.n40k1@gmail.com> Date: Thu, 4 Dec 2025 11:49:37 +0900 Subject: [PATCH 67/67] fix(debug): version mismatch (#4403) Co-authored-by: Bo-Yi Wu --- debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug.go b/debug.go index f22dfd87..e07c8b46 100644 --- a/debug.go +++ b/debug.go @@ -13,7 +13,7 @@ import ( "sync/atomic" ) -const ginSupportMinGoVer = 23 +const ginSupportMinGoVer = 24 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode.