From 6fab4c373eaabe3425b08fe1a5f1484c93d07c2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 15:39:38 +0800 Subject: [PATCH 001/332] chore(deps): bump actions/setup-go from 2 to 3 (#3340) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 64ed8b2b..654585b9 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: 1.17 - From 6296175f70e21bd1c09efc424a2c14574904720d Mon Sep 17 00:00:00 2001 From: mstmdev Date: Wed, 12 Oct 2022 14:18:12 +0800 Subject: [PATCH 002/332] Fix the GO-2022-0969 and GO-2022-0288 vulnerabilities (#3333) --- .github/workflows/gin.yml | 2 +- go.mod | 2 +- go.sum | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 004f9b9d..686d08a3 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -31,7 +31,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: [1.15, 1.16, 1.17, 1.18] + go: [1.16, 1.17, 1.18] test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] include: - os: ubuntu-latest diff --git a/go.mod b/go.mod index fa1db99a..1c64cf1b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.2 github.com/stretchr/testify v1.8.0 github.com/ugorji/go/codec v1.2.7 - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 + golang.org/x/net v0.0.0-20221004154528-8021a29435af google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 1c31d299..35bfd2e9 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,9 @@ golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SX golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= +golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From fa58bff301a823ce25af800614d3f016b10ae586 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Sun, 16 Oct 2022 09:32:28 +0800 Subject: [PATCH 003/332] chore(dep): Changes minimum support go version to go1.16 (#3361) --- README.md | 2 +- debug.go | 4 ++-- debug_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8d7b5ec4..960c66dc 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. You first need [Go](https://golang.org/) installed (**version 1.15+ is required**), then you can use the below Go command to install Gin. +1. You first need [Go](https://golang.org/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin. ```sh go get -u github.com/gin-gonic/gin diff --git a/debug.go b/debug.go index b9f8234a..cbcedbc9 100644 --- a/debug.go +++ b/debug.go @@ -12,7 +12,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 15 +const ginSupportMinGoVer = 16 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.15+. + debugPrint(`[WARNING] Now Gin requires Go 1.16+. `) } diff --git a/debug_test.go b/debug_test.go index bf0e6ab8..abe8b41c 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.15+.\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.16+.\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 4c64f1c3859fa853659ff1db65a8f0fa67856ed9 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Sun, 16 Oct 2022 09:33:26 +0800 Subject: [PATCH 004/332] =?UTF-8?q?chore(go):=20Add=C2=A0support=20go=201.?= =?UTF-8?q?19=20(#3272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gin.yml | 2 +- context_1.17_test.go | 17 +++++++++++++++++ context_1.19_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 context_1.19_test.go diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 686d08a3..18b36d0e 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -31,7 +31,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: [1.16, 1.17, 1.18] + go: [1.16, 1.17, 1.18, 1.19] test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] include: - os: ubuntu-latest diff --git a/context_1.17_test.go b/context_1.17_test.go index 23377ffd..0f8527fe 100644 --- a/context_1.17_test.go +++ b/context_1.17_test.go @@ -12,6 +12,8 @@ import ( "mime/multipart" "net/http" "net/http/httptest" + "runtime" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -28,6 +30,9 @@ func (i interceptedWriter) WriteHeader(code int) { } func TestContextFormFileFailed17(t *testing.T) { + if !isGo117OrGo118() { + return + } buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) defer func(mw *multipart.Writer) { @@ -75,3 +80,15 @@ func TestInterceptedHeader(t *testing.T) { assert.Equal(t, "", w.Result().Header.Get("X-Test")) assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) } + +func isGo117OrGo118() bool { + version := strings.Split(runtime.Version()[2:], ".") + if len(version) >= 2 { + x := version[0] + y := version[1] + if x == "1" && (y == "17" || y == "18") { + return true + } + } + return false +} diff --git a/context_1.19_test.go b/context_1.19_test.go new file mode 100644 index 00000000..4b34ea24 --- /dev/null +++ b/context_1.19_test.go @@ -0,0 +1,31 @@ +// Copyright 2022 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build go1.19 +// +build go1.19 + +package gin + +import ( + "bytes" + "mime/multipart" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContextFormFileFailed19(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + c.engine.MaxMultipartMemory = 8 << 20 + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) +} From 24a1d2adb9dba64d38426d76f2a4cf76123a4711 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Sun, 16 Oct 2022 11:41:14 +1000 Subject: [PATCH 005/332] fix(typo): spelling `covert` -> `convert` (#3325) --- binding/form_mapping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 98cebfec..540bbbb8 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -19,7 +19,7 @@ import ( var ( errUnknownType = errors.New("unknown type") - // ErrConvertMapStringSlice can not covert to map[string][]string + // ErrConvertMapStringSlice can not convert to map[string][]string ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings") // ErrConvertToMapString can not convert to map[string]string From 45c758e2f9d36ee6a6e7d8b2131474970e41b12d Mon Sep 17 00:00:00 2001 From: Mohana sai krishna Kandula <73701479+mskKandula@users.noreply.github.com> Date: Sun, 16 Oct 2022 07:15:08 +0530 Subject: [PATCH 006/332] chore(file): Creates a directory named path (#3316) Co-authored-by: mohanak --- context.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/context.go b/context.go index f9489a77..b66b8adc 100644 --- a/context.go +++ b/context.go @@ -15,6 +15,7 @@ import ( "net/http" "net/url" "os" + "path/filepath" "strings" "sync" "time" @@ -601,6 +602,10 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error } defer src.Close() + if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil { + return err + } + out, err := os.Create(dst) if err != nil { return err From 33ab0fc155e68acbc7fd30281c0d9b2e6e091b7b Mon Sep 17 00:00:00 2001 From: hopehook Date: Sun, 16 Oct 2022 09:49:24 +0800 Subject: [PATCH 007/332] refactor(struct): Remove redundant type conversions (#3345) --- binding/binding_test.go | 8 ++------ binding/form_mapping_test.go | 8 ++++---- render/render_test.go | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index f0996216..eae27802 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1107,9 +1107,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s assert.Equal(t, struct { Idx int "form:\"idx\"" - }(struct { - Idx int "form:\"idx\"" - }{Idx: 123}), + }{Idx: 123}, obj.StructFoo) case "StructPointer": obj := FooStructForStructPointerType{} @@ -1118,9 +1116,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s assert.Equal(t, struct { Name string "form:\"name\"" - }(struct { - Name string "form:\"name\"" - }{Name: "thinkerou"}), + }{Name: "thinkerou"}, *obj.StructPointerFoo) case "Map": obj := FooStructForMapType{} diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 78f4df0e..93d6a92f 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -114,7 +114,7 @@ func TestMappingPrivateField(t *testing.T) { } err := mappingByPtr(&s, formSource{"field": {"6"}}, "form") assert.NoError(t, err) - assert.Equal(t, int(0), s.f) + assert.Equal(t, 0, s.f) } func TestMappingUnknownFieldType(t *testing.T) { @@ -133,7 +133,7 @@ func TestMappingURI(t *testing.T) { } err := mapURI(&s, map[string][]string{"field": {"6"}}) assert.NoError(t, err) - assert.Equal(t, int(6), s.F) + assert.Equal(t, 6, s.F) } func TestMappingForm(t *testing.T) { @@ -142,7 +142,7 @@ func TestMappingForm(t *testing.T) { } err := mapForm(&s, map[string][]string{"field": {"6"}}) assert.NoError(t, err) - assert.Equal(t, int(6), s.F) + assert.Equal(t, 6, s.F) } func TestMapFormWithTag(t *testing.T) { @@ -151,7 +151,7 @@ func TestMapFormWithTag(t *testing.T) { } err := MapFormWithTag(&s, map[string][]string{"field": {"6"}}, "externalTag") assert.NoError(t, err) - assert.Equal(t, int(6), s.F) + assert.Equal(t, 6, s.F) } func TestMappingTime(t *testing.T) { diff --git a/render/render_test.go b/render/render_test.go index a13fff42..3509db3c 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -173,7 +173,7 @@ func TestRenderAsciiJSON(t *testing.T) { assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() - data2 := float64(3.1415926) + data2 := 3.1415926 err = (AsciiJSON{data2}).Render(w2) assert.NoError(t, err) From 51aea73ba0f125f6cacc3b4b695efdf21d9c634f Mon Sep 17 00:00:00 2001 From: Jesse <1430482733@qq.com> Date: Thu, 20 Oct 2022 00:49:19 +0800 Subject: [PATCH 008/332] fix: modify interface check way (#3327) --- binding/default_validator.go | 2 +- context_test.go | 2 +- errors.go | 2 +- gin.go | 2 +- response_writer.go | 2 +- routergroup.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/binding/default_validator.go b/binding/default_validator.go index c03afe75..e216b854 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -43,7 +43,7 @@ func (err SliceValidationError) Error() string { } } -var _ StructValidator = &defaultValidator{} +var _ StructValidator = (*defaultValidator)(nil) // ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. func (v *defaultValidator) ValidateStruct(obj any) error { diff --git a/context_test.go b/context_test.go index b3e81c14..cc55cb01 100644 --- a/context_test.go +++ b/context_test.go @@ -30,7 +30,7 @@ import ( "google.golang.org/protobuf/proto" ) -var _ context.Context = &Context{} +var _ context.Context = (*Context)(nil) // Unit tests TODO // func (c *Context) File(filepath string) { diff --git a/errors.go b/errors.go index ca2bfc3f..06b53c28 100644 --- a/errors.go +++ b/errors.go @@ -39,7 +39,7 @@ type Error struct { type errorMsgs []*Error -var _ error = &Error{} +var _ error = (*Error)(nil) // SetType sets the error's type. func (msg *Error) SetType(flags ErrorType) *Error { diff --git a/gin.go b/gin.go index f9324299..a2e2e67c 100644 --- a/gin.go +++ b/gin.go @@ -166,7 +166,7 @@ type Engine struct { trustedCIDRs []*net.IPNet } -var _ IRouter = &Engine{} +var _ IRouter = (*Engine)(nil) // New returns a new blank Engine instance without any middleware attached. // By default, the configuration is: diff --git a/response_writer.go b/response_writer.go index 77c7ed8f..43e828d7 100644 --- a/response_writer.go +++ b/response_writer.go @@ -49,7 +49,7 @@ type responseWriter struct { status int } -var _ ResponseWriter = &responseWriter{} +var _ ResponseWriter = (*responseWriter)(nil) func (w *responseWriter) reset(writer http.ResponseWriter) { w.ResponseWriter = writer diff --git a/routergroup.go b/routergroup.go index 27308bc8..dfbdd7b8 100644 --- a/routergroup.go +++ b/routergroup.go @@ -58,7 +58,7 @@ type RouterGroup struct { root bool } -var _ IRouter = &RouterGroup{} +var _ IRouter = (*RouterGroup)(nil) // Use adds middleware to the group, see example code in GitHub. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { From 8b9c55e8b059a1ffcc69e3b5dc0b9d583958811f Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 6 Nov 2022 17:02:40 +0800 Subject: [PATCH 009/332] fix(route): redirectSlash bug (#3227) fixes https://github.com/gin-gonic/gin/issues/2959 fixes https://github.com/gin-gonic/gin/issues/2282 fixes https://github.com/gin-gonic/gin/issues/2211 --- tree.go | 9 ++++++++- tree_test.go | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tree.go b/tree.go index 956bf4dd..3f34b8ee 100644 --- a/tree.go +++ b/tree.go @@ -107,7 +107,8 @@ func countSections(path string) uint16 { type nodeType uint8 const ( - root nodeType = iota + 1 + static nodeType = iota + root param catchAll ) @@ -173,6 +174,7 @@ walk: child := node{ path: n.path[i:], wildChild: n.wildChild, + nType: static, indices: n.indices, children: n.children, handlers: n.handlers, @@ -604,6 +606,11 @@ walk: // Outer loop for walking the tree return } + if path == "/" && n.nType == static { + value.tsr = true + return + } + // No handle found. Check if a handle for this path + a // trailing slash exists for trailing slash recommendation for i, c := range []byte(n.indices) { diff --git a/tree_test.go b/tree_test.go index 085b5803..2005738e 100644 --- a/tree_test.go +++ b/tree_test.go @@ -684,6 +684,26 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { } } +func TestRedirectTrailingSlash(t *testing.T) { + var data = []struct { + path string + }{ + {"/hello/:name"}, + {"/hello/:name/123"}, + {"/hello/:name/234"}, + } + + node := &node{} + for _, item := range data { + node.addRoute(item.path, fakeHandler("test")) + } + + value := node.getValue("/hello/abx/", nil, getSkippedNodes(), false) + if value.tsr != true { + t.Fatalf("want true, is false") + } +} + func TestTreeFindCaseInsensitivePath(t *testing.T) { tree := &node{} From 971fe21876e491f2597a390522adc849272e3bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=93=88=E5=93=88?= <31426858+wanghaha-dev@users.noreply.github.com> Date: Sun, 6 Nov 2022 17:05:10 +0800 Subject: [PATCH 010/332] docs(comment): Modify comment syntax error (#3389) --- CHANGELOG.md | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc51a8c..a682c8c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,7 +113,7 @@ * chore(performance): improve countParams ([#2378](https://github.com/gin-gonic/gin/pull/2378)) * Remove some functions that have the same effect as the bytes package ([#2387](https://github.com/gin-gonic/gin/pull/2387)) * update:SetMode function ([#2321](https://github.com/gin-gonic/gin/pull/2321)) -* remove a unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391)) +* remove an unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391)) * Add a redirect sample for POST method ([#2389](https://github.com/gin-gonic/gin/pull/2389)) * Add CustomRecovery builtin middleware ([#2322](https://github.com/gin-gonic/gin/pull/2322)) * binding: avoid 2038 problem on 32-bit architectures ([#2450](https://github.com/gin-gonic/gin/pull/2450)) diff --git a/README.md b/README.md index 960c66dc..760bde02 100644 --- a/README.md +++ b/README.md @@ -293,7 +293,7 @@ func main() { router := gin.Default() // Query string parameters are parsed using the existing underlying request object. - // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe + // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") @@ -693,7 +693,7 @@ Also, Gin provides two sets of methods for binding: When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. -You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned. +You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has an empty value when binding, an error will be returned. ```go // Binding from JSON @@ -2096,7 +2096,7 @@ func validate(obj interface{}) error { } // Now we can do this!!! -// FormA is a external type that we can't modify it's tag +// FormA is an external type that we can't modify it's tag type FormA struct { FieldA string `url:"field_a"` } From 55e27f12465e058058180280d5f0bdc473eb3302 Mon Sep 17 00:00:00 2001 From: RoCry Date: Sun, 6 Nov 2022 17:08:11 +0800 Subject: [PATCH 011/332] fix(engine): missing route params for CreateTestContext (#2778) (#2803) --- context_test.go | 16 +++++++++++++++- gin.go | 6 +++--- test_helpers.go | 10 +++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/context_test.go b/context_test.go index cc55cb01..85e0a616 100644 --- a/context_test.go +++ b/context_test.go @@ -146,7 +146,7 @@ func TestSaveUploadedCreateFailed(t *testing.T) { func TestContextReset(t *testing.T) { router := New() - c := router.allocateContext() + c := router.allocateContext(0) assert.Equal(t, c.engine, router) c.index = 2 @@ -2354,3 +2354,17 @@ func TestContextAddParam(t *testing.T) { assert.Equal(t, ok, true) assert.Equal(t, value, v) } + +func TestCreateTestContextWithRouteParams(t *testing.T) { + w := httptest.NewRecorder() + engine := New() + engine.GET("/:action/:name", func(ctx *Context) { + ctx.String(http.StatusOK, "%s %s", ctx.Param("action"), ctx.Param("name")) + }) + c := CreateTestContextOnly(w, engine) + c.Request, _ = http.NewRequest(http.MethodGet, "/hello/gin", nil) + engine.HandleContext(c) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "hello gin", w.Body.String()) +} diff --git a/gin.go b/gin.go index a2e2e67c..35159d03 100644 --- a/gin.go +++ b/gin.go @@ -203,7 +203,7 @@ func New() *Engine { } engine.RouterGroup.engine = engine engine.pool.New = func() any { - return engine.allocateContext() + return engine.allocateContext(engine.maxParams) } return engine } @@ -225,8 +225,8 @@ func (engine *Engine) Handler() http.Handler { return h2c.NewHandler(engine, h2s) } -func (engine *Engine) allocateContext() *Context { - v := make(Params, 0, engine.maxParams) +func (engine *Engine) allocateContext(maxParams uint16) *Context { + v := make(Params, 0, maxParams) skippedNodes := make([]skippedNode, 0, engine.maxSections) return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes} } diff --git a/test_helpers.go b/test_helpers.go index b3be93b4..7508c5c9 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -9,7 +9,15 @@ import "net/http" // CreateTestContext returns a fresh engine and context for testing purposes func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { r = New() - c = r.allocateContext() + c = r.allocateContext(0) + c.reset() + c.writermem.reset(w) + return +} + +// CreateTestContextOnly returns a fresh context base on the engine for testing purposes +func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) { + c = r.allocateContext(r.maxParams) c.reset() c.writermem.reset(w) return From 3a6865ac03871578cd68dcf2ebe64205b325c1b1 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Sun, 6 Nov 2022 17:09:25 +0800 Subject: [PATCH 012/332] docs(readme): The krakend is rename to lura (#3377) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 760bde02..8a6b262a 100644 --- a/README.md +++ b/README.md @@ -2364,7 +2364,7 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. * [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. -* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. +* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. From 8edb7a71a17061bbaa85db53bb8ba6daad3c3aa8 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Sun, 6 Nov 2022 17:10:33 +0800 Subject: [PATCH 013/332] docs(readme): Update some go website links (#3376) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8a6b262a..6445e97d 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. You first need [Go](https://golang.org/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin. +1. You first need [Go](https://go.dev/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin. ```sh go get -u github.com/gin-gonic/gin @@ -678,7 +678,7 @@ func main() { To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz). -Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). +Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. @@ -1820,7 +1820,7 @@ Alternatives: #### Manually -In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown). +In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://pkg.go.dev/net/http#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown). ```go // +build go1.8 @@ -2116,7 +2116,7 @@ func ListHandler(s *Service) func(ctx *gin.Context) { ### http2 server push -http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. +http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information. ```go package main From aefae309a4fc197ce5d57cd8391562b6d2a63a95 Mon Sep 17 00:00:00 2001 From: jessetang <1430482733@qq.com> Date: Sun, 6 Nov 2022 17:12:11 +0800 Subject: [PATCH 014/332] fix: test fmt.Println replace t.Error (#3328) --- gin_test.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/gin_test.go b/gin_test.go index 4fac677a..5ab2430c 100644 --- a/gin_test.go +++ b/gin_test.go @@ -73,7 +73,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -83,7 +83,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { func TestH2c(t *testing.T) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { - fmt.Println(err) + t.Error(err) } r := Default() r.UseH2C = true @@ -93,7 +93,7 @@ func TestH2c(t *testing.T) { go func() { err := http.Serve(ln, r.Handler()) if err != nil { - fmt.Println(err) + t.Log(err) } }() defer ln.Close() @@ -111,7 +111,7 @@ func TestH2c(t *testing.T) { res, err := httpClient.Get(url) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -131,7 +131,7 @@ func TestLoadHTMLGlobTestMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -151,7 +151,7 @@ func TestLoadHTMLGlobReleaseMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -178,7 +178,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) { client := &http.Client{Transport: tr} res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -198,7 +198,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -229,7 +229,7 @@ func TestLoadHTMLFilesTestMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -249,7 +249,7 @@ func TestLoadHTMLFilesDebugMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -269,7 +269,7 @@ func TestLoadHTMLFilesReleaseMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -296,7 +296,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) { client := &http.Client{Transport: tr} res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -316,7 +316,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) From c4b3c2c23a5ab25a80e8648f504065922631593e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Nov 2022 19:53:08 +0800 Subject: [PATCH 015/332] chore(deps): bump github.com/stretchr/testify from 1.8.0 to 1.8.1 (#3373) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 1c64cf1b..ebc1bd1a 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.16 github.com/pelletier/go-toml/v2 v2.0.2 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20221004154528-8021a29435af google.golang.org/protobuf v1.28.1 diff --git a/go.sum b/go.sum index 35bfd2e9..acdf805d 100644 --- a/go.sum +++ b/go.sum @@ -55,13 +55,15 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M= github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= From b682b8a54e165088efa71d8b941e7ad28cde348b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Nov 2022 19:53:33 +0800 Subject: [PATCH 016/332] chore(deps): bump golangci/golangci-lint-action from 3.2.0 to 3.3.0 (#3372) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.2.0...v3.3.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 18b36d0e..45ae932e 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.2.0 + uses: golangci/golangci-lint-action@v3.3.0 with: version: v1.48.0 args: --verbose From 212267d6716324536be15512c0b6ed080794b798 Mon Sep 17 00:00:00 2001 From: lgbgbl <65756378+lgbgbl@users.noreply.github.com> Date: Tue, 8 Nov 2022 19:54:48 +0800 Subject: [PATCH 017/332] fix: fix typo in comment (#3371) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index b66b8adc..ac9db17e 100644 --- a/context.go +++ b/context.go @@ -558,7 +558,7 @@ func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { return c.get(c.formCache, key) } -// get is an internal method and returns a map which satisfy conditions. +// get is an internal method and returns a map which satisfies conditions. func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) { dicts := make(map[string]string) exist := false From a0acf1df2814fcd828cb2d7128f2f4e2136d3fac Mon Sep 17 00:00:00 2001 From: mstmdev Date: Wed, 9 Nov 2022 14:50:46 +0800 Subject: [PATCH 018/332] docs(readme): Using the embed package as a recommended example that build a single binary with templates (#3379) --- README.md | 72 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 6445e97d..48a0a13c 100644 --- a/README.md +++ b/README.md @@ -1884,48 +1884,56 @@ func main() { ### Build a single binary with templates -You can build a server into a single binary containing templates by using [go-assets][]. - -[go-assets]: https://github.com/jessevdk/go-assets +You can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package. ```go +package main + +import ( + "embed" + "html/template" + "net/http" + + "github.com/gin-gonic/gin" +) + +//go:embed assets/* templates/* +var f embed.FS + func main() { - r := gin.New() + router := gin.Default() + templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl")) + router.SetHTMLTemplate(templ) - t, err := loadTemplate() - if err != nil { - panic(err) - } - r.SetHTMLTemplate(t) + // example: /public/assets/images/example.png + router.StaticFS("/public", http.FS(f)) - r.GET("/", func(c *gin.Context) { - c.HTML(http.StatusOK, "/html/index.tmpl",nil) + router.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.tmpl", gin.H{ + "title": "Main website", + }) }) - r.Run(":8080") -} -// loadTemplate loads templates embedded by go-assets-builder -func loadTemplate() (*template.Template, error) { - t := template.New("") - for name, file := range Assets.Files { - defer file.Close() - if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { - continue - } - h, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - t, err = t.New(name).Parse(string(h)) - if err != nil { - return nil, err - } - } - return t, nil + router.GET("/foo", func(c *gin.Context) { + c.HTML(http.StatusOK, "bar.tmpl", gin.H{ + "title": "Foo website", + }) + }) + + router.GET("favicon.ico", func(c *gin.Context) { + file, _ := f.ReadFile("assets/favicon.ico") + c.Data( + http.StatusOK, + "image/x-icon", + file, + ) + }) + + router.Run(":8080") } ``` -See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary` directory. +See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory. ### Bind form-data request with custom struct From 234a1d33f7b329a6d701a7b249167f72de57c901 Mon Sep 17 00:00:00 2001 From: gobai <38973236+go-bai@users.noreply.github.com> Date: Thu, 17 Nov 2022 22:34:37 +0800 Subject: [PATCH 019/332] docs(readme): Modify sample code bugs (#3394) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 48a0a13c..65492e52 100644 --- a/README.md +++ b/README.md @@ -1854,7 +1854,7 @@ func main() { // Initializing the server in a goroutine so that // it won't block the graceful shutdown handling below go func() { - if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Printf("listen: %s\n", err) } }() From 6150c488e73518f119cfed53094d6179a0d33bf7 Mon Sep 17 00:00:00 2001 From: Qt Date: Thu, 17 Nov 2022 22:35:55 +0800 Subject: [PATCH 020/332] remove deprecated of package io/ioutil (#3395) --- binding/binding_test.go | 9 ++++----- binding/multipart_form_mapping_test.go | 4 ++-- binding/protobuf.go | 4 ++-- context.go | 5 ++--- gin_integration_test.go | 4 ++-- gin_test.go | 24 ++++++++++++------------ recovery.go | 3 +-- routes_test.go | 5 ++--- 8 files changed, 27 insertions(+), 31 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index eae27802..9af4f88a 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -9,7 +9,6 @@ import ( "encoding/json" "errors" "io" - "io/ioutil" "mime/multipart" "net/http" "os" @@ -656,12 +655,12 @@ func TestBindingFormFilesMultipart(t *testing.T) { // file from os f, _ := os.Open("form.go") defer f.Close() - fileActual, _ := ioutil.ReadAll(f) + fileActual, _ := io.ReadAll(f) // file from multipart mf, _ := obj.File.Open() defer mf.Close() - fileExpect, _ := ioutil.ReadAll(mf) + fileExpect, _ := io.ReadAll(mf) assert.Equal(t, FormMultipart.Name(), "multipart/form-data") assert.Equal(t, obj.Foo, "bar") @@ -1347,13 +1346,13 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body obj := protoexample.Test{} req := requestWithBody("POST", path, body) - req.Body = ioutil.NopCloser(&hook{}) + req.Body = io.NopCloser(&hook{}) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) assert.Error(t, err) invalidobj := FooStruct{} - req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`)) + req.Body = io.NopCloser(strings.NewReader(`{"msg":"hello"}`)) req.Header.Add("Content-Type", MIMEPROTOBUF) err = b.Bind(req, &invalidobj) assert.Error(t, err) diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go index 99328603..4e97c0f0 100644 --- a/binding/multipart_form_mapping_test.go +++ b/binding/multipart_form_mapping_test.go @@ -6,7 +6,7 @@ package binding import ( "bytes" - "io/ioutil" + "io" "mime/multipart" "net/http" "testing" @@ -129,7 +129,7 @@ func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file test fl, err := fh.Open() assert.NoError(t, err) - body, err := ioutil.ReadAll(fl) + body, err := io.ReadAll(fl) assert.NoError(t, err) assert.Equal(t, string(file.Content), string(body)) diff --git a/binding/protobuf.go b/binding/protobuf.go index 44f2fdb9..57721fc9 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -6,7 +6,7 @@ package binding import ( "errors" - "io/ioutil" + "io" "net/http" "google.golang.org/protobuf/proto" @@ -19,7 +19,7 @@ func (protobufBinding) Name() string { } func (b protobufBinding) Bind(req *http.Request, obj any) error { - buf, err := ioutil.ReadAll(req.Body) + buf, err := io.ReadAll(req.Body) if err != nil { return err } diff --git a/context.go b/context.go index ac9db17e..c41c71ec 100644 --- a/context.go +++ b/context.go @@ -7,7 +7,6 @@ package gin import ( "errors" "io" - "io/ioutil" "log" "math" "mime/multipart" @@ -753,7 +752,7 @@ func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error } } if body == nil { - body, err = ioutil.ReadAll(c.Request.Body) + body, err = io.ReadAll(c.Request.Body) if err != nil { return err } @@ -872,7 +871,7 @@ func (c *Context) GetHeader(key string) string { // GetRawData returns stream data. func (c *Context) GetRawData() ([]byte, error) { - return ioutil.ReadAll(c.Request.Body) + return io.ReadAll(c.Request.Body) } // SetSameSite with cookie diff --git a/gin_integration_test.go b/gin_integration_test.go index b0532a25..02b96221 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -9,7 +9,7 @@ import ( "crypto/tls" "fmt" "html/template" - "io/ioutil" + "io" "net" "net/http" "net/http/httptest" @@ -43,7 +43,7 @@ func testRequest(t *testing.T, params ...string) { assert.NoError(t, err) defer resp.Body.Close() - body, ioerr := ioutil.ReadAll(resp.Body) + body, ioerr := io.ReadAll(resp.Body) assert.NoError(t, ioerr) var responseStatus = "200 OK" diff --git a/gin_test.go b/gin_test.go index 5ab2430c..8825ac7e 100644 --- a/gin_test.go +++ b/gin_test.go @@ -8,7 +8,7 @@ import ( "crypto/tls" "fmt" "html/template" - "io/ioutil" + "io" "net" "net/http" "net/http/httptest" @@ -76,7 +76,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -114,7 +114,7 @@ func TestH2c(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -134,7 +134,7 @@ func TestLoadHTMLGlobTestMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -154,7 +154,7 @@ func TestLoadHTMLGlobReleaseMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -181,7 +181,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -201,7 +201,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01", string(resp)) } @@ -232,7 +232,7 @@ func TestLoadHTMLFilesTestMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -252,7 +252,7 @@ func TestLoadHTMLFilesDebugMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -272,7 +272,7 @@ func TestLoadHTMLFilesReleaseMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -299,7 +299,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -319,7 +319,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01", string(resp)) } diff --git a/recovery.go b/recovery.go index 05b30d95..3a90f250 100644 --- a/recovery.go +++ b/recovery.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "log" "net" "net/http" @@ -121,7 +120,7 @@ func stack(skip int) []byte { // Print this much at least. If we can't find the source, it won't show. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) if file != lastFile { - data, err := ioutil.ReadFile(file) + data, err := os.ReadFile(file) if err != nil { continue } diff --git a/routes_test.go b/routes_test.go index d7034b22..cd8cf141 100644 --- a/routes_test.go +++ b/routes_test.go @@ -6,7 +6,6 @@ package gin import ( "fmt" - "io/ioutil" "net/http" "net/http/httptest" "os" @@ -294,7 +293,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) { func TestRouteStaticFile(t *testing.T) { // SETUP file testRoot, _ := os.Getwd() - f, err := ioutil.TempFile(testRoot, "") + f, err := os.CreateTemp(testRoot, "") if err != nil { t.Error(err) } @@ -329,7 +328,7 @@ func TestRouteStaticFile(t *testing.T) { func TestRouteStaticFileFS(t *testing.T) { // SETUP file testRoot, _ := os.Getwd() - f, err := ioutil.TempFile(testRoot, "") + f, err := os.CreateTemp(testRoot, "") if err != nil { t.Error(err) } From c629689591bf3f50650292f382f16ec9b6952904 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Thu, 17 Nov 2022 22:37:50 +0800 Subject: [PATCH 021/332] docs(readme): Add the TOML rendering example (#3400) --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 65492e52..103a53c7 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind Header](#bind-header) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) + - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering) - [SecureJSON](#securejson) - [JSONP](#jsonp) - [AsciiJSON](#asciijson) @@ -1114,7 +1114,7 @@ Test it with: curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile ``` -### XML, JSON, YAML and ProtoBuf rendering +### XML, JSON, YAML, TOML and ProtoBuf rendering ```go func main() { @@ -1148,6 +1148,10 @@ func main() { c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) + r.GET("/someTOML", func(c *gin.Context) { + c.TOML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + r.GET("/someProtoBuf", func(c *gin.Context) { reps := []int64{int64(1), int64(2)} label := "test" From 8fe209a447426233f55093af1d51d3964ca10654 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Nov 2022 22:38:19 +0800 Subject: [PATCH 022/332] chore(deps): bump golangci/golangci-lint-action from 3.3.0 to 3.3.1 (#3399) 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 45ae932e..7a4e61c6 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.3.0 + uses: golangci/golangci-lint-action@v3.3.1 with: version: v1.48.0 args: --verbose From 80cd679c43a3d6ed03957b6a3614f97d0af0751c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Nov 2022 15:34:18 +0800 Subject: [PATCH 023/332] chore(deps): bump github.com/pelletier/go-toml/v2 from 2.0.2 to 2.0.6 (#3408) Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.2 to 2.0.6. - [Release notes](https://github.com/pelletier/go-toml/releases) - [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml) - [Commits](https://github.com/pelletier/go-toml/compare/v2.0.2...v2.0.6) --- updated-dependencies: - dependency-name: github.com/pelletier/go-toml/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index ebc1bd1a..200a4403 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/goccy/go-json v0.9.11 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.16 - github.com/pelletier/go-toml/v2 v2.0.2 + github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20221004154528-8021a29435af diff --git a/go.sum b/go.sum index acdf805d..574e4a9a 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= -github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -60,7 +60,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= From cc367f9125516313a4b8d2da1eed5527b5420dbc Mon Sep 17 00:00:00 2001 From: Cookiery <33125275+Cookiery@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:15:31 +0800 Subject: [PATCH 024/332] docs(context): #3369 modify the annotation about Context.Param() (#3414) --- context.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index c41c71ec..737e4d7a 100644 --- a/context.go +++ b/context.go @@ -386,7 +386,9 @@ func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) // // router.GET("/user/:id", func(c *gin.Context) { // // a GET request to /user/john -// id := c.Param("id") // id == "john" +// id := c.Param("id") // id == "/john" +// // a GET request to /user/john/ +// id := c.Param("id") // id == "/john/" // }) func (c *Context) Param(key string) string { return c.Params.ByName(key) From 483ac2a63bff7e1104fbe236dacff774c09bc24d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Dec 2022 14:43:06 +0800 Subject: [PATCH 025/332] chore(deps): bump goreleaser/goreleaser-action from 3 to 4 (#3441) Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 3 to 4. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v3...v4) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 654585b9..3af3a455 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -24,7 +24,7 @@ jobs: go-version: 1.17 - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v3 + uses: goreleaser/goreleaser-action@v4 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser From f551d7d8c2930c0140781a284f4372490c365a43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Dec 2022 14:43:42 +0800 Subject: [PATCH 026/332] chore(deps): bump github.com/bytedance/sonic from 1.4.0 to 1.6.0 (#3442) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.4.0 to 1.6.0. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.4.0...v1.6.0) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 19 +++++-------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 200a4403..1929c959 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.4.0 + github.com/bytedance/sonic v1.6.0 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.1 github.com/goccy/go-json v0.9.11 @@ -18,7 +18,7 @@ require ( ) require ( - github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect diff --git a/go.sum b/go.sum index 574e4a9a..054058aa 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,9 @@ -github.com/bytedance/sonic v1.4.0 h1:d6vgPhwgHfpmEiz/9Fzea9fGzWY7RO1TQEySBiRwDLY= -github.com/bytedance/sonic v1.4.0/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.6.0 h1:j90DM/Ss1bmySEQYL2U4jRsUjJ+chASzCCZYxohJR60= +github.com/bytedance/sonic v1.6.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a h1:lmGPzuocwDxoPAMr9h16zoJY/USZR9jIh99nrmKk1uI= -github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 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= @@ -17,7 +18,6 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -63,15 +63,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M= -github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc= -github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= 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 v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= From d4caeee7c7672dca05e5033e9e30204b52d398e6 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Wed, 21 Dec 2022 14:44:36 +0800 Subject: [PATCH 027/332] Fix the GO-2022-1144 vulnerability (#3432) --- go.mod | 6 +++--- go.sum | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 1929c959..7a355e98 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.7 - golang.org/x/net v0.0.0-20221004154528-8021a29435af + golang.org/x/net v0.4.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -30,7 +30,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect - golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 054058aa..516c053c 100644 --- a/go.sum +++ b/go.sum @@ -74,18 +74,20 @@ golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= -golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 2285aa5430fb5e68bb55a89009fc4f5dd261b466 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 21 Dec 2022 15:02:00 +0800 Subject: [PATCH 028/332] docs(readme): release v1.8.2 version (#3420) * docs(readme): release v1.8.2 version * Update CHANGELOG.md --- CHANGELOG.md | 11 +++++++++++ version.go | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a682c8c6..2ab56179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Gin ChangeLog +## Gin v1.8.2 + +### Bugs + +* fix(route): redirectSlash bug ([#3227]((https://github.com/gin-gonic/gin/pull/3227))) +* fix(engine): missing route params for CreateTestContext ([#2778]((https://github.com/gin-gonic/gin/pull/2778))) ([#2803]((https://github.com/gin-gonic/gin/pull/2803))) + +### Security + +* Fix the GO-2022-1144 vulnerability ([#3432]((https://github.com/gin-gonic/gin/pull/3432))) + ## Gin v1.8.1 ### ENHANCEMENTS diff --git a/version.go b/version.go index 632ca7d1..37e27f27 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.8.1" +const Version = "v1.8.2" From 297b664cf8bbb3b9434e473cf976c98f00528ff7 Mon Sep 17 00:00:00 2001 From: lgbgbl <65756378+lgbgbl@users.noreply.github.com> Date: Thu, 22 Dec 2022 23:17:19 +0800 Subject: [PATCH 029/332] refactor: avoid calling strings.ToLower twice (#3433) --- recovery.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 3a90f250..2955c03a 100644 --- a/recovery.go +++ b/recovery.go @@ -62,7 +62,9 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { if ne, ok := err.(*net.OpError); ok { var se *os.SyscallError if errors.As(ne, &se) { - if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + seStr := strings.ToLower(se.Error()) + if strings.Contains(seStr, "broken pipe") || + strings.Contains(seStr, "connection reset by peer") { brokenPipe = true } } From e868fd1d3d350d7186efcdf71f24f56d8e2b8408 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Thu, 22 Dec 2022 23:18:47 +0800 Subject: [PATCH 030/332] test(TOML): Add some tests for the TOML render (#3401) --- render/render_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/render/render_test.go b/render/render_test.go index 3509db3c..c5c5375f 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -8,6 +8,7 @@ import ( "encoding/xml" "errors" "html/template" + "net" "net/http" "net/http/httptest" "strconv" @@ -254,6 +255,27 @@ func TestRenderYAMLFail(t *testing.T) { assert.Error(t, err) } +func TestRenderTOML(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]any{ + "foo": "bar", + "html": "", + } + (TOML{data}).WriteContentType(w) + assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) + + err := (TOML{data}).Render(w) + assert.NoError(t, err) + assert.Equal(t, "foo = 'bar'\nhtml = ''\n", w.Body.String()) + assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) +} + +func TestRenderTOMLFail(t *testing.T) { + w := httptest.NewRecorder() + err := (TOML{net.IPv4bcast}).Render(w) + assert.Error(t, err) +} + // test Protobuf rendering func TestRenderProtoBuf(t *testing.T) { w := httptest.NewRecorder() From 8659ab573cf7d26b2fa2a41e90075d84606188f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Dec 2022 13:49:44 +0800 Subject: [PATCH 031/332] chore(deps): bump github.com/goccy/go-json from 0.9.11 to 0.10.0 (#3424) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.9.11 to 0.10.0. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.9.11...v0.10.0) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 7a355e98..e9698339 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/bytedance/sonic v1.6.0 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.1 - github.com/goccy/go-json v0.9.11 + github.com/goccy/go-json v0.10.0 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.16 github.com/pelletier/go-toml/v2 v2.0.6 diff --git a/go.sum b/go.sum index 516c053c..6d8df64a 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= +github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From 82e1c53cc0a3b0bdb7baab6b95d05a7ec796f7a2 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 2 Jan 2023 10:40:25 +0800 Subject: [PATCH 032/332] docs(readme): move more example to docs/doc.md (#3449) --- README.md | 2344 ++------------------------------------------------- docs/doc.md | 2246 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2317 insertions(+), 2273 deletions(-) create mode 100644 docs/doc.md diff --git a/README.md b/README.md index 103a53c7..eccf814c 100644 --- a/README.md +++ b/README.md @@ -12,106 +12,47 @@ [![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 (Golang). 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 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. -## Contents +**The key features of Gin are:** -- [Gin Web Framework](#gin-web-framework) - - [Contents](#contents) - - [Installation](#installation) - - [Quick start](#quick-start) - - [Benchmarks](#benchmarks) - - [Gin v1. stable](#gin-v1-stable) - - [Build with json replacement](#build-with-json-replacement) - - [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature) - - [API Examples](#api-examples) - - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) - - [Parameters in path](#parameters-in-path) - - [Querystring parameters](#querystring-parameters) - - [Multipart/Urlencoded Form](#multiparturlencoded-form) - - [Another example: query + post form](#another-example-query--post-form) - - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) - - [Upload files](#upload-files) - - [Single file](#single-file) - - [Multiple files](#multiple-files) - - [Grouping routes](#grouping-routes) - - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - - [Using middleware](#using-middleware) - - [Custom Recovery behavior](#custom-recovery-behavior) - - [How to write log file](#how-to-write-log-file) - - [Custom Log Format](#custom-log-format) - - [Controlling Log output coloring](#controlling-log-output-coloring) - - [Model binding and validation](#model-binding-and-validation) - - [Custom Validators](#custom-validators) - - [Only Bind Query String](#only-bind-query-string) - - [Bind Query String or Post Data](#bind-query-string-or-post-data) - - [Bind Uri](#bind-uri) - - [Bind Header](#bind-header) - - [Bind HTML checkboxes](#bind-html-checkboxes) - - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering) - - [SecureJSON](#securejson) - - [JSONP](#jsonp) - - [AsciiJSON](#asciijson) - - [PureJSON](#purejson) - - [Serving static files](#serving-static-files) - - [Serving data from file](#serving-data-from-file) - - [Serving data from reader](#serving-data-from-reader) - - [HTML rendering](#html-rendering) - - [Custom Template renderer](#custom-template-renderer) - - [Custom Delimiters](#custom-delimiters) - - [Custom Template Funcs](#custom-template-funcs) - - [Multitemplate](#multitemplate) - - [Redirects](#redirects) - - [Custom Middleware](#custom-middleware) - - [Using BasicAuth() middleware](#using-basicauth-middleware) - - [Goroutines inside a middleware](#goroutines-inside-a-middleware) - - [Custom HTTP configuration](#custom-http-configuration) - - [Support Let's Encrypt](#support-lets-encrypt) - - [Run multiple service using Gin](#run-multiple-service-using-gin) - - [Graceful shutdown or restart](#graceful-shutdown-or-restart) - - [Third-party packages](#third-party-packages) - - [Manually](#manually) - - [Build a single binary with templates](#build-a-single-binary-with-templates) - - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - - [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag) - - [http2 server push](#http2-server-push) - - [Define format for the log of routes](#define-format-for-the-log-of-routes) - - [Set and get a cookie](#set-and-get-a-cookie) - - [Don't trust all proxies](#dont-trust-all-proxies) - - [Testing](#testing) - - [Users](#users) +- Zero allocation router +- Fast +- Middleware support +- Crash-free +- JSON validation +- Routes grouping +- Error management +- Rendering built-in +- Extendable -## Installation -To install Gin package, you need to install Go and set your Go workspace first. +## Getting started -1. You first need [Go](https://go.dev/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin. +### Prerequisites + +- **[Go](https://go.dev/)**: ~~any one of the **three latest major** [releases](https://go.dev/doc/devel/release)~~ (now version **1.16+** is required). + +### Getting Gin + +With [Go module](https://github.com/golang/go/wiki/Modules) support, simply add the following import -```sh -go get -u github.com/gin-gonic/gin ``` - -2. Import it in your code: - -```go import "github.com/gin-gonic/gin" ``` -3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. +to your code, and then `go [build|run|test]` will automatically fetch the necessary dependencies. -```go -import "net/http" -``` - -## Quick start +Otherwise, run the following Go command to install the `gin` package: ```sh -# assume the following codes in example.go file -$ cat example.go +$ go get -u github.com/gin-gonic/gin ``` +### Running Gin + +First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`: + ```go package main @@ -132,16 +73,48 @@ func main() { } ``` +And use the Go command to run the demo: + ``` -# run example.go and visit 0.0.0.0:8080/ping (for windows "localhost:8080/ping") on browser +# run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go ``` +### Learn more examples + +#### Quick Start + +Learn and practice more examples, please read the [Gin Quick Start](docs/doc.md) which includes API examples and builds tag. + +#### Examples + +A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. + + +## Documentation + +See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. + +All documentation is available on the Gin website. + +- [English](https://gin-gonic.com/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/) + +### Articles about Gin + +A curated list of awesome Gin framework. + +- [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin) + ## Benchmarks -Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) - -[See all benchmarks](/BENCHMARKS.md) +Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks details](/BENCHMARKS.md). | Benchmark name | (1) | (2) | (3) | (4) | | ------------------------------ | ---------:| ---------------:| ------------:| ---------------:| @@ -181,2193 +154,11 @@ 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 -## Gin v1. stable -- [x] Zero allocation router. -- [x] Still the fastest http router and framework. From routing to writing. -- [x] Complete suite of unit tests. -- [x] Battle tested. -- [x] API frozen, new releases will not break your code. +## Middlewares -## Build with json replacement +You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). -Gin uses `encoding/json` as default json package but you can change it by build from other tags. - -[jsoniter](https://github.com/json-iterator/go) - -```sh -go build -tags=jsoniter . -``` - -[go-json](https://github.com/goccy/go-json) - -```sh -go build -tags=go_json . -``` - -[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.) - -```sh -$ go build -tags="sonic avx" . -``` - -## Build without `MsgPack` rendering feature - -Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag. - -```sh -go build -tags=nomsgpack . -``` - -This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852). - -## API Examples - -You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). - -### Using GET, POST, PUT, PATCH, DELETE and OPTIONS - -```go -func main() { - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/someGet", getting) - router.POST("/somePost", posting) - router.PUT("/somePut", putting) - router.DELETE("/someDelete", deleting) - router.PATCH("/somePatch", patching) - router.HEAD("/someHead", head) - router.OPTIONS("/someOptions", options) - - // By default it serves on :8080 unless a - // PORT environment variable was defined. - router.Run() - // router.Run(":3000") for a hard coded port -} -``` - -### Parameters in path - -```go -func main() { - router := gin.Default() - - // This handler will match /user/john but will not match /user/ or /user - router.GET("/user/:name", func(c *gin.Context) { - name := c.Param("name") - c.String(http.StatusOK, "Hello %s", name) - }) - - // However, this one will match /user/john/ and also /user/john/send - // If no other routers match /user/john, it will redirect to /user/john/ - router.GET("/user/:name/*action", func(c *gin.Context) { - name := c.Param("name") - action := c.Param("action") - message := name + " is " + action - c.String(http.StatusOK, message) - }) - - // For each matched request Context will hold the route definition - router.POST("/user/:name/*action", func(c *gin.Context) { - b := c.FullPath() == "/user/:name/*action" // true - c.String(http.StatusOK, "%t", b) - }) - - // This handler will add a new router for /user/groups. - // Exact routes are resolved before param routes, regardless of the order they were defined. - // Routes starting with /user/groups are never interpreted as /user/:name/... routes - router.GET("/user/groups", func(c *gin.Context) { - c.String(http.StatusOK, "The available groups are [...]") - }) - - router.Run(":8080") -} -``` - -### Querystring parameters - -```go -func main() { - router := gin.Default() - - // Query string parameters are parsed using the existing underlying request object. - // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe - router.GET("/welcome", func(c *gin.Context) { - firstname := c.DefaultQuery("firstname", "Guest") - lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") - - c.String(http.StatusOK, "Hello %s %s", firstname, lastname) - }) - router.Run(":8080") -} -``` - -### Multipart/Urlencoded Form - -```go -func main() { - router := gin.Default() - - router.POST("/form_post", func(c *gin.Context) { - message := c.PostForm("message") - nick := c.DefaultPostForm("nick", "anonymous") - - c.JSON(http.StatusOK, gin.H{ - "status": "posted", - "message": message, - "nick": nick, - }) - }) - router.Run(":8080") -} -``` - -### Another example: query + post form - -```sh -POST /post?id=1234&page=1 HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -name=manu&message=this_is_great -``` - -```go -func main() { - router := gin.Default() - - router.POST("/post", func(c *gin.Context) { - - id := c.Query("id") - page := c.DefaultQuery("page", "0") - name := c.PostForm("name") - message := c.PostForm("message") - - fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) - }) - router.Run(":8080") -} -``` - -```sh -id: 1234; page: 1; name: manu; message: this_is_great -``` - -### Map as querystring or postform parameters - -```sh -POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -names[first]=thinkerou&names[second]=tianou -``` - -```go -func main() { - router := gin.Default() - - router.POST("/post", func(c *gin.Context) { - - ids := c.QueryMap("ids") - names := c.PostFormMap("names") - - fmt.Printf("ids: %v; names: %v", ids, names) - }) - router.Run(":8080") -} -``` - -```sh -ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou] -``` - -### Upload files - -#### Single file - -References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single). - -`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) - -> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. - -```go -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // Single file - file, _ := c.FormFile("file") - log.Println(file.Filename) - - // Upload the file to specific dst. - c.SaveUploadedFile(file, dst) - - c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) - }) - router.Run(":8080") -} -``` - -How to `curl`: - -```bash -curl -X POST http://localhost:8080/upload \ - -F "file=@/Users/appleboy/test.zip" \ - -H "Content-Type: multipart/form-data" -``` - -#### Multiple files - -See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). - -```go -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // Multipart form - form, _ := c.MultipartForm() - files := form.File["upload[]"] - - for _, file := range files { - log.Println(file.Filename) - - // Upload the file to specific dst. - c.SaveUploadedFile(file, dst) - } - c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) - }) - router.Run(":8080") -} -``` - -How to `curl`: - -```bash -curl -X POST http://localhost:8080/upload \ - -F "upload[]=@/Users/appleboy/test1.zip" \ - -F "upload[]=@/Users/appleboy/test2.zip" \ - -H "Content-Type: multipart/form-data" -``` - -### Grouping routes - -```go -func main() { - router := gin.Default() - - // Simple group: v1 - v1 := router.Group("/v1") - { - v1.POST("/login", loginEndpoint) - v1.POST("/submit", submitEndpoint) - v1.POST("/read", readEndpoint) - } - - // Simple group: v2 - v2 := router.Group("/v2") - { - v2.POST("/login", loginEndpoint) - v2.POST("/submit", submitEndpoint) - v2.POST("/read", readEndpoint) - } - - router.Run(":8080") -} -``` - -### Blank Gin without middleware by default - -Use - -```go -r := gin.New() -``` - -instead of - -```go -// Default With the Logger and Recovery middleware already attached -r := gin.Default() -``` - -### Using middleware - -```go -func main() { - // Creates a router without any middleware by default - r := gin.New() - - // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. - // By default gin.DefaultWriter = os.Stdout - r.Use(gin.Logger()) - - // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.Recovery()) - - // Per route middleware, you can add as many as you desire. - r.GET("/benchmark", MyBenchLogger(), benchEndpoint) - - // Authorization group - // authorized := r.Group("/", AuthRequired()) - // exactly the same as: - authorized := r.Group("/") - // per group middleware! in this case we use the custom created - // AuthRequired() middleware just in the "authorized" group. - authorized.Use(AuthRequired()) - { - authorized.POST("/login", loginEndpoint) - authorized.POST("/submit", submitEndpoint) - authorized.POST("/read", readEndpoint) - - // nested group - testing := authorized.Group("testing") - // visit 0.0.0.0:8080/testing/analytics - testing.GET("/analytics", analyticsEndpoint) - } - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Custom Recovery behavior - -```go -func main() { - // Creates a router without any middleware by default - r := gin.New() - - // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. - // By default gin.DefaultWriter = os.Stdout - r.Use(gin.Logger()) - - // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { - if err, ok := recovered.(string); ok { - c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) - } - c.AbortWithStatus(http.StatusInternalServerError) - })) - - r.GET("/panic", func(c *gin.Context) { - // panic with a string -- the custom middleware could save this to a database or report it to the user - panic("foo") - }) - - r.GET("/", func(c *gin.Context) { - c.String(http.StatusOK, "ohai") - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### How to write log file - -```go -func main() { - // Disable Console Color, you don't need console color when writing the logs to file. - gin.DisableConsoleColor() - - // Logging to a file. - f, _ := os.Create("gin.log") - gin.DefaultWriter = io.MultiWriter(f) - - // Use the following code if you need to write the logs to file and console at the same time. - // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) - - router := gin.Default() - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - ย  ย router.Run(":8080") -} -``` - -### Custom Log Format - -```go -func main() { - router := gin.New() - - // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter - // By default gin.DefaultWriter = os.Stdout - router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { - - // your custom format - return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", - param.ClientIP, - param.TimeStamp.Format(time.RFC1123), - param.Method, - param.Path, - param.Request.Proto, - param.StatusCode, - param.Latency, - param.Request.UserAgent(), - param.ErrorMessage, - ) - })) - router.Use(gin.Recovery()) - - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - router.Run(":8080") -} -``` - -Sample Output - -```sh -::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767ยตs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " -``` - -### Controlling Log output coloring - -By default, logs output on console should be colorized depending on the detected TTY. - -Never colorize logs: - -```go -func main() { - // Disable log's color - gin.DisableConsoleColor() - - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - router.Run(":8080") -} -``` - -Always colorize logs: - -```go -func main() { - // Force log's color - gin.ForceConsoleColor() - - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - router.Run(":8080") -} -``` - -### Model binding and validation - -To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz). - -Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). - -Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. - -Also, Gin provides two sets of methods for binding: - -- **Type** - Must bind - - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML` - - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. -- **Type** - Should bind - - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`, - - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. - -When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. - -You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has an empty value when binding, an error will be returned. - -```go -// Binding from JSON -type Login struct { - User string `form:"user" json:"user" xml:"user" binding:"required"` - Password string `form:"password" json:"password" xml:"password" binding:"required"` -} - -func main() { - router := gin.Default() - - // Example for binding JSON ({"user": "manu", "password": "123"}) - router.POST("/loginJSON", func(c *gin.Context) { - var json Login - if err := c.ShouldBindJSON(&json); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if json.User != "manu" || json.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding XML ( - // - // - // manu - // 123 - // ) - router.POST("/loginXML", func(c *gin.Context) { - var xml Login - if err := c.ShouldBindXML(&xml); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if xml.User != "manu" || xml.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding a HTML form (user=manu&password=123) - router.POST("/loginForm", func(c *gin.Context) { - var form Login - // This will infer what binder to use depending on the content-type header. - if err := c.ShouldBind(&form); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if form.User != "manu" || form.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Listen and serve on 0.0.0.0:8080 - router.Run(":8080") -} -``` - -Sample request - -```sh -$ curl -v -X POST \ - http://localhost:8080/loginJSON \ - -H 'content-type: application/json' \ - -d '{ "user": "manu" }' -> POST /loginJSON HTTP/1.1 -> Host: localhost:8080 -> User-Agent: curl/7.51.0 -> Accept: */* -> content-type: application/json -> Content-Length: 18 -> -* upload completely sent off: 18 out of 18 bytes -< HTTP/1.1 400 Bad Request -< Content-Type: application/json; charset=utf-8 -< Date: Fri, 04 Aug 2017 03:51:31 GMT -< Content-Length: 100 -< -{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} -``` - -Skip validate: when running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. - -### Custom Validators - -It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). - -```go -package main - -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" - "github.com/go-playground/validator/v10" -) - -// Booking contains binded and validated data. -type Booking struct { - CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` - CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` -} - -var bookableDate validator.Func = func(fl validator.FieldLevel) bool { - date, ok := fl.Field().Interface().(time.Time) - if ok { - today := time.Now() - if today.After(date) { - return false - } - } - return true -} - -func main() { - route := gin.Default() - - if v, ok := binding.Validator.Engine().(*validator.Validate); ok { - v.RegisterValidation("bookabledate", bookableDate) - } - - route.GET("/bookable", getBookable) - route.Run(":8085") -} - -func getBookable(c *gin.Context) { - var b Booking - if err := c.ShouldBindWith(&b, binding.Query); err == nil { - c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) - } else { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - } -} -``` - -```console -$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17" -{"message":"Booking dates are valid!"} - -$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09" -{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"} - -$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10" -{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}% -``` - -[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. -See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. - -### Only Bind Query String - -`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). - -```go -package main - -import ( - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -type Person struct { - Name string `form:"name"` - Address string `form:"address"` -} - -func main() { - route := gin.Default() - route.Any("/testing", startPage) - route.Run(":8085") -} - -func startPage(c *gin.Context) { - var person Person - if c.ShouldBindQuery(&person) == nil { - log.Println("====== Only Bind By Query String ======") - log.Println(person.Name) - log.Println(person.Address) - } - c.String(http.StatusOK, "Success") -} - -``` - -### Bind Query String or Post Data - -See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). - -```go -package main - -import ( - "log" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -type Person struct { - Name string `form:"name"` - Address string `form:"address"` - Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` - CreateTime time.Time `form:"createTime" time_format:"unixNano"` - UnixTime time.Time `form:"unixTime" time_format:"unix"` -} - -func main() { - route := gin.Default() - route.GET("/testing", startPage) - route.Run(":8085") -} - -func startPage(c *gin.Context) { - var person Person - // If `GET`, only `Form` binding engine (`query`) used. - // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). - // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88 - if c.ShouldBind(&person) == nil { - log.Println(person.Name) - log.Println(person.Address) - log.Println(person.Birthday) - log.Println(person.CreateTime) - log.Println(person.UnixTime) - } - - c.String(http.StatusOK, "Success") -} -``` - -Test it with: - -```sh -curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" -``` - -### Bind Uri - -See the [detail information](https://github.com/gin-gonic/gin/issues/846). - -```go -package main - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -type Person struct { - ID string `uri:"id" binding:"required,uuid"` - Name string `uri:"name" binding:"required"` -} - -func main() { - route := gin.Default() - route.GET("/:name/:id", func(c *gin.Context) { - var person Person - if err := c.ShouldBindUri(&person); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID}) - }) - route.Run(":8088") -} -``` - -Test it with: - -```sh -curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 -curl -v localhost:8088/thinkerou/not-uuid -``` - -### Bind Header - -```go -package main - -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" -) - -type testHeader struct { - Rate int `header:"Rate"` - Domain string `header:"Domain"` -} - -func main() { - r := gin.Default() - r.GET("/", func(c *gin.Context) { - h := testHeader{} - - if err := c.ShouldBindHeader(&h); err != nil { - c.JSON(http.StatusOK, err) - } - - fmt.Printf("%#v\n", h) - c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain}) - }) - - r.Run() - -// client -// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ -// output -// {"Domain":"music","Rate":300} -} -``` - -### Bind HTML checkboxes - -See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) - -main.go - -```go -... - -type myForm struct { - Colors []string `form:"colors[]"` -} - -... - -func formHandler(c *gin.Context) { - var fakeForm myForm - c.ShouldBind(&fakeForm) - c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors}) -} - -... - -``` - -form.html - -```html -
-

Check some colors

- - - - - - - -
-``` - -result: - -```json -{"color":["red","green","blue"]} -``` - -### Multipart/Urlencoded binding - -```go -type ProfileForm struct { - Name string `form:"name" binding:"required"` - Avatar *multipart.FileHeader `form:"avatar" binding:"required"` - - // or for multiple files - // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"` -} - -func main() { - router := gin.Default() - router.POST("/profile", func(c *gin.Context) { - // you can bind multipart form with explicit binding declaration: - // c.ShouldBindWith(&form, binding.Form) - // or you can simply use autobinding with ShouldBind method: - var form ProfileForm - // in this case proper binding will be automatically selected - if err := c.ShouldBind(&form); err != nil { - c.String(http.StatusBadRequest, "bad request") - return - } - - err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename) - if err != nil { - c.String(http.StatusInternalServerError, "unknown error") - return - } - - // db.Save(&form) - - c.String(http.StatusOK, "ok") - }) - router.Run(":8080") -} -``` - -Test it with: - -```sh -curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile -``` - -### XML, JSON, YAML, TOML and ProtoBuf rendering - -```go -func main() { - r := gin.Default() - - // gin.H is a shortcut for map[string]interface{} - r.GET("/someJSON", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/moreJSON", func(c *gin.Context) { - // You also can use a struct - var msg struct { - Name string `json:"user"` - Message string - Number int - } - msg.Name = "Lena" - msg.Message = "hey" - msg.Number = 123 - // Note that msg.Name becomes "user" in the JSON - // Will output : {"user": "Lena", "Message": "hey", "Number": 123} - c.JSON(http.StatusOK, msg) - }) - - r.GET("/someXML", func(c *gin.Context) { - c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/someYAML", func(c *gin.Context) { - c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/someTOML", func(c *gin.Context) { - c.TOML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/someProtoBuf", func(c *gin.Context) { - reps := []int64{int64(1), int64(2)} - label := "test" - // The specific definition of protobuf is written in the testdata/protoexample file. - data := &protoexample.Test{ - Label: &label, - Reps: reps, - } - // Note that data becomes binary data in the response - // Will output protoexample.Test protobuf serialized data - c.ProtoBuf(http.StatusOK, data) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -#### SecureJSON - -Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to response body if the given struct is array values. - -```go -func main() { - r := gin.Default() - - // You can also use your own secure json prefix - // r.SecureJsonPrefix(")]}',\n") - - r.GET("/someJSON", func(c *gin.Context) { - names := []string{"lena", "austin", "foo"} - - // Will output : while(1);["lena","austin","foo"] - c.SecureJSON(http.StatusOK, names) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -#### JSONP - -Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. - -```go -func main() { - r := gin.Default() - - r.GET("/JSONP", func(c *gin.Context) { - data := gin.H{ - "foo": "bar", - } - - //callback is x - // Will output : x({\"foo\":\"bar\"}) - c.JSONP(http.StatusOK, data) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") - - // client - // curl http://127.0.0.1:8080/JSONP?callback=x -} -``` - -#### AsciiJSON - -Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters. - -```go -func main() { - r := gin.Default() - - r.GET("/someJSON", func(c *gin.Context) { - data := gin.H{ - "lang": "GO่ฏญ่จ€", - "tag": "
", - } - - // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} - c.AsciiJSON(http.StatusOK, data) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -#### PureJSON - -Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. -This feature is unavailable in Go 1.6 and lower. - -```go -func main() { - r := gin.Default() - - // Serves unicode entities - r.GET("/json", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "html": "Hello, world!", - }) - }) - - // Serves literal characters - r.GET("/purejson", func(c *gin.Context) { - c.PureJSON(http.StatusOK, gin.H{ - "html": "Hello, world!", - }) - }) - - // listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Serving static files - -```go -func main() { - router := gin.Default() - router.Static("/assets", "./assets") - router.StaticFS("/more_static", http.Dir("my_file_system")) - router.StaticFile("/favicon.ico", "./resources/favicon.ico") - router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system")) - - // Listen and serve on 0.0.0.0:8080 - router.Run(":8080") -} -``` - -### Serving data from file - -```go -func main() { - router := gin.Default() - - router.GET("/local/file", func(c *gin.Context) { - c.File("local/file.go") - }) - - var fs http.FileSystem = // ... - router.GET("/fs/file", func(c *gin.Context) { - c.FileFromFS("fs/file.go", fs) - }) -} - -``` - -### Serving data from reader - -```go -func main() { - router := gin.Default() - router.GET("/someDataFromReader", func(c *gin.Context) { - response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png") - if err != nil || response.StatusCode != http.StatusOK { - c.Status(http.StatusServiceUnavailable) - return - } - - reader := response.Body - defer reader.Close() - contentLength := response.ContentLength - contentType := response.Header.Get("Content-Type") - - extraHeaders := map[string]string{ - "Content-Disposition": `attachment; filename="gopher.png"`, - } - - c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) - }) - router.Run(":8080") -} -``` - -### HTML rendering - -Using LoadHTMLGlob() or LoadHTMLFiles() - -```go -func main() { - router := gin.Default() - router.LoadHTMLGlob("templates/*") - //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") - router.GET("/index", func(c *gin.Context) { - c.HTML(http.StatusOK, "index.tmpl", gin.H{ - "title": "Main website", - }) - }) - router.Run(":8080") -} -``` - -templates/index.tmpl - -```html - -

- {{ .title }} -

- -``` - -Using templates with same name in different directories - -```go -func main() { - router := gin.Default() - router.LoadHTMLGlob("templates/**/*") - router.GET("/posts/index", func(c *gin.Context) { - c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ - "title": "Posts", - }) - }) - router.GET("/users/index", func(c *gin.Context) { - c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ - "title": "Users", - }) - }) - router.Run(":8080") -} -``` - -templates/posts/index.tmpl - -```html -{{ define "posts/index.tmpl" }} -

- {{ .title }} -

-

Using posts/index.tmpl

- -{{ end }} -``` - -templates/users/index.tmpl - -```html -{{ define "users/index.tmpl" }} -

- {{ .title }} -

-

Using users/index.tmpl

- -{{ end }} -``` - -#### Custom Template renderer - -You can also use your own html template render - -```go -import "html/template" - -func main() { - router := gin.Default() - html := template.Must(template.ParseFiles("file1", "file2")) - router.SetHTMLTemplate(html) - router.Run(":8080") -} -``` - -#### Custom Delimiters - -You may use custom delims - -```go - r := gin.Default() - r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates") -``` - -#### Custom Template Funcs - -See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). - -main.go - -```go -import ( - "fmt" - "html/template" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -func formatAsDate(t time.Time) string { - year, month, day := t.Date() - return fmt.Sprintf("%d/%02d/%02d", year, month, day) -} - -func main() { - router := gin.Default() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLFiles("./testdata/template/raw.tmpl") - - router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", gin.H{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - - router.Run(":8080") -} - -``` - -raw.tmpl - -```html -Date: {[{.now | formatAsDate}]} -``` - -Result: - -```sh -Date: 2017/07/01 -``` - -### Multitemplate - -Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. - -### Redirects - -Issuing a HTTP redirect is easy. Both internal and external locations are supported. - -```go -r.GET("/test", func(c *gin.Context) { - c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") -}) -``` - -Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444) - -```go -r.POST("/test", func(c *gin.Context) { - c.Redirect(http.StatusFound, "/foo") -}) -``` - -Issuing a Router redirect, use `HandleContext` like below. - -``` go -r.GET("/test", func(c *gin.Context) { - c.Request.URL.Path = "/test2" - r.HandleContext(c) -}) -r.GET("/test2", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"hello": "world"}) -}) -``` - -### Custom Middleware - -```go -func Logger() gin.HandlerFunc { - return func(c *gin.Context) { - t := time.Now() - - // Set example variable - c.Set("example", "12345") - - // before request - - c.Next() - - // after request - latency := time.Since(t) - log.Print(latency) - - // access the status we are sending - status := c.Writer.Status() - log.Println(status) - } -} - -func main() { - r := gin.New() - r.Use(Logger()) - - r.GET("/test", func(c *gin.Context) { - example := c.MustGet("example").(string) - - // it would print: "12345" - log.Println(example) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Using BasicAuth() middleware - -```go -// simulate some private data -var secrets = gin.H{ - "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, - "austin": gin.H{"email": "austin@example.com", "phone": "666"}, - "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, -} - -func main() { - r := gin.Default() - - // Group using gin.BasicAuth() middleware - // gin.Accounts is a shortcut for map[string]string - authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ - "foo": "bar", - "austin": "1234", - "lena": "hello2", - "manu": "4321", - })) - - // /admin/secrets endpoint - // hit "localhost:8080/admin/secrets - authorized.GET("/secrets", func(c *gin.Context) { - // get user, it was set by the BasicAuth middleware - user := c.MustGet(gin.AuthUserKey).(string) - if secret, ok := secrets[user]; ok { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) - } else { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) - } - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Goroutines inside a middleware - -When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. - -```go -func main() { - r := gin.Default() - - r.GET("/long_async", func(c *gin.Context) { - // create copy to be used inside the goroutine - cCp := c.Copy() - go func() { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // note that you are using the copied context "cCp", IMPORTANT - log.Println("Done! in path " + cCp.Request.URL.Path) - }() - }) - - r.GET("/long_sync", func(c *gin.Context) { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // since we are NOT using a goroutine, we do not have to copy the context - log.Println("Done! in path " + c.Request.URL.Path) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Custom HTTP configuration - -Use `http.ListenAndServe()` directly, like this: - -```go -func main() { - router := gin.Default() - http.ListenAndServe(":8080", router) -} -``` - -or - -```go -func main() { - router := gin.Default() - - s := &http.Server{ - Addr: ":8080", - Handler: router, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - s.ListenAndServe() -} -``` - -### Support Let's Encrypt - -example for 1-line LetsEncrypt HTTPS servers. - -```go -package main - -import ( - "log" - "net/http" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - log.Fatal(autotls.Run(r, "example1.com", "example2.com")) -} -``` - -example for custom autocert manager. - -```go -package main - -import ( - "log" - "net/http" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" - "golang.org/x/crypto/acme/autocert" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), - Cache: autocert.DirCache("/var/www/.cache"), - } - - log.Fatal(autotls.RunWithManager(r, &m)) -} -``` - -### Run multiple service using Gin - -See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: - -```go -package main - -import ( - "log" - "net/http" - "time" - - "github.com/gin-gonic/gin" - "golang.org/x/sync/errgroup" -) - -var ( - g errgroup.Group -) - -func router01() http.Handler { - e := gin.New() - e.Use(gin.Recovery()) - e.GET("/", func(c *gin.Context) { - c.JSON( - http.StatusOK, - gin.H{ - "code": http.StatusOK, - "error": "Welcome server 01", - }, - ) - }) - - return e -} - -func router02() http.Handler { - e := gin.New() - e.Use(gin.Recovery()) - e.GET("/", func(c *gin.Context) { - c.JSON( - http.StatusOK, - gin.H{ - "code": http.StatusOK, - "error": "Welcome server 02", - }, - ) - }) - - return e -} - -func main() { - server01 := &http.Server{ - Addr: ":8080", - Handler: router01(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - server02 := &http.Server{ - Addr: ":8081", - Handler: router02(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - g.Go(func() error { - err := server01.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Fatal(err) - } - return err - }) - - g.Go(func() error { - err := server02.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Fatal(err) - } - return err - }) - - if err := g.Wait(); err != nil { - log.Fatal(err) - } -} -``` - -### Graceful shutdown or restart - -There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages. - -#### Third-party packages - -We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer to issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details. - -```go -router := gin.Default() -router.GET("/", handler) -// [...] -endless.ListenAndServe(":4242", router) -``` - -Alternatives: - -* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. -* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. -* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. - -#### Manually - -In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://pkg.go.dev/net/http#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown). - -```go -// +build go1.8 - -package main - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - router.GET("/", func(c *gin.Context) { - time.Sleep(5 * time.Second) - c.String(http.StatusOK, "Welcome Gin Server") - }) - - srv := &http.Server{ - Addr: ":8080", - Handler: router, - } - - // Initializing the server in a goroutine so that - // it won't block the graceful shutdown handling below - go func() { - if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Printf("listen: %s\n", err) - } - }() - - // Wait for interrupt signal to gracefully shutdown the server with - // a timeout of 5 seconds. - quit := make(chan os.Signal) - // kill (no param) default send syscall.SIGTERM - // kill -2 is syscall.SIGINT - // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - log.Println("Shutting down server...") - - // The context is used to inform the server it has 5 seconds to finish - // the request it is currently handling - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := srv.Shutdown(ctx); err != nil { - log.Fatal("Server forced to shutdown:", err) - } - - log.Println("Server exiting") -} -``` - -### Build a single binary with templates - -You can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package. - -```go -package main - -import ( - "embed" - "html/template" - "net/http" - - "github.com/gin-gonic/gin" -) - -//go:embed assets/* templates/* -var f embed.FS - -func main() { - router := gin.Default() - templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl")) - router.SetHTMLTemplate(templ) - - // example: /public/assets/images/example.png - router.StaticFS("/public", http.FS(f)) - - router.GET("/", func(c *gin.Context) { - c.HTML(http.StatusOK, "index.tmpl", gin.H{ - "title": "Main website", - }) - }) - - router.GET("/foo", func(c *gin.Context) { - c.HTML(http.StatusOK, "bar.tmpl", gin.H{ - "title": "Foo website", - }) - }) - - router.GET("favicon.ico", func(c *gin.Context) { - file, _ := f.ReadFile("assets/favicon.ico") - c.Data( - http.StatusOK, - "image/x-icon", - file, - ) - }) - - router.Run(":8080") -} -``` - -See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory. - -### Bind form-data request with custom struct - -The follow example using custom struct: - -```go -type StructA struct { - FieldA string `form:"field_a"` -} - -type StructB struct { - NestedStruct StructA - FieldB string `form:"field_b"` -} - -type StructC struct { - NestedStructPointer *StructA - FieldC string `form:"field_c"` -} - -type StructD struct { - NestedAnonyStruct struct { - FieldX string `form:"field_x"` - } - FieldD string `form:"field_d"` -} - -func GetDataB(c *gin.Context) { - var b StructB - c.Bind(&b) - c.JSON(http.StatusOK, gin.H{ - "a": b.NestedStruct, - "b": b.FieldB, - }) -} - -func GetDataC(c *gin.Context) { - var b StructC - c.Bind(&b) - c.JSON(http.StatusOK, gin.H{ - "a": b.NestedStructPointer, - "c": b.FieldC, - }) -} - -func GetDataD(c *gin.Context) { - var b StructD - c.Bind(&b) - c.JSON(http.StatusOK, gin.H{ - "x": b.NestedAnonyStruct, - "d": b.FieldD, - }) -} - -func main() { - r := gin.Default() - r.GET("/getb", GetDataB) - r.GET("/getc", GetDataC) - r.GET("/getd", GetDataD) - - r.Run() -} -``` - -Using the command `curl` command result: - -```sh -$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" -{"a":{"FieldA":"hello"},"b":"world"} -$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" -{"a":{"FieldA":"hello"},"c":"world"} -$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" -{"d":"world","x":{"FieldX":"hello"}} -``` - -### Try to bind body into different structs - -The normal methods for binding request body consumes `c.Request.Body` and they -cannot be called multiple times. - -```go -type formA struct { - Foo string `json:"foo" xml:"foo" binding:"required"` -} - -type formB struct { - Bar string `json:"bar" xml:"bar" binding:"required"` -} - -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // This c.ShouldBind consumes c.Request.Body and it cannot be reused. - if errA := c.ShouldBind(&objA); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // Always an error is occurred by this because c.Request.Body is EOF now. - } else if errB := c.ShouldBind(&objB); errB == nil { - c.String(http.StatusOK, `the body should be formB`) - } else { - ... - } -} -``` - -For this, you can use `c.ShouldBindBodyWith`. - -```go -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // This reads c.Request.Body and stores the result into the context. - if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // At this time, it reuses body stored in the context. - } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { - c.String(http.StatusOK, `the body should be formB JSON`) - // And it can accepts other formats - } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { - c.String(http.StatusOK, `the body should be formB XML`) - } else { - ... - } -} -``` - -1. `c.ShouldBindBodyWith` stores body into the context before binding. This has -a slight impact to performance, so you should not use this method if you are -enough to call binding at once. -2. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, -`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, -can be called by `c.ShouldBind()` multiple times without any damage to -performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). - -### Bind form-data request with custom struct and custom tag - -```go -const ( - customerTag = "url" - defaultMemory = 32 << 20 -) - -type customerBinding struct {} - -func (customerBinding) Name() string { - return "form" -} - -func (customerBinding) Bind(req *http.Request, obj interface{}) error { - if err := req.ParseForm(); err != nil { - return err - } - if err := req.ParseMultipartForm(defaultMemory); err != nil { - if err != http.ErrNotMultipart { - return err - } - } - if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil { - return err - } - return validate(obj) -} - -func validate(obj interface{}) error { - if binding.Validator == nil { - return nil - } - return binding.Validator.ValidateStruct(obj) -} - -// Now we can do this!!! -// FormA is an external type that we can't modify it's tag -type FormA struct { - FieldA string `url:"field_a"` -} - -func ListHandler(s *Service) func(ctx *gin.Context) { - return func(ctx *gin.Context) { - var urlBinding = customerBinding{} - var opt FormA - err := ctx.MustBindWith(&opt, urlBinding) - if err != nil { - ... - } - ... - } -} -``` - -### http2 server push - -http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information. - -```go -package main - -import ( - "html/template" - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -var html = template.Must(template.New("https").Parse(` - - - Https Test - - - -

Welcome, Ginner!

- - -`)) - -func main() { - r := gin.Default() - r.Static("/assets", "./assets") - r.SetHTMLTemplate(html) - - r.GET("/", func(c *gin.Context) { - if pusher := c.Writer.Pusher(); pusher != nil { - // use pusher.Push() to do server push - if err := pusher.Push("/assets/app.js", nil); err != nil { - log.Printf("Failed to push: %v", err) - } - } - c.HTML(http.StatusOK, "https", gin.H{ - "status": "success", - }) - }) - - // Listen and Server in https://127.0.0.1:8080 - r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") -} -``` - -### Define format for the log of routes - -The default log of routes is: - -```sh -[GIN-debug] POST /foo --> main.main.func1 (3 handlers) -[GIN-debug] GET /bar --> main.main.func2 (3 handlers) -[GIN-debug] GET /status --> main.main.func3 (3 handlers) -``` - -If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. -In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. - -```go -import ( - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { - log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) - } - - r.POST("/foo", func(c *gin.Context) { - c.JSON(http.StatusOK, "foo") - }) - - r.GET("/bar", func(c *gin.Context) { - c.JSON(http.StatusOK, "bar") - }) - - r.GET("/status", func(c *gin.Context) { - c.JSON(http.StatusOK, "ok") - }) - - // Listen and Server in http://0.0.0.0:8080 - r.Run() -} -``` - -### Set and get a cookie - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - - router.GET("/cookie", func(c *gin.Context) { - - cookie, err := c.Cookie("gin_cookie") - - if err != nil { - cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) - } - - fmt.Printf("Cookie value: %s \n", cookie) - }) - - router.Run() -} -``` - -## Don't trust all proxies - -Gin lets you specify which headers to hold the real client IP (if any), -as well as specifying which proxies (or direct clients) you trust to -specify one of these headers. - -Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses -or network CIDRs from where clients which their request headers related to client -IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or -IPv6 CIDRs. - -**Attention:** Gin trust all proxies by default if you don't specify a trusted -proxy using the function above, **this is NOT safe**. At the same time, if you don't -use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`, -then `Context.ClientIP()` will return the remote address directly to avoid some -unnecessary computation. - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - router.SetTrustedProxies([]string{"192.168.1.2"}) - - router.GET("/", func(c *gin.Context) { - // If the client is 192.168.1.2, use the X-Forwarded-For - // header to deduce the original client IP from the trust- - // worthy parts of that header. - // Otherwise, simply return the direct client IP - fmt.Printf("ClientIP: %s\n", c.ClientIP()) - }) - router.Run() -} -``` - -**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` -to skip TrustedProxies check, it has a higher priority than TrustedProxies. -Look at the example below: - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - // Use predefined header gin.PlatformXXX - router.TrustedPlatform = gin.PlatformGoogleAppEngine - // Or set your own trusted request header for another trusted proxy service - // Don't set it to any suspect request header, it's unsafe - router.TrustedPlatform = "X-CDN-IP" - - router.GET("/", func(c *gin.Context) { - // If you set TrustedPlatform, ClientIP() will resolve the - // corresponding header and return IP directly - fmt.Printf("ClientIP: %s\n", c.ClientIP()) - }) - router.Run() -} -``` - -## Testing - -The `net/http/httptest` package is preferable way for HTTP testing. - -```go -package main - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -func setupRouter() *gin.Engine { - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - return r -} - -func main() { - r := setupRouter() - r.Run(":8080") -} -``` - -Test for code example above: - -```go -package main - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPingRoute(t *testing.T) { - router := setupRouter() - - w := httptest.NewRecorder() - req, _ := http.NewRequest(http.MethodGet, "/ping", nil) - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "pong", w.Body.String()) -} -``` ## Users @@ -2380,3 +171,10 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. + + +## Contributing + +Gin is the work of hundreds of contributors. We appreciate your help! + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. \ No newline at end of file diff --git a/docs/doc.md b/docs/doc.md new file mode 100644 index 00000000..008a91db --- /dev/null +++ b/docs/doc.md @@ -0,0 +1,2246 @@ +# Gin Quick Start + +## Contents + +- [Build Tags](#build-tags) + - [Build with json replacement](#build-with-json-replacement) + - [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature) +- [API Examples](#api-examples) + - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) + - [Parameters in path](#parameters-in-path) + - [Querystring parameters](#querystring-parameters) + - [Multipart/Urlencoded Form](#multiparturlencoded-form) + - [Another example: query + post form](#another-example-query--post-form) + - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) + - [Upload files](#upload-files) + - [Single file](#single-file) + - [Multiple files](#multiple-files) + - [Grouping routes](#grouping-routes) + - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) + - [Using middleware](#using-middleware) + - [Custom Recovery behavior](#custom-recovery-behavior) + - [How to write log file](#how-to-write-log-file) + - [Custom Log Format](#custom-log-format) + - [Controlling Log output coloring](#controlling-log-output-coloring) + - [Model binding and validation](#model-binding-and-validation) + - [Custom Validators](#custom-validators) + - [Only Bind Query String](#only-bind-query-string) + - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Bind Uri](#bind-uri) + - [Bind Header](#bind-header) + - [Bind HTML checkboxes](#bind-html-checkboxes) + - [Multipart/Urlencoded binding](#multiparturlencoded-binding) + - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering) + - [SecureJSON](#securejson) + - [JSONP](#jsonp) + - [AsciiJSON](#asciijson) + - [PureJSON](#purejson) + - [Serving static files](#serving-static-files) + - [Serving data from file](#serving-data-from-file) + - [Serving data from reader](#serving-data-from-reader) + - [HTML rendering](#html-rendering) + - [Custom Template renderer](#custom-template-renderer) + - [Custom Delimiters](#custom-delimiters) + - [Custom Template Funcs](#custom-template-funcs) + - [Multitemplate](#multitemplate) + - [Redirects](#redirects) + - [Custom Middleware](#custom-middleware) + - [Using BasicAuth() middleware](#using-basicauth-middleware) + - [Goroutines inside a middleware](#goroutines-inside-a-middleware) + - [Custom HTTP configuration](#custom-http-configuration) + - [Support Let's Encrypt](#support-lets-encrypt) + - [Run multiple service using Gin](#run-multiple-service-using-gin) + - [Graceful shutdown or restart](#graceful-shutdown-or-restart) + - [Third-party packages](#third-party-packages) + - [Manually](#manually) + - [Build a single binary with templates](#build-a-single-binary-with-templates) + - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) + - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) + - [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag) + - [http2 server push](#http2-server-push) + - [Define format for the log of routes](#define-format-for-the-log-of-routes) + - [Set and get a cookie](#set-and-get-a-cookie) +- [Don't trust all proxies](#dont-trust-all-proxies) +- [Testing](#testing) + +## Build tags + +### Build with json replacement + +Gin uses `encoding/json` as default json package but you can change it by build from other tags. + +[jsoniter](https://github.com/json-iterator/go) + +```sh +go build -tags=jsoniter . +``` + +[go-json](https://github.com/goccy/go-json) + +```sh +go build -tags=go_json . +``` + +[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.) + +```sh +$ go build -tags="sonic avx" . +``` + +### Build without `MsgPack` rendering feature + +Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag. + +```sh +go build -tags=nomsgpack . +``` + +This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852). + +## API Examples + +You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). + +### Using GET, POST, PUT, PATCH, DELETE and OPTIONS + +```go +func main() { + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/someGet", getting) + router.POST("/somePost", posting) + router.PUT("/somePut", putting) + router.DELETE("/someDelete", deleting) + router.PATCH("/somePatch", patching) + router.HEAD("/someHead", head) + router.OPTIONS("/someOptions", options) + + // By default it serves on :8080 unless a + // PORT environment variable was defined. + router.Run() + // router.Run(":3000") for a hard coded port +} +``` + +### Parameters in path + +```go +func main() { + router := gin.Default() + + // This handler will match /user/john but will not match /user/ or /user + router.GET("/user/:name", func(c *gin.Context) { + name := c.Param("name") + c.String(http.StatusOK, "Hello %s", name) + }) + + // However, this one will match /user/john/ and also /user/john/send + // If no other routers match /user/john, it will redirect to /user/john/ + router.GET("/user/:name/*action", func(c *gin.Context) { + name := c.Param("name") + action := c.Param("action") + message := name + " is " + action + c.String(http.StatusOK, message) + }) + + // For each matched request Context will hold the route definition + router.POST("/user/:name/*action", func(c *gin.Context) { + b := c.FullPath() == "/user/:name/*action" // true + c.String(http.StatusOK, "%t", b) + }) + + // This handler will add a new router for /user/groups. + // Exact routes are resolved before param routes, regardless of the order they were defined. + // Routes starting with /user/groups are never interpreted as /user/:name/... routes + router.GET("/user/groups", func(c *gin.Context) { + c.String(http.StatusOK, "The available groups are [...]") + }) + + router.Run(":8080") +} +``` + +### Querystring parameters + +```go +func main() { + router := gin.Default() + + // Query string parameters are parsed using the existing underlying request object. + // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe + router.GET("/welcome", func(c *gin.Context) { + firstname := c.DefaultQuery("firstname", "Guest") + lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") + + c.String(http.StatusOK, "Hello %s %s", firstname, lastname) + }) + router.Run(":8080") +} +``` + +### Multipart/Urlencoded Form + +```go +func main() { + router := gin.Default() + + router.POST("/form_post", func(c *gin.Context) { + message := c.PostForm("message") + nick := c.DefaultPostForm("nick", "anonymous") + + c.JSON(http.StatusOK, gin.H{ + "status": "posted", + "message": message, + "nick": nick, + }) + }) + router.Run(":8080") +} +``` + +### Another example: query + post form + +```sh +POST /post?id=1234&page=1 HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +name=manu&message=this_is_great +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + id := c.Query("id") + page := c.DefaultQuery("page", "0") + name := c.PostForm("name") + message := c.PostForm("message") + + fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) + }) + router.Run(":8080") +} +``` + +```sh +id: 1234; page: 1; name: manu; message: this_is_great +``` + +### Map as querystring or postform parameters + +```sh +POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +names[first]=thinkerou&names[second]=tianou +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + ids := c.QueryMap("ids") + names := c.PostFormMap("names") + + fmt.Printf("ids: %v; names: %v", ids, names) + }) + router.Run(":8080") +} +``` + +```sh +ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou] +``` + +### Upload files + +#### Single file + +References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single). + +`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) + +> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. + +```go +func main() { + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // Single file + file, _ := c.FormFile("file") + log.Println(file.Filename) + + // Upload the file to specific dst. + c.SaveUploadedFile(file, dst) + + c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) + }) + router.Run(":8080") +} +``` + +How to `curl`: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "file=@/Users/appleboy/test.zip" \ + -H "Content-Type: multipart/form-data" +``` + +#### Multiple files + +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). + +```go +func main() { + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // Multipart form + form, _ := c.MultipartForm() + files := form.File["upload[]"] + + for _, file := range files { + log.Println(file.Filename) + + // Upload the file to specific dst. + c.SaveUploadedFile(file, dst) + } + c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) + }) + router.Run(":8080") +} +``` + +How to `curl`: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "upload[]=@/Users/appleboy/test1.zip" \ + -F "upload[]=@/Users/appleboy/test2.zip" \ + -H "Content-Type: multipart/form-data" +``` + +### Grouping routes + +```go +func main() { + router := gin.Default() + + // Simple group: v1 + v1 := router.Group("/v1") + { + v1.POST("/login", loginEndpoint) + v1.POST("/submit", submitEndpoint) + v1.POST("/read", readEndpoint) + } + + // Simple group: v2 + v2 := router.Group("/v2") + { + v2.POST("/login", loginEndpoint) + v2.POST("/submit", submitEndpoint) + v2.POST("/read", readEndpoint) + } + + router.Run(":8080") +} +``` + +### Blank Gin without middleware by default + +Use + +```go +r := gin.New() +``` + +instead of + +```go +// Default With the Logger and Recovery middleware already attached +r := gin.Default() +``` + +### Using middleware + +```go +func main() { + // Creates a router without any middleware by default + r := gin.New() + + // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout + r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.Recovery()) + + // Per route middleware, you can add as many as you desire. + r.GET("/benchmark", MyBenchLogger(), benchEndpoint) + + // Authorization group + // authorized := r.Group("/", AuthRequired()) + // exactly the same as: + authorized := r.Group("/") + // per group middleware! in this case we use the custom created + // AuthRequired() middleware just in the "authorized" group. + authorized.Use(AuthRequired()) + { + authorized.POST("/login", loginEndpoint) + authorized.POST("/submit", submitEndpoint) + authorized.POST("/read", readEndpoint) + + // nested group + testing := authorized.Group("testing") + // visit 0.0.0.0:8080/testing/analytics + testing.GET("/analytics", analyticsEndpoint) + } + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Custom Recovery behavior + +```go +func main() { + // Creates a router without any middleware by default + r := gin.New() + + // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout + r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { + if err, ok := recovered.(string); ok { + c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) + } + c.AbortWithStatus(http.StatusInternalServerError) + })) + + r.GET("/panic", func(c *gin.Context) { + // panic with a string -- the custom middleware could save this to a database or report it to the user + panic("foo") + }) + + r.GET("/", func(c *gin.Context) { + c.String(http.StatusOK, "ohai") + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### How to write log file + +```go +func main() { + // Disable Console Color, you don't need console color when writing the logs to file. + gin.DisableConsoleColor() + + // Logging to a file. + f, _ := os.Create("gin.log") + gin.DefaultWriter = io.MultiWriter(f) + + // Use the following code if you need to write the logs to file and console at the same time. + // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) + + router := gin.Default() + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + ย  ย router.Run(":8080") +} +``` + +### Custom Log Format + +```go +func main() { + router := gin.New() + + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + + // your custom format + return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", + param.ClientIP, + param.TimeStamp.Format(time.RFC1123), + param.Method, + param.Path, + param.Request.Proto, + param.StatusCode, + param.Latency, + param.Request.UserAgent(), + param.ErrorMessage, + ) + })) + router.Use(gin.Recovery()) + + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + router.Run(":8080") +} +``` + +Sample Output + +```sh +::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767ยตs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " +``` + +### Controlling Log output coloring + +By default, logs output on console should be colorized depending on the detected TTY. + +Never colorize logs: + +```go +func main() { + // Disable log's color + gin.DisableConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + router.Run(":8080") +} +``` + +Always colorize logs: + +```go +func main() { + // Force log's color + gin.ForceConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + router.Run(":8080") +} +``` + +### Model binding and validation + +To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz). + +Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). + +Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. + +Also, Gin provides two sets of methods for binding: + +- **Type** - Must bind + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML` + - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. +- **Type** - Should bind + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`, + - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. + +When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. + +You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has an empty value when binding, an error will be returned. + +```go +// Binding from JSON +type Login struct { + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` +} + +func main() { + router := gin.Default() + + // Example for binding JSON ({"user": "manu", "password": "123"}) + router.POST("/loginJSON", func(c *gin.Context) { + var json Login + if err := c.ShouldBindJSON(&json); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if json.User != "manu" || json.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding XML ( + // + // + // manu + // 123 + // ) + router.POST("/loginXML", func(c *gin.Context) { + var xml Login + if err := c.ShouldBindXML(&xml); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if xml.User != "manu" || xml.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding a HTML form (user=manu&password=123) + router.POST("/loginForm", func(c *gin.Context) { + var form Login + // This will infer what binder to use depending on the content-type header. + if err := c.ShouldBind(&form); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if form.User != "manu" || form.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Listen and serve on 0.0.0.0:8080 + router.Run(":8080") +} +``` + +Sample request + +```sh +$ curl -v -X POST \ + http://localhost:8080/loginJSON \ + -H 'content-type: application/json' \ + -d '{ "user": "manu" }' +> POST /loginJSON HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.51.0 +> Accept: */* +> content-type: application/json +> Content-Length: 18 +> +* upload completely sent off: 18 out of 18 bytes +< HTTP/1.1 400 Bad Request +< Content-Type: application/json; charset=utf-8 +< Date: Fri, 04 Aug 2017 03:51:31 GMT +< Content-Length: 100 +< +{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} +``` + +Skip validate: when running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. + +### Custom Validators + +It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). + +```go +package main + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/validator/v10" +) + +// Booking contains binded and validated data. +type Booking struct { + CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` + CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` +} + +var bookableDate validator.Func = func(fl validator.FieldLevel) bool { + date, ok := fl.Field().Interface().(time.Time) + if ok { + today := time.Now() + if today.After(date) { + return false + } + } + return true +} + +func main() { + route := gin.Default() + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } + + route.GET("/bookable", getBookable) + route.Run(":8085") +} + +func getBookable(c *gin.Context) { + var b Booking + if err := c.ShouldBindWith(&b, binding.Query); err == nil { + c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } +} +``` + +```console +$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17" +{"message":"Booking dates are valid!"} + +$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09" +{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"} + +$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10" +{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}% +``` + +[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. +See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. + +### Only Bind Query String + +`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). + +```go +package main + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Address string `form:"address"` +} + +func main() { + route := gin.Default() + route.Any("/testing", startPage) + route.Run(":8085") +} + +func startPage(c *gin.Context) { + var person Person + if c.ShouldBindQuery(&person) == nil { + log.Println("====== Only Bind By Query String ======") + log.Println(person.Name) + log.Println(person.Address) + } + c.String(http.StatusOK, "Success") +} + +``` + +### Bind Query String or Post Data + +See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). + +```go +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Address string `form:"address"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` +} + +func main() { + route := gin.Default() + route.GET("/testing", startPage) + route.Run(":8085") +} + +func startPage(c *gin.Context) { + var person Person + // If `GET`, only `Form` binding engine (`query`) used. + // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88 + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Address) + log.Println(person.Birthday) + log.Println(person.CreateTime) + log.Println(person.UnixTime) + } + + c.String(http.StatusOK, "Success") +} +``` + +Test it with: + +```sh +curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" +``` + +### Bind Uri + +See the [detail information](https://github.com/gin-gonic/gin/issues/846). + +```go +package main + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Person struct { + ID string `uri:"id" binding:"required,uuid"` + Name string `uri:"name" binding:"required"` +} + +func main() { + route := gin.Default() + route.GET("/:name/:id", func(c *gin.Context) { + var person Person + if err := c.ShouldBindUri(&person); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID}) + }) + route.Run(":8088") +} +``` + +Test it with: + +```sh +curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 +curl -v localhost:8088/thinkerou/not-uuid +``` + +### Bind Header + +```go +package main + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" +) + +type testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` +} + +func main() { + r := gin.Default() + r.GET("/", func(c *gin.Context) { + h := testHeader{} + + if err := c.ShouldBindHeader(&h); err != nil { + c.JSON(http.StatusOK, err) + } + + fmt.Printf("%#v\n", h) + c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain}) + }) + + r.Run() + +// client +// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ +// output +// {"Domain":"music","Rate":300} +} +``` + +### Bind HTML checkboxes + +See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) + +main.go + +```go +... + +type myForm struct { + Colors []string `form:"colors[]"` +} + +... + +func formHandler(c *gin.Context) { + var fakeForm myForm + c.ShouldBind(&fakeForm) + c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors}) +} + +... + +``` + +form.html + +```html +
+

Check some colors

+ + + + + + + +
+``` + +result: + +```json +{"color":["red","green","blue"]} +``` + +### Multipart/Urlencoded binding + +```go +type ProfileForm struct { + Name string `form:"name" binding:"required"` + Avatar *multipart.FileHeader `form:"avatar" binding:"required"` + + // or for multiple files + // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"` +} + +func main() { + router := gin.Default() + router.POST("/profile", func(c *gin.Context) { + // you can bind multipart form with explicit binding declaration: + // c.ShouldBindWith(&form, binding.Form) + // or you can simply use autobinding with ShouldBind method: + var form ProfileForm + // in this case proper binding will be automatically selected + if err := c.ShouldBind(&form); err != nil { + c.String(http.StatusBadRequest, "bad request") + return + } + + err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename) + if err != nil { + c.String(http.StatusInternalServerError, "unknown error") + return + } + + // db.Save(&form) + + c.String(http.StatusOK, "ok") + }) + router.Run(":8080") +} +``` + +Test it with: + +```sh +curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile +``` + +### XML, JSON, YAML, TOML and ProtoBuf rendering + +```go +func main() { + r := gin.Default() + + // gin.H is a shortcut for map[string]interface{} + r.GET("/someJSON", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + + r.GET("/moreJSON", func(c *gin.Context) { + // You also can use a struct + var msg struct { + Name string `json:"user"` + Message string + Number int + } + msg.Name = "Lena" + msg.Message = "hey" + msg.Number = 123 + // Note that msg.Name becomes "user" in the JSON + // Will output : {"user": "Lena", "Message": "hey", "Number": 123} + c.JSON(http.StatusOK, msg) + }) + + r.GET("/someXML", func(c *gin.Context) { + c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + + r.GET("/someYAML", func(c *gin.Context) { + c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + + r.GET("/someTOML", func(c *gin.Context) { + c.TOML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + + r.GET("/someProtoBuf", func(c *gin.Context) { + reps := []int64{int64(1), int64(2)} + label := "test" + // The specific definition of protobuf is written in the testdata/protoexample file. + data := &protoexample.Test{ + Label: &label, + Reps: reps, + } + // Note that data becomes binary data in the response + // Will output protoexample.Test protobuf serialized data + c.ProtoBuf(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### SecureJSON + +Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to response body if the given struct is array values. + +```go +func main() { + r := gin.Default() + + // You can also use your own secure json prefix + // r.SecureJsonPrefix(")]}',\n") + + r.GET("/someJSON", func(c *gin.Context) { + names := []string{"lena", "austin", "foo"} + + // Will output : while(1);["lena","austin","foo"] + c.SecureJSON(http.StatusOK, names) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### JSONP + +Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. + +```go +func main() { + r := gin.Default() + + r.GET("/JSONP", func(c *gin.Context) { + data := gin.H{ + "foo": "bar", + } + + //callback is x + // Will output : x({\"foo\":\"bar\"}) + c.JSONP(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") + + // client + // curl http://127.0.0.1:8080/JSONP?callback=x +} +``` + +#### AsciiJSON + +Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters. + +```go +func main() { + r := gin.Default() + + r.GET("/someJSON", func(c *gin.Context) { + data := gin.H{ + "lang": "GO่ฏญ่จ€", + "tag": "
", + } + + // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} + c.AsciiJSON(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### PureJSON + +Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. +This feature is unavailable in Go 1.6 and lower. + +```go +func main() { + r := gin.Default() + + // Serves unicode entities + r.GET("/json", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "html": "Hello, world!", + }) + }) + + // Serves literal characters + r.GET("/purejson", func(c *gin.Context) { + c.PureJSON(http.StatusOK, gin.H{ + "html": "Hello, world!", + }) + }) + + // listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Serving static files + +```go +func main() { + router := gin.Default() + router.Static("/assets", "./assets") + router.StaticFS("/more_static", http.Dir("my_file_system")) + router.StaticFile("/favicon.ico", "./resources/favicon.ico") + router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system")) + + // Listen and serve on 0.0.0.0:8080 + router.Run(":8080") +} +``` + +### Serving data from file + +```go +func main() { + router := gin.Default() + + router.GET("/local/file", func(c *gin.Context) { + c.File("local/file.go") + }) + + var fs http.FileSystem = // ... + router.GET("/fs/file", func(c *gin.Context) { + c.FileFromFS("fs/file.go", fs) + }) +} + +``` + +### Serving data from reader + +```go +func main() { + router := gin.Default() + router.GET("/someDataFromReader", func(c *gin.Context) { + response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png") + if err != nil || response.StatusCode != http.StatusOK { + c.Status(http.StatusServiceUnavailable) + return + } + + reader := response.Body + defer reader.Close() + contentLength := response.ContentLength + contentType := response.Header.Get("Content-Type") + + extraHeaders := map[string]string{ + "Content-Disposition": `attachment; filename="gopher.png"`, + } + + c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) + }) + router.Run(":8080") +} +``` + +### HTML rendering + +Using LoadHTMLGlob() or LoadHTMLFiles() + +```go +func main() { + router := gin.Default() + router.LoadHTMLGlob("templates/*") + //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") + router.GET("/index", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.tmpl", gin.H{ + "title": "Main website", + }) + }) + router.Run(":8080") +} +``` + +templates/index.tmpl + +```html + +

+ {{ .title }} +

+ +``` + +Using templates with same name in different directories + +```go +func main() { + router := gin.Default() + router.LoadHTMLGlob("templates/**/*") + router.GET("/posts/index", func(c *gin.Context) { + c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ + "title": "Posts", + }) + }) + router.GET("/users/index", func(c *gin.Context) { + c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ + "title": "Users", + }) + }) + router.Run(":8080") +} +``` + +templates/posts/index.tmpl + +```html +{{ define "posts/index.tmpl" }} +

+ {{ .title }} +

+

Using posts/index.tmpl

+ +{{ end }} +``` + +templates/users/index.tmpl + +```html +{{ define "users/index.tmpl" }} +

+ {{ .title }} +

+

Using users/index.tmpl

+ +{{ end }} +``` + +#### Custom Template renderer + +You can also use your own html template render + +```go +import "html/template" + +func main() { + router := gin.Default() + html := template.Must(template.ParseFiles("file1", "file2")) + router.SetHTMLTemplate(html) + router.Run(":8080") +} +``` + +#### Custom Delimiters + +You may use custom delims + +```go + r := gin.Default() + r.Delims("{[{", "}]}") + r.LoadHTMLGlob("/path/to/templates") +``` + +#### Custom Template Funcs + +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). + +main.go + +```go +import ( + "fmt" + "html/template" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +func formatAsDate(t time.Time) string { + year, month, day := t.Date() + return fmt.Sprintf("%d/%02d/%02d", year, month, day) +} + +func main() { + router := gin.Default() + router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + router.LoadHTMLFiles("./testdata/template/raw.tmpl") + + router.GET("/raw", func(c *gin.Context) { + c.HTML(http.StatusOK, "raw.tmpl", gin.H{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) + + router.Run(":8080") +} + +``` + +raw.tmpl + +```html +Date: {[{.now | formatAsDate}]} +``` + +Result: + +```sh +Date: 2017/07/01 +``` + +### Multitemplate + +Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. + +### Redirects + +Issuing a HTTP redirect is easy. Both internal and external locations are supported. + +```go +r.GET("/test", func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") +}) +``` + +Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444) + +```go +r.POST("/test", func(c *gin.Context) { + c.Redirect(http.StatusFound, "/foo") +}) +``` + +Issuing a Router redirect, use `HandleContext` like below. + +``` go +r.GET("/test", func(c *gin.Context) { + c.Request.URL.Path = "/test2" + r.HandleContext(c) +}) +r.GET("/test2", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"hello": "world"}) +}) +``` + +### Custom Middleware + +```go +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + t := time.Now() + + // Set example variable + c.Set("example", "12345") + + // before request + + c.Next() + + // after request + latency := time.Since(t) + log.Print(latency) + + // access the status we are sending + status := c.Writer.Status() + log.Println(status) + } +} + +func main() { + r := gin.New() + r.Use(Logger()) + + r.GET("/test", func(c *gin.Context) { + example := c.MustGet("example").(string) + + // it would print: "12345" + log.Println(example) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Using BasicAuth() middleware + +```go +// simulate some private data +var secrets = gin.H{ + "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, + "austin": gin.H{"email": "austin@example.com", "phone": "666"}, + "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, +} + +func main() { + r := gin.Default() + + // Group using gin.BasicAuth() middleware + // gin.Accounts is a shortcut for map[string]string + authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ + "foo": "bar", + "austin": "1234", + "lena": "hello2", + "manu": "4321", + })) + + // /admin/secrets endpoint + // hit "localhost:8080/admin/secrets + authorized.GET("/secrets", func(c *gin.Context) { + // get user, it was set by the BasicAuth middleware + user := c.MustGet(gin.AuthUserKey).(string) + if secret, ok := secrets[user]; ok { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) + } else { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) + } + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Goroutines inside a middleware + +When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. + +```go +func main() { + r := gin.Default() + + r.GET("/long_async", func(c *gin.Context) { + // create copy to be used inside the goroutine + cCp := c.Copy() + go func() { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // note that you are using the copied context "cCp", IMPORTANT + log.Println("Done! in path " + cCp.Request.URL.Path) + }() + }) + + r.GET("/long_sync", func(c *gin.Context) { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // since we are NOT using a goroutine, we do not have to copy the context + log.Println("Done! in path " + c.Request.URL.Path) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Custom HTTP configuration + +Use `http.ListenAndServe()` directly, like this: + +```go +func main() { + router := gin.Default() + http.ListenAndServe(":8080", router) +} +``` + +or + +```go +func main() { + router := gin.Default() + + s := &http.Server{ + Addr: ":8080", + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + s.ListenAndServe() +} +``` + +### Support Let's Encrypt + +example for 1-line LetsEncrypt HTTPS servers. + +```go +package main + +import ( + "log" + "net/http" + + "github.com/gin-gonic/autotls" + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + log.Fatal(autotls.Run(r, "example1.com", "example2.com")) +} +``` + +example for custom autocert manager. + +```go +package main + +import ( + "log" + "net/http" + + "github.com/gin-gonic/autotls" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/acme/autocert" +) + +func main() { + r := gin.Default() + + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), + Cache: autocert.DirCache("/var/www/.cache"), + } + + log.Fatal(autotls.RunWithManager(r, &m)) +} +``` + +### Run multiple service using Gin + +See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: + +```go +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "golang.org/x/sync/errgroup" +) + +var ( + g errgroup.Group +) + +func router01() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 01", + }, + ) + }) + + return e +} + +func router02() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 02", + }, + ) + }) + + return e +} + +func main() { + server01 := &http.Server{ + Addr: ":8080", + Handler: router01(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + server02 := &http.Server{ + Addr: ":8081", + Handler: router02(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + g.Go(func() error { + err := server01.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + return err + }) + + g.Go(func() error { + err := server02.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + return err + }) + + if err := g.Wait(); err != nil { + log.Fatal(err) + } +} +``` + +### Graceful shutdown or restart + +There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages. + +#### Third-party packages + +We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer to issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details. + +```go +router := gin.Default() +router.GET("/", handler) +// [...] +endless.ListenAndServe(":4242", router) +``` + +Alternatives: + +* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. +* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. +* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. + +#### Manually + +In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://pkg.go.dev/net/http#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown). + +```go +// +build go1.8 + +package main + +import ( + "context" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gin-gonic/gin" +) + +func main() { + router := gin.Default() + router.GET("/", func(c *gin.Context) { + time.Sleep(5 * time.Second) + c.String(http.StatusOK, "Welcome Gin Server") + }) + + srv := &http.Server{ + Addr: ":8080", + Handler: router, + } + + // Initializing the server in a goroutine so that + // it won't block the graceful shutdown handling below + go func() { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Printf("listen: %s\n", err) + } + }() + + // Wait for interrupt signal to gracefully shutdown the server with + // a timeout of 5 seconds. + quit := make(chan os.Signal) + // kill (no param) default send syscall.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + log.Println("Shutting down server...") + + // The context is used to inform the server it has 5 seconds to finish + // the request it is currently handling + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := srv.Shutdown(ctx); err != nil { + log.Fatal("Server forced to shutdown:", err) + } + + log.Println("Server exiting") +} +``` + +### Build a single binary with templates + +You can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package. + +```go +package main + +import ( + "embed" + "html/template" + "net/http" + + "github.com/gin-gonic/gin" +) + +//go:embed assets/* templates/* +var f embed.FS + +func main() { + router := gin.Default() + templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl")) + router.SetHTMLTemplate(templ) + + // example: /public/assets/images/example.png + router.StaticFS("/public", http.FS(f)) + + router.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.tmpl", gin.H{ + "title": "Main website", + }) + }) + + router.GET("/foo", func(c *gin.Context) { + c.HTML(http.StatusOK, "bar.tmpl", gin.H{ + "title": "Foo website", + }) + }) + + router.GET("favicon.ico", func(c *gin.Context) { + file, _ := f.ReadFile("assets/favicon.ico") + c.Data( + http.StatusOK, + "image/x-icon", + file, + ) + }) + + router.Run(":8080") +} +``` + +See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory. + +### Bind form-data request with custom struct + +The follow example using custom struct: + +```go +type StructA struct { + FieldA string `form:"field_a"` +} + +type StructB struct { + NestedStruct StructA + FieldB string `form:"field_b"` +} + +type StructC struct { + NestedStructPointer *StructA + FieldC string `form:"field_c"` +} + +type StructD struct { + NestedAnonyStruct struct { + FieldX string `form:"field_x"` + } + FieldD string `form:"field_d"` +} + +func GetDataB(c *gin.Context) { + var b StructB + c.Bind(&b) + c.JSON(http.StatusOK, gin.H{ + "a": b.NestedStruct, + "b": b.FieldB, + }) +} + +func GetDataC(c *gin.Context) { + var b StructC + c.Bind(&b) + c.JSON(http.StatusOK, gin.H{ + "a": b.NestedStructPointer, + "c": b.FieldC, + }) +} + +func GetDataD(c *gin.Context) { + var b StructD + c.Bind(&b) + c.JSON(http.StatusOK, gin.H{ + "x": b.NestedAnonyStruct, + "d": b.FieldD, + }) +} + +func main() { + r := gin.Default() + r.GET("/getb", GetDataB) + r.GET("/getc", GetDataC) + r.GET("/getd", GetDataD) + + r.Run() +} +``` + +Using the command `curl` command result: + +```sh +$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" +{"a":{"FieldA":"hello"},"b":"world"} +$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" +{"a":{"FieldA":"hello"},"c":"world"} +$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" +{"d":"world","x":{"FieldX":"hello"}} +``` + +### Try to bind body into different structs + +The normal methods for binding request body consumes `c.Request.Body` and they +cannot be called multiple times. + +```go +type formA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` +} + +type formB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` +} + +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This c.ShouldBind consumes c.Request.Body and it cannot be reused. + if errA := c.ShouldBind(&objA); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // Always an error is occurred by this because c.Request.Body is EOF now. + } else if errB := c.ShouldBind(&objB); errB == nil { + c.String(http.StatusOK, `the body should be formB`) + } else { + ... + } +} +``` + +For this, you can use `c.ShouldBindBodyWith`. + +```go +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This reads c.Request.Body and stores the result into the context. + if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // At this time, it reuses body stored in the context. + } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { + c.String(http.StatusOK, `the body should be formB JSON`) + // And it can accepts other formats + } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { + c.String(http.StatusOK, `the body should be formB XML`) + } else { + ... + } +} +``` + +1. `c.ShouldBindBodyWith` stores body into the context before binding. This has +a slight impact to performance, so you should not use this method if you are +enough to call binding at once. +2. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, +`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, +can be called by `c.ShouldBind()` multiple times without any damage to +performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). + +### Bind form-data request with custom struct and custom tag + +```go +const ( + customerTag = "url" + defaultMemory = 32 << 20 +) + +type customerBinding struct {} + +func (customerBinding) Name() string { + return "form" +} + +func (customerBinding) Bind(req *http.Request, obj interface{}) error { + if err := req.ParseForm(); err != nil { + return err + } + if err := req.ParseMultipartForm(defaultMemory); err != nil { + if err != http.ErrNotMultipart { + return err + } + } + if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil { + return err + } + return validate(obj) +} + +func validate(obj interface{}) error { + if binding.Validator == nil { + return nil + } + return binding.Validator.ValidateStruct(obj) +} + +// Now we can do this!!! +// FormA is an external type that we can't modify it's tag +type FormA struct { + FieldA string `url:"field_a"` +} + +func ListHandler(s *Service) func(ctx *gin.Context) { + return func(ctx *gin.Context) { + var urlBinding = customerBinding{} + var opt FormA + err := ctx.MustBindWith(&opt, urlBinding) + if err != nil { + ... + } + ... + } +} +``` + +### http2 server push + +http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information. + +```go +package main + +import ( + "html/template" + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +var html = template.Must(template.New("https").Parse(` + + + Https Test + + + +

Welcome, Ginner!

+ + +`)) + +func main() { + r := gin.Default() + r.Static("/assets", "./assets") + r.SetHTMLTemplate(html) + + r.GET("/", func(c *gin.Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + // use pusher.Push() to do server push + if err := pusher.Push("/assets/app.js", nil); err != nil { + log.Printf("Failed to push: %v", err) + } + } + c.HTML(http.StatusOK, "https", gin.H{ + "status": "success", + }) + }) + + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") +} +``` + +### Define format for the log of routes + +The default log of routes is: + +```sh +[GIN-debug] POST /foo --> main.main.func1 (3 handlers) +[GIN-debug] GET /bar --> main.main.func2 (3 handlers) +[GIN-debug] GET /status --> main.main.func3 (3 handlers) +``` + +If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. +In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. + +```go +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + + r.POST("/foo", func(c *gin.Context) { + c.JSON(http.StatusOK, "foo") + }) + + r.GET("/bar", func(c *gin.Context) { + c.JSON(http.StatusOK, "bar") + }) + + r.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) + + // Listen and Server in http://0.0.0.0:8080 + r.Run() +} +``` + +### Set and get a cookie + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + + router.GET("/cookie", func(c *gin.Context) { + + cookie, err := c.Cookie("gin_cookie") + + if err != nil { + cookie = "NotSet" + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + } + + fmt.Printf("Cookie value: %s \n", cookie) + }) + + router.Run() +} +``` + +## Don't trust all proxies + +Gin lets you specify which headers to hold the real client IP (if any), +as well as specifying which proxies (or direct clients) you trust to +specify one of these headers. + +Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses +or network CIDRs from where clients which their request headers related to client +IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or +IPv6 CIDRs. + +**Attention:** Gin trust all proxies by default if you don't specify a trusted +proxy using the function above, **this is NOT safe**. At the same time, if you don't +use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`, +then `Context.ClientIP()` will return the remote address directly to avoid some +unnecessary computation. + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + router.SetTrustedProxies([]string{"192.168.1.2"}) + + router.GET("/", func(c *gin.Context) { + // If the client is 192.168.1.2, use the X-Forwarded-For + // header to deduce the original client IP from the trust- + // worthy parts of that header. + // Otherwise, simply return the direct client IP + fmt.Printf("ClientIP: %s\n", c.ClientIP()) + }) + router.Run() +} +``` + +**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` +to skip TrustedProxies check, it has a higher priority than TrustedProxies. +Look at the example below: + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + // Use predefined header gin.PlatformXXX + router.TrustedPlatform = gin.PlatformGoogleAppEngine + // Or set your own trusted request header for another trusted proxy service + // Don't set it to any suspect request header, it's unsafe + router.TrustedPlatform = "X-CDN-IP" + + router.GET("/", func(c *gin.Context) { + // If you set TrustedPlatform, ClientIP() will resolve the + // corresponding header and return IP directly + fmt.Printf("ClientIP: %s\n", c.ClientIP()) + }) + router.Run() +} +``` + +## Testing + +The `net/http/httptest` package is preferable way for HTTP testing. + +```go +package main + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func setupRouter() *gin.Engine { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + return r +} + +func main() { + r := setupRouter() + r.Run(":8080") +} +``` + +Test for code example above: + +```go +package main + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPingRoute(t *testing.T) { + router := setupRouter() + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/ping", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "pong", w.Body.String()) +} +``` From 41f2669ebcc24dbbef7dcca705a4cd75b7c43f28 Mon Sep 17 00:00:00 2001 From: "Alireza (Pure)" Date: Mon, 2 Jan 2023 07:08:53 +0330 Subject: [PATCH 033/332] console logger HTTP status bug fixed and the corresponding unit test added (#3453) --- response_writer.go | 1 + response_writer_test.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/response_writer.go b/response_writer.go index 43e828d7..5cec1f66 100644 --- a/response_writer.go +++ b/response_writer.go @@ -61,6 +61,7 @@ func (w *responseWriter) WriteHeader(code int) { if code > 0 && w.status != code { if w.Written() { debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code) + return } w.status = code } diff --git a/response_writer_test.go b/response_writer_test.go index 57d163c9..6fa5ec71 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -132,3 +132,21 @@ func TestResponseWriterFlush(t *testing.T) { assert.NoError(t, err) assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) } + +func TestResponseWriterStatusCode(t *testing.T) { + testWriter := httptest.NewRecorder() + writer := &responseWriter{} + writer.reset(testWriter) + w := ResponseWriter(writer) + + w.WriteHeader(http.StatusOK) + w.WriteHeaderNow() + + assert.Equal(t, http.StatusOK, w.Status()) + assert.True(t, w.Written()) + + w.WriteHeader(http.StatusUnauthorized) + + // status must be 200 although we tried to change it + assert.Equal(t, http.StatusOK, w.Status()) +} From 7d8fc1563b4e1b4229e61c2fe4c9e31ce13ace7d Mon Sep 17 00:00:00 2001 From: youngxhui Date: Mon, 2 Jan 2023 11:39:26 +0800 Subject: [PATCH 034/332] update context.go Get/Set method use defer (#3429) Using defer to unlock is more in line with go standards --- context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 737e4d7a..b1352b9b 100644 --- a/context.go +++ b/context.go @@ -248,20 +248,20 @@ func (c *Context) Error(err error) *Error { // It also lazy initializes c.Keys if it was not used previously. func (c *Context) Set(key string, value any) { c.mu.Lock() + defer c.mu.Unlock() if c.Keys == nil { c.Keys = make(map[string]any) } c.Keys[key] = value - c.mu.Unlock() } // Get returns the value for the given key, ie: (value, true). // If the value does not exist it returns (nil, false) func (c *Context) Get(key string) (value any, exists bool) { c.mu.RLock() + defer c.mu.RUnlock() value, exists = c.Keys[key] - c.mu.RUnlock() return } From c9b27249fbb6092bcc7f749811d73ef1d50eee73 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 2 Jan 2023 12:40:48 +0800 Subject: [PATCH 035/332] chore(yaml): upgrade dependency to v3 version (#3456) fixes https://github.com/gin-gonic/gin/issues/3451 fixes https://github.com/gin-gonic/gin/issues/3306 fixes https://github.com/gin-gonic/gin/issues/3362 fixes https://github.com/gin-gonic/gin/issues/2581 --- binding/yaml.go | 2 +- go.mod | 3 +-- go.sum | 2 -- render/render_test.go | 2 +- render/yaml.go | 2 +- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/binding/yaml.go b/binding/yaml.go index b0d36a35..2535f8c3 100644 --- a/binding/yaml.go +++ b/binding/yaml.go @@ -9,7 +9,7 @@ import ( "io" "net/http" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) type yamlBinding struct{} diff --git a/go.mod b/go.mod index e9698339..2b7a98cc 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.4.0 google.golang.org/protobuf v1.28.1 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -32,5 +32,4 @@ require ( golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6d8df64a..a4f0f387 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/render/render_test.go b/render/render_test.go index c5c5375f..ebb7d414 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -238,7 +238,7 @@ b: err := (YAML{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String()) + assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String()) assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/render/yaml.go b/render/yaml.go index 4f0ac01f..fc927c1f 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -7,7 +7,7 @@ package render import ( "net/http" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) // YAML contains the given interface object. From 7626361587bdce4b02335edd1d38627b79027b5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 10:40:53 +0800 Subject: [PATCH 036/332] chore(deps): bump github.com/ugorji/go/codec from 1.2.7 to 1.2.8 (#3458) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.2.7 to 1.2.8. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/v1.2.7...v1.2.8) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 2b7a98cc..daff61de 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/mattn/go-isatty v0.0.16 github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 - github.com/ugorji/go/codec v1.2.7 + github.com/ugorji/go/codec v1.2.8 golang.org/x/net v0.4.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index a4f0f387..c7e0b38a 100644 --- a/go.sum +++ b/go.sum @@ -65,9 +65,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0= +github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= From 79a61b90324586d3c3a5859f8755cae2d1c46f2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 11:39:57 +0800 Subject: [PATCH 037/332] chore(deps): bump github.com/mattn/go-isatty from 0.0.16 to 0.0.17 (#3457) Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.16 to 0.0.17. - [Release notes](https://github.com/mattn/go-isatty/releases) - [Commits](https://github.com/mattn/go-isatty/compare/v0.0.16...v0.0.17) --- updated-dependencies: - dependency-name: github.com/mattn/go-isatty dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 daff61de..8cb21f9c 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-playground/validator/v10 v10.11.1 github.com/goccy/go-json v0.10.0 github.com/json-iterator/go v1.1.12 - github.com/mattn/go-isatty v0.0.16 + github.com/mattn/go-isatty v0.0.17 github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.8 diff --git a/go.sum b/go.sum index c7e0b38a..daf51f7d 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= From c58e0d59ca6753da47daa0a64b844bc869030759 Mon Sep 17 00:00:00 2001 From: apriil15 Date: Thu, 5 Jan 2023 10:15:29 +0800 Subject: [PATCH 038/332] docs: update markdown format (#3446) * docs: update markdown format * fix: resolve conflict * docs: update markdown format * docs: update * docs: update * Revert "docs: update" This reverts commit 82716193b753dbcad6fee85973790727b7a31ae5. --- docs/doc.md | 149 +++++++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 76 deletions(-) diff --git a/docs/doc.md b/docs/doc.md index 008a91db..7cebab56 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -450,22 +450,22 @@ func main() { ```go func main() { - // Disable Console Color, you don't need console color when writing the logs to file. - gin.DisableConsoleColor() + // Disable Console Color, you don't need console color when writing the logs to file. + gin.DisableConsoleColor() - // Logging to a file. - f, _ := os.Create("gin.log") - gin.DefaultWriter = io.MultiWriter(f) + // Logging to a file. + f, _ := os.Create("gin.log") + gin.DefaultWriter = io.MultiWriter(f) - // Use the following code if you need to write the logs to file and console at the same time. - // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) + // Use the following code if you need to write the logs to file and console at the same time. + // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) - router := gin.Default() - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) + router := gin.Default() + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) - ย  ย router.Run(":8080") +ย  ย router.Run(":8080") } ``` @@ -516,18 +516,18 @@ Never colorize logs: ```go func main() { - // Disable log's color - gin.DisableConsoleColor() + // Disable log's color + gin.DisableConsoleColor() - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) - router.Run(":8080") + router.Run(":8080") } ``` @@ -535,18 +535,18 @@ Always colorize logs: ```go func main() { - // Force log's color - gin.ForceConsoleColor() + // Force log's color + gin.ForceConsoleColor() - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) - router.Run(":8080") + router.Run(":8080") } ``` @@ -786,11 +786,11 @@ import ( ) type Person struct { - Name string `form:"name"` - Address string `form:"address"` - Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` - CreateTime time.Time `form:"createTime" time_format:"unixNano"` - UnixTime time.Time `form:"unixTime" time_format:"unix"` + Name string `form:"name"` + Address string `form:"address"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` } func main() { @@ -804,13 +804,13 @@ func startPage(c *gin.Context) { // If `GET`, only `Form` binding engine (`query`) used. // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88 - if c.ShouldBind(&person) == nil { - log.Println(person.Name) - log.Println(person.Address) - log.Println(person.Birthday) - log.Println(person.CreateTime) - log.Println(person.UnixTime) - } + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Address) + log.Println(person.Birthday) + log.Println(person.CreateTime) + log.Println(person.UnixTime) + } c.String(http.StatusOK, "Success") } @@ -1311,34 +1311,34 @@ main.go ```go import ( - "fmt" - "html/template" - "net/http" - "time" + "fmt" + "html/template" + "net/http" + "time" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) func formatAsDate(t time.Time) string { - year, month, day := t.Date() - return fmt.Sprintf("%d/%02d/%02d", year, month, day) + year, month, day := t.Date() + return fmt.Sprintf("%d/%02d/%02d", year, month, day) } func main() { - router := gin.Default() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLFiles("./testdata/template/raw.tmpl") + router := gin.Default() + router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + router.LoadHTMLFiles("./testdata/template/raw.tmpl") - router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", gin.H{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) + router.GET("/raw", func(c *gin.Context) { + c.HTML(http.StatusOK, "raw.tmpl", gin.H{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) - router.Run(":8080") + router.Run(":8080") } ``` @@ -2099,28 +2099,27 @@ func main() { ```go import ( - "fmt" + "fmt" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) func main() { + router := gin.Default() - router := gin.Default() + router.GET("/cookie", func(c *gin.Context) { - router.GET("/cookie", func(c *gin.Context) { + cookie, err := c.Cookie("gin_cookie") - cookie, err := c.Cookie("gin_cookie") + if err != nil { + cookie = "NotSet" + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + } - if err != nil { - cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) - } + fmt.Printf("Cookie value: %s \n", cookie) + }) - fmt.Printf("Cookie value: %s \n", cookie) - }) - - router.Run() + router.Run() } ``` @@ -2149,7 +2148,6 @@ import ( ) func main() { - router := gin.Default() router.SetTrustedProxies([]string{"192.168.1.2"}) @@ -2176,7 +2174,6 @@ import ( ) func main() { - router := gin.Default() // Use predefined header gin.PlatformXXX router.TrustedPlatform = gin.PlatformGoogleAppEngine From 8eb5f832bac1853fc84c508a2b9406134d39492e Mon Sep 17 00:00:00 2001 From: Kristian Svalland <54534849+kristiansvalland@users.noreply.github.com> Date: Sat, 7 Jan 2023 01:57:54 +0100 Subject: [PATCH 039/332] fix(router): tree bug where loop index is not decremented. (#3460) fixes https://github.com/gin-gonic/gin/issues/3459 --- routes_test.go | 19 +++++++++++++++++++ tree.go | 18 +++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/routes_test.go b/routes_test.go index cd8cf141..ada8e1e4 100644 --- a/routes_test.go +++ b/routes_test.go @@ -670,3 +670,22 @@ func TestRouteContextHoldsFullPath(t *testing.T) { w := PerformRequest(router, http.MethodGet, "/not-found") assert.Equal(t, http.StatusNotFound, w.Code) } + +func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) { + r := New() + r.HandleMethodNotAllowed = true + + base := r.Group("base") + base.GET("/metrics", handlerTest1) + + v1 := base.Group("v1") + + v1.GET("/:id/devices", handlerTest1) + v1.GET("/user/:id/groups", handlerTest1) + + v1.GET("/orgs/:id", handlerTest1) + v1.DELETE("/orgs/:id", handlerTest1) + + w := PerformRequest(r, "GET", "/base/v1/user/groups") + assert.Equal(t, http.StatusNotFound, w.Code) +} diff --git a/tree.go b/tree.go index 3f34b8ee..dda8f4f7 100644 --- a/tree.go +++ b/tree.go @@ -459,9 +459,9 @@ walk: // Outer loop for walking the tree // If the path at the end of the loop is not equal to '/' and the current node has no child nodes // the current node needs to roll back to last valid skippedNode if path != "/" { - for l := len(*skippedNodes); l > 0; { - skippedNode := (*skippedNodes)[l-1] - *skippedNodes = (*skippedNodes)[:l-1] + for length := len(*skippedNodes); length > 0; length-- { + skippedNode := (*skippedNodes)[length-1] + *skippedNodes = (*skippedNodes)[:length-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node @@ -576,9 +576,9 @@ walk: // Outer loop for walking the tree // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node // the current node needs to roll back to last valid skippedNode if n.handlers == nil && path != "/" { - for l := len(*skippedNodes); l > 0; { - skippedNode := (*skippedNodes)[l-1] - *skippedNodes = (*skippedNodes)[:l-1] + for length := len(*skippedNodes); length > 0; length-- { + skippedNode := (*skippedNodes)[length-1] + *skippedNodes = (*skippedNodes)[:length-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node @@ -633,9 +633,9 @@ walk: // Outer loop for walking the tree // roll back to last valid skippedNode if !value.tsr && path != "/" { - for l := len(*skippedNodes); l > 0; { - skippedNode := (*skippedNodes)[l-1] - *skippedNodes = (*skippedNodes)[:l-1] + for length := len(*skippedNodes); length > 0; length-- { + skippedNode := (*skippedNodes)[length-1] + *skippedNodes = (*skippedNodes)[:length-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node From 47ae6ee386c5b1be402de09c6d69f8d2f0db3c4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 10:11:45 +0800 Subject: [PATCH 040/332] chore(deps): bump golang.org/x/net from 0.4.0 to 0.5.0 (#3466) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 8cb21f9c..0b1d3a6b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.8 - golang.org/x/net v0.4.0 + golang.org/x/net v0.5.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,6 +30,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/sys v0.4.0 // indirect + golang.org/x/text v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index daf51f7d..f86942b5 100644 --- a/go.sum +++ b/go.sum @@ -73,20 +73,20 @@ golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 3010cbd7f4eccdbb610c510274895e083b8c058c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 10:12:12 +0800 Subject: [PATCH 041/332] chore(deps): bump github.com/bytedance/sonic from 1.6.0 to 1.6.1 (#3467) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.6.0 to 1.6.1. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.6.0...v1.6.1) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 0b1d3a6b..c498b482 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.6.0 + github.com/bytedance/sonic v1.6.1 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.1 github.com/goccy/go-json v0.10.0 diff --git a/go.sum b/go.sum index f86942b5..409d393b 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.6.0 h1:j90DM/Ss1bmySEQYL2U4jRsUjJ+chASzCCZYxohJR60= -github.com/bytedance/sonic v1.6.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.6.1 h1:HEyWqlvEh95R/rMg5Mh6jDx5Zt35MG24QWzpHMVuan0= +github.com/bytedance/sonic v1.6.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From 7cb151bb4c4cfc6018a00a125422ff38a041b9f8 Mon Sep 17 00:00:00 2001 From: adrianiacobghiula <2491756+adrianiacobghiula@users.noreply.github.com> Date: Mon, 16 Jan 2023 15:50:07 +0100 Subject: [PATCH 042/332] fix(context): panic on NegotiateFormat - index out of range (#3397) --- context.go | 2 +- context_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index b1352b9b..04742527 100644 --- a/context.go +++ b/context.go @@ -1147,7 +1147,7 @@ func (c *Context) NegotiateFormat(offered ...string) string { // According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers, // therefore we can just iterate over the string without casting it into []rune i := 0 - for ; i < len(accepted); i++ { + for ; i < len(accepted) && i < len(offer); i++ { if accepted[i] == '*' || offer[i] == '*' { return offer } diff --git a/context_test.go b/context_test.go index 85e0a616..827ee0fa 100644 --- a/context_test.go +++ b/context_test.go @@ -1311,6 +1311,14 @@ func TestContextNegotiationFormatCustom(t *testing.T) { assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) } +func TestContextNegotiationFormat2(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("Accept", "image/tiff-fx") + + assert.Equal(t, "", c.NegotiateFormat("image/tiff")) +} + func TestContextIsAborted(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.False(t, c.IsAborted()) From 97082f8accd197ec1240b31df3a20993c3747e6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jan 2023 09:58:28 +0800 Subject: [PATCH 043/332] chore(deps): bump github.com/bytedance/sonic from 1.6.1 to 1.7.0 (#3473) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.6.1 to 1.7.0. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.6.1...v1.7.0) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 c498b482..e03001ec 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.6.1 + github.com/bytedance/sonic v1.7.0 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.1 github.com/goccy/go-json v0.10.0 diff --git a/go.sum b/go.sum index 409d393b..4e4e9632 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.6.1 h1:HEyWqlvEh95R/rMg5Mh6jDx5Zt35MG24QWzpHMVuan0= -github.com/bytedance/sonic v1.6.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.7.0 h1:P7DyGrkLbVDzcuqagPsSFnAwwljjhmB3qVF5wzmHOxE= +github.com/bytedance/sonic v1.7.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From 1660995a04f579b4e0d5683ff45e1af4c2a50346 Mon Sep 17 00:00:00 2001 From: Heliner <32272517+Heliner@users.noreply.github.com> Date: Tue, 17 Jan 2023 14:23:54 +0800 Subject: [PATCH 044/332] Adjust the position of some functions (#3385) Co-authored-by: fredhan --- binding/toml.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/binding/toml.go b/binding/toml.go index a6b8a90a..a66b93aa 100644 --- a/binding/toml.go +++ b/binding/toml.go @@ -18,14 +18,6 @@ func (tomlBinding) Name() string { return "toml" } -func decodeToml(r io.Reader, obj any) error { - decoder := toml.NewDecoder(r) - if err := decoder.Decode(obj); err != nil { - return err - } - return decoder.Decode(obj) -} - func (tomlBinding) Bind(req *http.Request, obj any) error { return decodeToml(req.Body, obj) } @@ -33,3 +25,11 @@ func (tomlBinding) Bind(req *http.Request, obj any) error { func (tomlBinding) BindBody(body []byte, obj any) error { return decodeToml(bytes.NewReader(body), obj) } + +func decodeToml(r io.Reader, obj any) error { + decoder := toml.NewDecoder(r) + if err := decoder.Decode(obj); err != nil { + return err + } + return decoder.Decode(obj) +} From 8cd11c82e447f74d63e3da6037cb0463440d8e16 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Tue, 17 Jan 2023 14:26:27 +0800 Subject: [PATCH 045/332] chore(docs): Remove the Brigade project, because the Gin is no longer used in the latest version and the Brigade is an archived CNCF project now (#3378) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index eccf814c..336155a9 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,6 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. * [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. -* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. From b2d4185eec36ce5e0cf21be7cb246fb8be9fd6db Mon Sep 17 00:00:00 2001 From: hopehook Date: Fri, 20 Jan 2023 09:51:42 +0800 Subject: [PATCH 046/332] Replace bytes.Buffer with strings.Builder where appropriate (#3347) To build strings more efficiently, use strings.Builder instead. --- debug_test.go | 4 ++-- githubapi_test.go | 4 ++-- logger_test.go | 14 +++++++------- recovery_test.go | 19 +++++++++---------- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/debug_test.go b/debug_test.go index abe8b41c..ce8b19da 100644 --- a/debug_test.go +++ b/debug_test.go @@ -5,7 +5,6 @@ package gin import ( - "bytes" "errors" "fmt" "html/template" @@ -13,6 +12,7 @@ import ( "log" "os" "runtime" + "strings" "sync" "testing" @@ -138,7 +138,7 @@ func captureOutput(t *testing.T, f func()) string { wg := new(sync.WaitGroup) wg.Add(1) go func() { - var buf bytes.Buffer + var buf strings.Builder wg.Done() _, err := io.Copy(&buf, reader) assert.NoError(t, err) diff --git a/githubapi_test.go b/githubapi_test.go index c6350e81..9276bed5 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -5,12 +5,12 @@ package gin import ( - "bytes" "fmt" "math/rand" "net/http" "net/http/httptest" "os" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -401,7 +401,7 @@ func TestGithubAPI(t *testing.T) { } func exampleFromPath(path string) (string, Params) { - output := new(bytes.Buffer) + output := new(strings.Builder) params := make(Params, 0, 6) start := -1 for i, c := range path { diff --git a/logger_test.go b/logger_test.go index 7bc11371..5f78708f 100644 --- a/logger_test.go +++ b/logger_test.go @@ -5,10 +5,10 @@ package gin import ( - "bytes" "errors" "fmt" "net/http" + "strings" "testing" "time" @@ -20,7 +20,7 @@ func init() { } func TestLogger(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.Use(LoggerWithWriter(buffer)) router.GET("/example", func(c *Context) {}) @@ -84,7 +84,7 @@ func TestLogger(t *testing.T) { } func TestLoggerWithConfig(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.Use(LoggerWithConfig(LoggerConfig{Output: buffer})) router.GET("/example", func(c *Context) {}) @@ -148,7 +148,7 @@ func TestLoggerWithConfig(t *testing.T) { } func TestLoggerWithFormatter(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) d := DefaultWriter DefaultWriter = buffer @@ -182,7 +182,7 @@ func TestLoggerWithFormatter(t *testing.T) { func TestLoggerWithConfigFormatting(t *testing.T) { var gotParam LogFormatterParams var gotKeys map[string]any - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.engine.trustedCIDRs, _ = router.engine.prepareTrustedCIDRs() @@ -382,7 +382,7 @@ func TestErrorLogger(t *testing.T) { } func TestLoggerWithWriterSkippingPaths(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.Use(LoggerWithWriter(buffer, "/skipped")) router.GET("/logged", func(c *Context) {}) @@ -397,7 +397,7 @@ func TestLoggerWithWriterSkippingPaths(t *testing.T) { } func TestLoggerWithConfigSkippingPaths(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.Use(LoggerWithConfig(LoggerConfig{ Output: buffer, diff --git a/recovery_test.go b/recovery_test.go index 347917e7..fa8ab894 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -5,7 +5,6 @@ package gin import ( - "bytes" "fmt" "net" "net/http" @@ -18,7 +17,7 @@ import ( ) func TestPanicClean(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() password := "my-super-secret-password" router.Use(RecoveryWithWriter(buffer)) @@ -50,7 +49,7 @@ func TestPanicClean(t *testing.T) { // TestPanicInHandler assert that panic has been recovered. func TestPanicInHandler(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.Use(RecoveryWithWriter(buffer)) router.GET("/recovery", func(_ *Context) { @@ -122,7 +121,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { for errno, expectMsg := range expectMsgs { t.Run(expectMsg, func(t *testing.T) { - var buf bytes.Buffer + var buf strings.Builder router := New() router.Use(RecoveryWithWriter(&buf)) @@ -145,8 +144,8 @@ func TestPanicWithBrokenPipe(t *testing.T) { } func TestCustomRecoveryWithWriter(t *testing.T) { - errBuffer := new(bytes.Buffer) - buffer := new(bytes.Buffer) + errBuffer := new(strings.Builder) + buffer := new(strings.Builder) router := New() handleRecovery := func(c *Context, err any) { errBuffer.WriteString(err.(string)) @@ -179,8 +178,8 @@ func TestCustomRecoveryWithWriter(t *testing.T) { } func TestCustomRecovery(t *testing.T) { - errBuffer := new(bytes.Buffer) - buffer := new(bytes.Buffer) + errBuffer := new(strings.Builder) + buffer := new(strings.Builder) router := New() DefaultErrorWriter = buffer handleRecovery := func(c *Context, err any) { @@ -214,8 +213,8 @@ func TestCustomRecovery(t *testing.T) { } func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { - errBuffer := new(bytes.Buffer) - buffer := new(bytes.Buffer) + errBuffer := new(strings.Builder) + buffer := new(strings.Builder) router := New() DefaultErrorWriter = buffer handleRecovery := func(c *Context, err any) { From ea1787503586f94d7d79323573d35eb3f442a561 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 19:53:10 +0800 Subject: [PATCH 047/332] chore(deps): bump golangci/golangci-lint-action from 3.3.1 to 3.4.0 (#3478) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.3.1 to 3.4.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.3.1...v3.4.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action 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> --- .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 7a4e61c6..9bd2698c 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.3.1 + uses: golangci/golangci-lint-action@v3.4.0 with: version: v1.48.0 args: --verbose From c5fd06361b934070e99978c34e1eaef05632bb5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 19:53:45 +0800 Subject: [PATCH 048/332] chore(deps): bump github.com/go-playground/validator/v10 (#3482) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.11.1 to 10.11.2. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.11.1...v10.11.2) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 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 | 9 +++++---- go.sum | 40 +++++++++------------------------------- 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index e03001ec..ea2c94ac 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/bytedance/sonic v1.7.0 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.11.1 + github.com/go-playground/validator/v10 v10.11.2 github.com/goccy/go-json v0.10.0 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.17 @@ -20,16 +20,17 @@ require ( require ( github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/klauspost/cpuid/v2 v2.0.14 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect - golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect + golang.org/x/crypto v0.5.0 // indirect golang.org/x/sys v0.4.0 // indirect golang.org/x/text v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index 4e4e9632..09341d45 100644 --- a/go.sum +++ b/go.sum @@ -10,14 +10,13 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -29,12 +28,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8= github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= @@ -47,12 +41,9 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -70,36 +61,23 @@ github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZg golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From e02ae6ae61fada360379b5bdc7f23e46f21ce5de Mon Sep 17 00:00:00 2001 From: "Alireza (Pure)" Date: Mon, 6 Feb 2023 11:16:42 +0330 Subject: [PATCH 049/332] chore(router): match method added to routergroup for multiple HTTP methods supporting (#3464) --- routergroup.go | 10 ++++++++++ routergroup_test.go | 1 + 2 files changed, 11 insertions(+) diff --git a/routergroup.go b/routergroup.go index dfbdd7b8..c833fe8f 100644 --- a/routergroup.go +++ b/routergroup.go @@ -42,6 +42,7 @@ type IRoutes interface { PUT(string, ...HandlerFunc) IRoutes OPTIONS(string, ...HandlerFunc) IRoutes HEAD(string, ...HandlerFunc) IRoutes + Match([]string, string, ...HandlerFunc) IRoutes StaticFile(string, string) IRoutes StaticFileFS(string, string, http.FileSystem) IRoutes @@ -151,6 +152,15 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRou return group.returnObj() } +// Match registers a route that matches the specified methods that you declared. +func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes { + for _, method := range methods { + group.handle(method, relativePath, handlers) + } + + return group.returnObj() +} + // StaticFile registers a single route in order to serve a single file of the local filesystem. // router.StaticFile("favicon.ico", "./resources/favicon.ico") func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { diff --git a/routergroup_test.go b/routergroup_test.go index 41f96372..6848063e 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -186,6 +186,7 @@ func testRoutesInterface(t *testing.T, r IRoutes) { assert.Equal(t, r, r.PUT("/", handler)) assert.Equal(t, r, r.OPTIONS("/", handler)) assert.Equal(t, r, r.HEAD("/", handler)) + assert.Equal(t, r, r.Match([]string{http.MethodPut, http.MethodPatch}, "/match", handler)) assert.Equal(t, r, r.StaticFile("/file", ".")) assert.Equal(t, r, r.StaticFileFS("/static2", ".", Dir(".", false))) From 153b229fcc6570bac0674d02ab1a629804f29072 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 16:37:36 +0800 Subject: [PATCH 050/332] chore(deps): bump github.com/ugorji/go/codec from 1.2.8 to 1.2.9 (#3491) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.2.8 to 1.2.9. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/v1.2.8...v1.2.9) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec 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 ea2c94ac..4ec53257 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/mattn/go-isatty v0.0.17 github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 - github.com/ugorji/go/codec v1.2.8 + github.com/ugorji/go/codec v1.2.9 golang.org/x/net v0.5.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 09341d45..b2d2f736 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0= -github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= +github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= From 0c96a20209ca035964be126a745c167196fb6db3 Mon Sep 17 00:00:00 2001 From: Vladislav Dmitriyev Date: Sun, 12 Feb 2023 05:01:33 +0300 Subject: [PATCH 051/332] Stop useless panicking in context and render (#2150) Co-authored-by: Bo-Yi Wu --- context.go | 4 +++- context_test.go | 20 +++++++++----------- render/json.go | 7 ++----- render/render_test.go | 4 ++-- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/context.go b/context.go index 04742527..556f8ac9 100644 --- a/context.go +++ b/context.go @@ -924,7 +924,9 @@ func (c *Context) Render(code int, r render.Render) { } if err := r.Render(c.Writer); err != nil { - panic(err) + // Pushing error to c.Errors + _ = c.Error(err) + c.Abort() } } diff --git a/context_test.go b/context_test.go index 827ee0fa..1ab6b339 100644 --- a/context_test.go +++ b/context_test.go @@ -32,6 +32,8 @@ import ( var _ context.Context = (*Context)(nil) +var errTestRender = errors.New("TestRender") + // Unit tests TODO // func (c *Context) File(filepath string) { // func (c *Context) Negotiate(code int, config Negotiate) { @@ -643,25 +645,21 @@ func TestContextBodyAllowedForStatus(t *testing.T) { assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) } -type TestPanicRender struct{} +type TestRender struct{} -func (*TestPanicRender) Render(http.ResponseWriter) error { - return errors.New("TestPanicRender") +func (*TestRender) Render(http.ResponseWriter) error { + return errTestRender } -func (*TestPanicRender) WriteContentType(http.ResponseWriter) {} +func (*TestRender) WriteContentType(http.ResponseWriter) {} -func TestContextRenderPanicIfErr(t *testing.T) { - defer func() { - r := recover() - assert.Equal(t, "TestPanicRender", fmt.Sprint(r)) - }() +func TestContextRenderIfErr(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Render(http.StatusOK, &TestPanicRender{}) + c.Render(http.StatusOK, &TestRender{}) - assert.Fail(t, "Panic not detected") + assert.Equal(t, errorMsgs{&Error{Err: errTestRender, Type: 1}}, c.Errors) } // Tests that the response is serialized as JSON diff --git a/render/json.go b/render/json.go index af678e80..fc8dea45 100644 --- a/render/json.go +++ b/render/json.go @@ -53,11 +53,8 @@ var ( ) // Render (JSON) writes data with custom ContentType. -func (r JSON) Render(w http.ResponseWriter) (err error) { - if err = WriteJSON(w, r.Data); err != nil { - panic(err) - } - return +func (r JSON) Render(w http.ResponseWriter) error { + return WriteJSON(w, r.Data) } // WriteContentType (JSON) writes JSON ContentType. diff --git a/render/render_test.go b/render/render_test.go index ebb7d414..19255251 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -40,12 +40,12 @@ func TestRenderJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } -func TestRenderJSONPanics(t *testing.T) { +func TestRenderJSONError(t *testing.T) { w := httptest.NewRecorder() data := make(chan int) // json: unsupported type: chan int - assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) }) + assert.Error(t, (JSON{data}).Render(w)) } func TestRenderIndentedJSON(t *testing.T) { From bd82c9e351be91e9e8267e5ce011627dd6c55d51 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Sun, 12 Feb 2023 13:01:05 +0800 Subject: [PATCH 052/332] =?UTF-8?q?chore(go):=20Add=C2=A0support=20go=201.?= =?UTF-8?q?20=20(#3484)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(go): Addย support go 1.20 * Surround the go version parameters with single quotes * chore(deps): bump github.com/bytedance/sonic from v1.7.0 to v1.7.1 --- .github/workflows/gin.yml | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 9bd2698c..fac97d47 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -31,7 +31,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: [1.16, 1.17, 1.18, 1.19] + go: ['1.16', '1.17', '1.18', '1.19', '1.20'] test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] include: - os: ubuntu-latest diff --git a/go.mod b/go.mod index 4ec53257..51353f54 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.7.0 + github.com/bytedance/sonic v1.7.1 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.2 github.com/goccy/go-json v0.10.0 diff --git a/go.sum b/go.sum index b2d2f736..01f94952 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.7.0 h1:P7DyGrkLbVDzcuqagPsSFnAwwljjhmB3qVF5wzmHOxE= -github.com/bytedance/sonic v1.7.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.7.1 h1:UYWEKUHQDye89c2U6zvrvuxWdGCI/wCrZITFQmKGtGc= +github.com/bytedance/sonic v1.7.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From c1d06e3d08692f9eddde381a5e277b41fff5a297 Mon Sep 17 00:00:00 2001 From: David Desmarais-Michaud Date: Sun, 12 Feb 2023 00:01:43 -0500 Subject: [PATCH 053/332] add supprt for go1.20 http.rwUnwrapper to gin.responseWriter (#3489) --- response_writer.go | 4 ++++ response_writer_test.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/response_writer.go b/response_writer.go index 5cec1f66..753a0b09 100644 --- a/response_writer.go +++ b/response_writer.go @@ -51,6 +51,10 @@ type responseWriter struct { var _ ResponseWriter = (*responseWriter)(nil) +func (w *responseWriter) Unwrap() http.ResponseWriter { + return w.ResponseWriter +} + func (w *responseWriter) reset(writer http.ResponseWriter) { w.ResponseWriter = writer w.size = noWritten diff --git a/response_writer_test.go b/response_writer_test.go index 6fa5ec71..9fd5e87c 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -30,6 +30,12 @@ func init() { SetMode(TestMode) } +func TestResponseWriterUnwrap(t *testing.T) { + testWriter := httptest.NewRecorder() + writer := &responseWriter{ResponseWriter: testWriter} + assert.Same(t, testWriter, writer.Unwrap()) +} + func TestResponseWriterReset(t *testing.T) { testWriter := httptest.NewRecorder() writer := &responseWriter{} From d07db174acf44bfaf191ca2f6d7beafa2ff946da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Feb 2023 10:59:36 +0800 Subject: [PATCH 054/332] chore(deps): bump golang.org/x/net from 0.5.0 to 0.6.0 (#3498) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.6.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: golang.org/x/net 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 | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 51353f54..2ef45cfe 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.9 - golang.org/x/net v0.5.0 + golang.org/x/net v0.6.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -31,6 +31,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect golang.org/x/crypto v0.5.0 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/text v0.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 01f94952..b47804fc 100644 --- a/go.sum +++ b/go.sum @@ -63,13 +63,13 @@ golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SX golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= From 81ac7d55a09e34013225db0aeac6e70c1ae68928 Mon Sep 17 00:00:00 2001 From: t0rchwo0d Date: Fri, 17 Feb 2023 11:00:19 +0900 Subject: [PATCH 055/332] Add escape logic for header (#3500) --- gin.go | 4 ++++ routes_test.go | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/gin.go b/gin.go index 35159d03..32dae249 100644 --- a/gin.go +++ b/gin.go @@ -9,6 +9,7 @@ import ( "html/template" "net" "net/http" + "net/url" "os" "path" "strings" @@ -668,6 +669,9 @@ func redirectTrailingSlash(c *Context) { req := c.Request p := req.URL.Path if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { + prefix = url.QueryEscape(prefix) + prefix = strings.ReplaceAll(prefix, "%2F", "/") + p = prefix + "/" + req.URL.Path } req.URL.Path = p + "/" diff --git a/routes_test.go b/routes_test.go index ada8e1e4..5310caec 100644 --- a/routes_test.go +++ b/routes_test.go @@ -185,6 +185,18 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) assert.Equal(t, 200, w.Code) + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../bug#?"}) + assert.Equal(t, "../../../bug%2523%253F/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"}) + assert.Equal(t, "https%3A/gin-gonic.com/%23/https%253A/gin-gonic.com/%2523/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#bug"}) + assert.Equal(t, "%23bug/%2523bug/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + router.RedirectTrailingSlash = false w = PerformRequest(router, http.MethodGet, "/path/") From fc1c43298de675e5252d0b44f97dc5e204bd4e1e Mon Sep 17 00:00:00 2001 From: Kevin Chen Date: Sat, 18 Feb 2023 01:43:39 -0500 Subject: [PATCH 056/332] fix(security): vulnerability GO-2023-1571 (#3505) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2ef45cfe..f7e28d8c 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.9 - golang.org/x/net v0.6.0 + golang.org/x/net v0.7.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index b47804fc..814f4eb3 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,8 @@ golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SX golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 4cee78f5382d5245c3652e6c15fee715eec505c3 Mon Sep 17 00:00:00 2001 From: t0rchwo0d Date: Sun, 19 Feb 2023 22:25:48 +0900 Subject: [PATCH 057/332] Fix #3500 Add escape logic for header (#3503) --- gin.go | 9 ++++++--- routes_test.go | 46 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/gin.go b/gin.go index 32dae249..f95e5dda 100644 --- a/gin.go +++ b/gin.go @@ -9,9 +9,9 @@ import ( "html/template" "net" "net/http" - "net/url" "os" "path" + "regexp" "strings" "sync" @@ -41,6 +41,9 @@ var defaultTrustedCIDRs = []*net.IPNet{ }, } +var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") +var regRemoveRepeatedChar = regexp.MustCompile("/{2,}") + // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) @@ -669,8 +672,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 = url.QueryEscape(prefix) - prefix = strings.ReplaceAll(prefix, "%2F", "/") + prefix = regSafePrefix.ReplaceAllString(prefix, "") + prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/") p = prefix + "/" + req.URL.Path } diff --git a/routes_test.go b/routes_test.go index 5310caec..633c0aba 100644 --- a/routes_test.go +++ b/routes_test.go @@ -185,16 +185,52 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) assert.Equal(t, 200, w.Code) - w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../bug#?"}) - assert.Equal(t, "../../../bug%2523%253F/path", w.Header().Get("Location")) + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api#?"}) + assert.Equal(t, "/api/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api"}) + assert.Equal(t, "/api/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../api"}) + assert.Equal(t, "/api/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../api"}) + assert.Equal(t, "/api/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../"}) + assert.Equal(t, "//path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../../"}) + assert.Equal(t, "/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../gin-gonic.com"}) + assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../gin-gonic.com"}) + assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location")) assert.Equal(t, 301, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"}) - assert.Equal(t, "https%3A/gin-gonic.com/%23/https%253A/gin-gonic.com/%2523/path", w.Header().Get("Location")) + assert.Equal(t, "https/gin-goniccom/https/gin-goniccom/path", w.Header().Get("Location")) assert.Equal(t, 301, w.Code) - w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#bug"}) - assert.Equal(t, "%23bug/%2523bug/path", w.Header().Get("Location")) + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#api"}) + assert.Equal(t, "api/api/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/#?a=1"}) + assert.Equal(t, "/nor-mal/a1/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/%2e%2e/"}) + assert.Equal(t, "/nor-mal/2e2e/path", w.Header().Get("Location")) assert.Equal(t, 301, w.Code) router.RedirectTrailingSlash = false From ea03e10384502e1baf6f560a2b0ea32c342ede5b Mon Sep 17 00:00:00 2001 From: thinkerou Date: Tue, 21 Feb 2023 17:20:32 +0800 Subject: [PATCH 058/332] docs(readme): release v1.9.0 version (#3474) --- CHANGELOG.md | 79 ++++++++++++++++++++++++++++++++++++++++++---------- go.mod | 6 ++-- go.sum | 10 +++---- version.go | 2 +- 4 files changed, 73 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ab56179..cf24ec28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,49 @@ # Gin ChangeLog +## Gin v1.9.0 + +### BREAK CHANGES + +* Stop useless panicking in context and render [#2150](https://github.com/gin-gonic/gin/pull/2150) + +### BUG FIXES + +* fix(router): tree bug where loop index is not decremented. [#3460](https://github.com/gin-gonic/gin/pull/3460) +* fix(context): panic on NegotiateFormat - index out of range [#3397](https://github.com/gin-gonic/gin/pull/3397) +* Add escape logic for header [#3500](https://github.com/gin-gonic/gin/pull/3500) and [#3503](https://github.com/gin-gonic/gin/pull/3503) + +### SECURITY + +* Fix the GO-2022-0969 and GO-2022-0288 vulnerabilities [#3333](https://github.com/gin-gonic/gin/pull/3333) +* fix(security): vulnerability GO-2023-1571 [#3505](https://github.com/gin-gonic/gin/pull/3505) + +### ENHANCEMENTS + +* feat: add sonic json support [#3184](https://github.com/gin-gonic/gin/pull/3184) +* chore(file): Creates a directory named path [#3316](https://github.com/gin-gonic/gin/pull/3316) +* fix: modify interface check way [#3327](https://github.com/gin-gonic/gin/pull/3327) +* remove deprecated of package io/ioutil [#3395](https://github.com/gin-gonic/gin/pull/3395) +* refactor: avoid calling strings.ToLower twice [#3343](https://github.com/gin-gonic/gin/pull/3433) +* console logger HTTP status code bug fixed [#3453](https://github.com/gin-gonic/gin/pull/3453) +* chore(yaml): upgrade dependency to v3 version [#3456](https://github.com/gin-gonic/gin/pull/3456) +* chore(router): match method added to routergroup for multiple HTTP methods supporting [#3464](https://github.com/gin-gonic/gin/pull/3464) +* chore(http): add support for go1.20 http.rwUnwrapper to gin.responseWriter [#3489](https://github.com/gin-gonic/gin/pull/3489) + +### DOCS + +* docs: update markdown format [#3260](https://github.com/gin-gonic/gin/pull/3260) +* docs(readme): Add the TOML rendering example [#3400](https://github.com/gin-gonic/gin/pull/3400) +* docs(readme): move more example to docs/doc.md [#3449](https://github.com/gin-gonic/gin/pull/3449) +* docs: update markdown format [#3446](https://github.com/gin-gonic/gin/pull/3446) + ## Gin v1.8.2 -### Bugs +### BUG FIXES * fix(route): redirectSlash bug ([#3227]((https://github.com/gin-gonic/gin/pull/3227))) * fix(engine): missing route params for CreateTestContext ([#2778]((https://github.com/gin-gonic/gin/pull/2778))) ([#2803]((https://github.com/gin-gonic/gin/pull/2803))) -### Security +### SECURITY * Fix the GO-2022-1144 vulnerability ([#3432]((https://github.com/gin-gonic/gin/pull/3432))) @@ -19,12 +55,12 @@ ## Gin v1.8.0 -## Break Changes +### BREAK CHANGES * TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967). Please replace `RemoteIP() (net.IP, bool)` with `RemoteIP() net.IP` * gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751) -### BUGFIXES +### BUG FIXES * Fixed SetOutput() panics on go 1.17 [#2861](https://github.com/gin-gonic/gin/pull/2861) * Fix: wrong when wildcard follows named param [#2983](https://github.com/gin-gonic/gin/pull/2983) @@ -61,7 +97,7 @@ ## Gin v1.7.7 -### BUGFIXES +### BUG FIXES * Fixed X-Forwarded-For unsafe handling of CVE-2020-28483 [#2844](https://github.com/gin-gonic/gin/pull/2844), closed issue [#2862](https://github.com/gin-gonic/gin/issues/2862). * Tree: updated the code logic for `latestNode` [#2897](https://github.com/gin-gonic/gin/pull/2897), closed issue [#2894](https://github.com/gin-gonic/gin/issues/2894) [#2878](https://github.com/gin-gonic/gin/issues/2878). @@ -79,37 +115,37 @@ ## Gin v1.7.6 -### BUGFIXES +### BUG FIXES * bump new release to fix v1.7.5 release error by using v1.7.4 codes. ## Gin v1.7.4 -### BUGFIXES +### BUG FIXES * bump new release to fix checksum mismatch ## Gin v1.7.3 -### BUGFIXES +### BUG FIXES * fix level 1 router match [#2767](https://github.com/gin-gonic/gin/issues/2767), [#2796](https://github.com/gin-gonic/gin/issues/2796) ## Gin v1.7.2 -### BUGFIXES +### BUG FIXES * Fix conflict between param and exact path [#2706](https://github.com/gin-gonic/gin/issues/2706). Close issue [#2682](https://github.com/gin-gonic/gin/issues/2682) [#2696](https://github.com/gin-gonic/gin/issues/2696). ## Gin v1.7.1 -### BUGFIXES +### BUG FIXES * fix: data race with trustedCIDRs from [#2674](https://github.com/gin-gonic/gin/issues/2674)([#2675](https://github.com/gin-gonic/gin/pull/2675)) ## Gin v1.7.0 -### BUGFIXES +### BUG FIXES * fix compile error from [#2572](https://github.com/gin-gonic/gin/pull/2572) ([#2600](https://github.com/gin-gonic/gin/pull/2600)) * fix: print headers without Authorization header on broken pipe ([#2528](https://github.com/gin-gonic/gin/pull/2528)) @@ -148,33 +184,44 @@ ## Gin v1.6.2 -### BUGFIXES +### BUG FIXES + * fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305) + ### ENHANCEMENTS + * Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306) ## Gin v1.6.1 -### BUGFIXES +### BUG FIXES + * Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294) ## Gin v1.6.0 ### BREAKING + * chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159) * drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148) * Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615) + ### FEATURES + * add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220) * FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112) -### BUGFIXES + +### BUG FIXES + * Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280) * Use json marshall in context json to fix breaking new line issue. Fixes #2209 [#2228](https://github.com/gin-gonic/gin/pull/2228) * fix accept incoming network connections [#2216](https://github.com/gin-gonic/gin/pull/2216) * Fixed a bug in the calculation of the maximum number of parameters [#2166](https://github.com/gin-gonic/gin/pull/2166) * [FIX] allow empty headers on DataFromReader [#2121](https://github.com/gin-gonic/gin/pull/2121) * Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391) + ### ENHANCEMENTS + * Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277) * tree: range over nodes values [#2229](https://github.com/gin-gonic/gin/pull/2229) * tree: remove duplicate assignment [#2222](https://github.com/gin-gonic/gin/pull/2222) @@ -189,7 +236,9 @@ * upgrade go-validator to v10 [#2149](https://github.com/gin-gonic/gin/pull/2149) * Refactor redirect request in gin.go [#1970](https://github.com/gin-gonic/gin/pull/1970) * Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852) + ### DOCS + * docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223) * Renew README to fit the modification of SetCookie method [#2217](https://github.com/gin-gonic/gin/pull/2217) * Fix spelling [#2202](https://github.com/gin-gonic/gin/pull/2202) @@ -202,7 +251,9 @@ * Add project to README [#2165](https://github.com/gin-gonic/gin/pull/2165) * docs(benchmarks): for gin v1.5 [#2153](https://github.com/gin-gonic/gin/pull/2153) * Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122) + ### MISC + * ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262) * chore: upgrade depend version [#2231](https://github.com/gin-gonic/gin/pull/2231) * Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147) diff --git a/go.mod b/go.mod index f7e28d8c..db36337e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.7.1 + github.com/bytedance/sonic v1.8.0 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.2 github.com/goccy/go-json v0.10.0 @@ -22,14 +22,14 @@ require ( github.com/davecgh/go-spew v1.1.1 // 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.14 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.5.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect diff --git a/go.sum b/go.sum index 814f4eb3..8bdb934a 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.7.1 h1:UYWEKUHQDye89c2U6zvrvuxWdGCI/wCrZITFQmKGtGc= -github.com/bytedance/sonic v1.7.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= @@ -25,9 +25,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8= -github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -58,9 +57,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.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= -golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= diff --git a/version.go b/version.go index 37e27f27..390da4f3 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.8.2" +const Version = "v1.9.0" From 0b5df9fc3992bde6e13fd71b795ff4f8b27d4f65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:42:49 +0800 Subject: [PATCH 059/332] chore(deps): bump github.com/bytedance/sonic from 1.7.1 to 1.8.1 (#3508) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.7.1 to 1.8.1. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.7.1...v1.8.1) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic 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 db36337e..3ec47800 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.8.0 + github.com/bytedance/sonic v1.8.1 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.2 github.com/goccy/go-json v0.10.0 diff --git a/go.sum b/go.sum index 8bdb934a..d6a91933 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= -github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.8.1 h1:NqAHCaGaTzro0xMmnTCLUyRlbEP6r8MCA1cJUrH3Pu4= +github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From 943e93cba04808294d0748b74bcdc8322b8ebaa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:43:24 +0800 Subject: [PATCH 060/332] chore(deps): bump github.com/ugorji/go/codec from 1.2.9 to 1.2.10 (#3509) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.2.9 to 1.2.10. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/v1.2.9...v1.2.10) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec 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 3ec47800..da978740 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/mattn/go-isatty v0.0.17 github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 - github.com/ugorji/go/codec v1.2.9 + github.com/ugorji/go/codec v1.2.10 golang.org/x/net v0.7.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index d6a91933..cab49ab0 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= -github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= +github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= From 1e1f0b1e76b89b48542171e2c5ee829a69c2b91f Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 1 Mar 2023 10:03:48 +0800 Subject: [PATCH 061/332] chore: support min go version 1.18 (#3511) * chore: min go version 1.18 * fix build tag error * remove build tag * fix word * remove any.go * replace interface{} instead of any --- .github/workflows/gin.yml | 6 +- .github/workflows/goreleaser.yml | 2 +- README.md | 2 +- any.go | 10 --- binding/any.go | 10 --- binding/binding.go | 1 - binding/binding_msgpack_test.go | 1 - binding/binding_nomsgpack.go | 1 - binding/json.go | 2 +- binding/msgpack.go | 1 - binding/msgpack_test.go | 1 - context.go | 6 +- context_1.17_test.go | 94 -------------------- context_1.16_test.go => context_1.18_test.go | 20 +++-- context_1.19_test.go | 1 - context_appengine.go | 1 - context_test.go | 41 ++++++++- debug.go | 4 +- debug_test.go | 4 +- deprecated.go | 2 +- docs/doc.md | 8 +- internal/json/go_json.go | 1 - internal/json/json.go | 3 - internal/json/jsoniter.go | 1 - internal/json/sonic.go | 4 - render/any.go | 10 --- render/msgpack.go | 1 - render/render_msgpack_test.go | 1 - testdata/protoexample/any.go | 10 --- utils.go | 2 +- 30 files changed, 72 insertions(+), 179 deletions(-) delete mode 100644 any.go delete mode 100644 binding/any.go delete mode 100644 context_1.17_test.go rename context_1.16_test.go => context_1.18_test.go (66%) delete mode 100644 render/any.go delete mode 100644 testdata/protoexample/any.go diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index fac97d47..5c1504a9 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -18,7 +18,7 @@ jobs: - name: Setup go uses: actions/setup-go@v3 with: - go-version: '^1.16' + go-version: '^1.18' - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint @@ -31,7 +31,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ['1.16', '1.17', '1.18', '1.19', '1.20'] + go: ['1.18', '1.19', '1.20'] test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] include: - os: ubuntu-latest @@ -73,7 +73,7 @@ jobs: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - name: Format - if: matrix.go-version == '1.19.x' + if: matrix.go-version == '1.20.x' run: diff -u <(echo -n) <(gofmt -d .) notification-gitter: needs: test diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 3af3a455..baf02af5 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -21,7 +21,7 @@ jobs: name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.17 + go-version: 1.20 - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 diff --git a/README.md b/README.md index 336155a9..cba54ab8 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Gin is a web framework written in [Go](https://go.dev/). It features a martini-l ### Prerequisites -- **[Go](https://go.dev/)**: ~~any one of the **three latest major** [releases](https://go.dev/doc/devel/release)~~ (now version **1.16+** is required). +- **[Go](https://go.dev/)**: any one of the **three latest major** [releases](https://go.dev/doc/devel/release) (we test it with these). ### Getting Gin diff --git a/any.go b/any.go deleted file mode 100644 index 42b1ea46..00000000 --- a/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package gin - -type any = interface{} diff --git a/binding/any.go b/binding/any.go deleted file mode 100644 index d8251a7c..00000000 --- a/binding/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package binding - -type any = interface{} diff --git a/binding/binding.go b/binding/binding.go index a58924ed..40948529 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go index 04d94079..a6cd6aa8 100644 --- a/binding/binding_msgpack_test.go +++ b/binding/binding_msgpack_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index 7f6a904a..93ad8ba3 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build nomsgpack -// +build nomsgpack package binding diff --git a/binding/json.go b/binding/json.go index 36eb27a3..e21c2ee3 100644 --- a/binding/json.go +++ b/binding/json.go @@ -15,7 +15,7 @@ import ( // EnableDecoderUseNumber is used to call the UseNumber method on the JSON // Decoder instance. UseNumber causes the Decoder to unmarshal a number into an -// interface{} as a Number instead of as a float64. +// any as a Number instead of as a float64. var EnableDecoderUseNumber = false // EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method diff --git a/binding/msgpack.go b/binding/msgpack.go index d1f035e4..22de9b55 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/binding/msgpack_test.go b/binding/msgpack_test.go index 11561c84..df386a6d 100644 --- a/binding/msgpack_test.go +++ b/binding/msgpack_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/context.go b/context.go index 556f8ac9..5716318e 100644 --- a/context.go +++ b/context.go @@ -652,7 +652,7 @@ func (c *Context) BindYAML(obj any) error { } // BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML). -func (c *Context) BindTOML(obj interface{}) error { +func (c *Context) BindTOML(obj any) error { return c.MustBindWith(obj, binding.TOML) } @@ -717,7 +717,7 @@ func (c *Context) ShouldBindYAML(obj any) error { } // ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML). -func (c *Context) ShouldBindTOML(obj interface{}) error { +func (c *Context) ShouldBindTOML(obj any) error { return c.ShouldBindWith(obj, binding.TOML) } @@ -995,7 +995,7 @@ func (c *Context) YAML(code int, obj any) { } // TOML serializes the given struct as TOML into the response body. -func (c *Context) TOML(code int, obj interface{}) { +func (c *Context) TOML(code int, obj any) { c.Render(code, render.TOML{Data: obj}) } diff --git a/context_1.17_test.go b/context_1.17_test.go deleted file mode 100644 index 0f8527fe..00000000 --- a/context_1.17_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build go1.17 -// +build go1.17 - -package gin - -import ( - "bytes" - "mime/multipart" - "net/http" - "net/http/httptest" - "runtime" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -type interceptedWriter struct { - ResponseWriter - b *bytes.Buffer -} - -func (i interceptedWriter) WriteHeader(code int) { - i.Header().Del("X-Test") - i.ResponseWriter.WriteHeader(code) -} - -func TestContextFormFileFailed17(t *testing.T) { - if !isGo117OrGo118() { - return - } - buf := new(bytes.Buffer) - mw := multipart.NewWriter(buf) - defer func(mw *multipart.Writer) { - err := mw.Close() - if err != nil { - assert.Error(t, err) - } - }(mw) - c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Set("Content-Type", mw.FormDataContentType()) - c.engine.MaxMultipartMemory = 8 << 20 - assert.Panics(t, func() { - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) - }) -} - -func TestInterceptedHeader(t *testing.T) { - w := httptest.NewRecorder() - c, r := CreateTestContext(w) - - r.Use(func(c *Context) { - i := interceptedWriter{ - ResponseWriter: c.Writer, - b: bytes.NewBuffer(nil), - } - c.Writer = i - c.Next() - c.Header("X-Test", "overridden") - c.Writer = i.ResponseWriter - }) - r.GET("/", func(c *Context) { - c.Header("X-Test", "original") - c.Header("X-Test-2", "present") - c.String(http.StatusOK, "hello world") - }) - c.Request = httptest.NewRequest("GET", "/", nil) - r.HandleContext(c) - // Result() has headers frozen when WriteHeaderNow() has been called - // Compared to this time, this is when the response headers will be flushed - // As response is flushed on c.String, the Header cannot be set by the first - // middleware. Assert this - assert.Equal(t, "", w.Result().Header.Get("X-Test")) - assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) -} - -func isGo117OrGo118() bool { - version := strings.Split(runtime.Version()[2:], ".") - if len(version) >= 2 { - x := version[0] - y := version[1] - if x == "1" && (y == "17" || y == "18") { - return true - } - } - return false -} diff --git a/context_1.16_test.go b/context_1.18_test.go similarity index 66% rename from context_1.16_test.go rename to context_1.18_test.go index 26760507..6118beaa 100644 --- a/context_1.16_test.go +++ b/context_1.18_test.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build !go1.17 -// +build !go1.17 +//go:build !go1.19 package gin @@ -17,15 +16,22 @@ import ( "github.com/stretchr/testify/assert" ) -func TestContextFormFileFailed16(t *testing.T) { +func TestContextFormFileFailed18(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) - mw.Close() + defer func(mw *multipart.Writer) { + err := mw.Close() + if err != nil { + assert.Error(t, err) + } + }(mw) c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) c.engine.MaxMultipartMemory = 8 << 20 - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) + assert.Panics(t, func() { + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) + }) } diff --git a/context_1.19_test.go b/context_1.19_test.go index 4b34ea24..dd75325b 100644 --- a/context_1.19_test.go +++ b/context_1.19_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build go1.19 -// +build go1.19 package gin diff --git a/context_appengine.go b/context_appengine.go index 931313f6..96b339c4 100644 --- a/context_appengine.go +++ b/context_appengine.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build appengine -// +build appengine package gin diff --git a/context_test.go b/context_test.go index 1ab6b339..1dec902c 100644 --- a/context_test.go +++ b/context_test.go @@ -37,7 +37,7 @@ var errTestRender = errors.New("TestRender") // Unit tests TODO // func (c *Context) File(filepath string) { // func (c *Context) Negotiate(code int, config Negotiate) { -// BAD case: func (c *Context) Render(code int, render render.Render, obj ...interface{}) { +// BAD case: func (c *Context) Render(code int, render render.Render, obj ...any) { // test that information is not leaked when reusing Contexts (using the Pool) func createMultipartRequest() *http.Request { @@ -2374,3 +2374,42 @@ func TestCreateTestContextWithRouteParams(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "hello gin", w.Body.String()) } + +type interceptedWriter struct { + ResponseWriter + b *bytes.Buffer +} + +func (i interceptedWriter) WriteHeader(code int) { + i.Header().Del("X-Test") + i.ResponseWriter.WriteHeader(code) +} + +func TestInterceptedHeader(t *testing.T) { + w := httptest.NewRecorder() + c, r := CreateTestContext(w) + + r.Use(func(c *Context) { + i := interceptedWriter{ + ResponseWriter: c.Writer, + b: bytes.NewBuffer(nil), + } + c.Writer = i + c.Next() + c.Header("X-Test", "overridden") + c.Writer = i.ResponseWriter + }) + r.GET("/", func(c *Context) { + c.Header("X-Test", "original") + c.Header("X-Test-2", "present") + c.String(http.StatusOK, "hello world") + }) + c.Request = httptest.NewRequest("GET", "/", nil) + r.HandleContext(c) + // Result() has headers frozen when WriteHeaderNow() has been called + // Compared to this time, this is when the response headers will be flushed + // As response is flushed on c.String, the Header cannot be set by the first + // middleware. Assert this + assert.Equal(t, "", w.Result().Header.Get("X-Test")) + assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) +} diff --git a/debug.go b/debug.go index cbcedbc9..1fc0cafe 100644 --- a/debug.go +++ b/debug.go @@ -12,7 +12,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 16 +const ginSupportMinGoVer = 18 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.16+. + debugPrint(`[WARNING] Now Gin requires Go 1.18+. `) } diff --git a/debug_test.go b/debug_test.go index ce8b19da..2d5e9a56 100644 --- a/debug_test.go +++ b/debug_test.go @@ -21,7 +21,7 @@ import ( // TODO // func debugRoute(httpMethod, absolutePath string, handlers HandlersChain) { -// func debugPrint(format string, values ...interface{}) { +// func debugPrint(format string, values ...any) { func TestIsDebugging(t *testing.T) { SetMode(DebugMode) @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.16+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.18+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/deprecated.go b/deprecated.go index fdad8554..9521308f 100644 --- a/deprecated.go +++ b/deprecated.go @@ -13,7 +13,7 @@ import ( // BindWith binds the passed struct pointer using the specified binding engine. // See the binding package. func (c *Context) BindWith(obj any, b binding.Binding) error { - log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to + log.Println(`BindWith(\"any, binding.Binding\") error is going to be deprecated, please check issue #662 and either use MustBindWith() if you want HTTP 400 to be automatically returned if any error occur, or use ShouldBindWith() if you need to manage the error.`) diff --git a/docs/doc.md b/docs/doc.md index 7cebab56..e48c2ba1 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -425,7 +425,7 @@ func main() { r.Use(gin.Logger()) // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { + r.Use(gin.CustomRecovery(func(c *gin.Context, recovered any) { if err, ok := recovered.(string); ok { c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) } @@ -996,7 +996,7 @@ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost: func main() { r := gin.Default() - // gin.H is a shortcut for map[string]interface{} + // gin.H is a shortcut for map[string]any r.GET("/someJSON", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) @@ -1961,7 +1961,7 @@ func (customerBinding) Name() string { return "form" } -func (customerBinding) Bind(req *http.Request, obj interface{}) error { +func (customerBinding) Bind(req *http.Request, obj any) error { if err := req.ParseForm(); err != nil { return err } @@ -1976,7 +1976,7 @@ func (customerBinding) Bind(req *http.Request, obj interface{}) error { return validate(obj) } -func validate(obj interface{}) error { +func validate(obj any) error { if binding.Validator == nil { return nil } diff --git a/internal/json/go_json.go b/internal/json/go_json.go index 23f71726..47c35598 100644 --- a/internal/json/go_json.go +++ b/internal/json/go_json.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build go_json -// +build go_json package json diff --git a/internal/json/json.go b/internal/json/json.go index c5f3efc8..c7ee83eb 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -3,9 +3,6 @@ // license that can be found in the LICENSE file. //go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64) -// +build !jsoniter -// +build !go_json -// +build !sonic !avx !linux,!windows,!darwin !amd64 package json diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index 853b1a90..45ed16ba 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build jsoniter -// +build jsoniter package json diff --git a/internal/json/sonic.go b/internal/json/sonic.go index 5a9ca4b2..529e16d0 100644 --- a/internal/json/sonic.go +++ b/internal/json/sonic.go @@ -3,10 +3,6 @@ // license that can be found in the LICENSE file. //go:build sonic && avx && (linux || windows || darwin) && amd64 -// +build sonic -// +build avx -// +build linux windows darwin -// +build amd64 package json diff --git a/render/any.go b/render/any.go deleted file mode 100644 index b19ad45d..00000000 --- a/render/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package render - -type any = interface{} diff --git a/render/msgpack.go b/render/msgpack.go index e0f30f7a..d1d8e84b 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package render diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go index 64212361..db4b71e5 100644 --- a/render/render_msgpack_test.go +++ b/render/render_msgpack_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package render diff --git a/testdata/protoexample/any.go b/testdata/protoexample/any.go deleted file mode 100644 index 2203f33a..00000000 --- a/testdata/protoexample/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package protoexample - -type any = interface{} diff --git a/utils.go b/utils.go index 4021a2ab..47106a7a 100644 --- a/utils.go +++ b/utils.go @@ -50,7 +50,7 @@ func WrapH(h http.Handler) HandlerFunc { } } -// H is a shortcut for map[string]interface{} +// H is a shortcut for map[string]any type H map[string]any // MarshalXML allows type H to be used with xml.Marshal. From d1b2408027e3dc61215e0591ef8735107848cbb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 10:04:56 +0800 Subject: [PATCH 062/332] chore(deps): bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#3515) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: github.com/stretchr/testify 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 | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index da978740..d52e73cb 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.17 github.com/pelletier/go-toml/v2 v2.0.6 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 github.com/ugorji/go/codec v1.2.10 golang.org/x/net v0.7.0 google.golang.org/protobuf v1.28.1 diff --git a/go.sum b/go.sum index cab49ab0..bb8225b3 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= From 457fabd7e14f36ca1b5f302f7247efeb4690e49c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 10:05:28 +0800 Subject: [PATCH 063/332] chore(deps): bump github.com/bytedance/sonic from 1.8.1 to 1.8.2 (#3516) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic 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 d52e73cb..484e388b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.8.1 + github.com/bytedance/sonic v1.8.2 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.2 github.com/goccy/go-json v0.10.0 diff --git a/go.sum b/go.sum index bb8225b3..6193e008 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.1 h1:NqAHCaGaTzro0xMmnTCLUyRlbEP6r8MCA1cJUrH3Pu4= -github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.8.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY= +github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From de1c4ec54616e30ecf2a6645e596ad5aacaff2c9 Mon Sep 17 00:00:00 2001 From: lgbgbl <65756378+lgbgbl@users.noreply.github.com> Date: Wed, 1 Mar 2023 13:57:15 +0800 Subject: [PATCH 064/332] refactor: use bytes.ReplaceAll directly (#3455) --- recovery.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 2955c03a..037be51a 100644 --- a/recovery.go +++ b/recovery.go @@ -164,7 +164,7 @@ func function(pc uintptr) []byte { if period := bytes.Index(name, dot); period >= 0 { name = name[period+1:] } - name = bytes.Replace(name, centerDot, dot, -1) + name = bytes.ReplaceAll(name, centerDot, dot) return name } From a889c58de78711cb9b53de6cfcc9272c8518c729 Mon Sep 17 00:00:00 2001 From: hopehook Date: Thu, 2 Mar 2023 08:12:20 +0800 Subject: [PATCH 065/332] Convert strings and slices using the officially recommended way (#3344) * Feat: Convert strings and slices using the officially recommended way. Go official is expected to provide unsafe.{SliceData, Slice, StringData, String} series methods in version 1.20 for conversion of strings and slices. * chore: add reference documentation link to comment of code * chore: update Copyright * chore: remove build tag "+build !go1.20" --- go.mod | 2 +- .../{bytesconv.go => bytesconv_1.19.go} | 2 ++ internal/bytesconv/bytesconv_1.20.go | 23 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) rename internal/bytesconv/{bytesconv.go => bytesconv_1.19.go} (96%) create mode 100644 internal/bytesconv/bytesconv_1.20.go diff --git a/go.mod b/go.mod index 484e388b..0358006d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gin-gonic/gin -go 1.18 +go 1.20 require ( github.com/bytedance/sonic v1.8.2 diff --git a/internal/bytesconv/bytesconv.go b/internal/bytesconv/bytesconv_1.19.go similarity index 96% rename from internal/bytesconv/bytesconv.go rename to internal/bytesconv/bytesconv_1.19.go index 86e4c4d4..669c9c91 100644 --- a/internal/bytesconv/bytesconv.go +++ b/internal/bytesconv/bytesconv_1.19.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build !go1.20 + package bytesconv import ( diff --git a/internal/bytesconv/bytesconv_1.20.go b/internal/bytesconv/bytesconv_1.20.go new file mode 100644 index 00000000..5b6040a6 --- /dev/null +++ b/internal/bytesconv/bytesconv_1.20.go @@ -0,0 +1,23 @@ +// Copyright 2023 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build go1.20 + +package bytesconv + +import ( + "unsafe" +) + +// StringToBytes converts string to byte slice without a memory allocation. +// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077. +func StringToBytes(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) +} + +// BytesToString converts byte slice to string without a memory allocation. +// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077. +func BytesToString(b []byte) string { + return unsafe.String(unsafe.SliceData(b), len(b)) +} From fe989b6a6f8091b2708b39a60b1dd2a2bd3b2812 Mon Sep 17 00:00:00 2001 From: Dylan Maassen van den Brink Date: Wed, 26 Apr 2023 05:18:22 +0200 Subject: [PATCH 066/332] docs: changed documentation link for trusted proxies (#3575) --- gin.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gin.go b/gin.go index f95e5dda..ed8b6dad 100644 --- a/gin.go +++ b/gin.go @@ -515,7 +515,7 @@ func (engine *Engine) RunUnix(file string) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } listener, err := net.Listen("unix", file) @@ -538,7 +538,7 @@ func (engine *Engine) RunFd(fd int) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) @@ -559,7 +559,7 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "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()) From 757a638b7bbdd998375432fb22f693e82d7a7840 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 26 Apr 2023 14:13:56 +0800 Subject: [PATCH 067/332] chore: improve linting, testing, and GitHub Actions setup (#3583) - Update golangci-lint version from `v1.48.0` to `v1.52.2` - Remove Gitter notifications from GitHub Actions workflow - Add gosec linter settings and include specific rules - Exclude revive linter for test files - Remove Gitter badge from README.md - Delete codecov.yml file - Change function parameter name in fs.go - Remove unused parameter in defaultHandleRecovery function Signed-off-by: appleboy --- .github/workflows/gin.yml | 16 +--------------- .golangci.yml | 19 +++++++++++++++++++ README.md | 3 +-- codecov.yml | 5 ----- fs.go | 2 +- recovery.go | 2 +- 6 files changed, 23 insertions(+), 24 deletions(-) delete mode 100644 codecov.yml diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 5c1504a9..b758c7fa 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,7 +24,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3.4.0 with: - version: v1.48.0 + version: v1.52.2 args: --verbose test: needs: lint @@ -75,17 +75,3 @@ jobs: - name: Format if: matrix.go-version == '1.20.x' run: diff -u <(echo -n) <(gofmt -d .) - notification-gitter: - needs: test - runs-on: ubuntu-latest - steps: - - name: Notification failure message - if: failure() - run: | - PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)" - curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" -d level=error https://webhooks.gitter.im/e/7f95bf605c4d356372f4 - - name: Notification success message - if: success() - run: | - PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)" - curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" https://webhooks.gitter.im/e/7f95bf605c4d356372f4 diff --git a/.golangci.yml b/.golangci.yml index c5e1de38..91dae02c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,6 +19,22 @@ linters: - nolintlint - revive - wastedassign + +linters-settings: + gosec: + # To select a subset of rules to run. + # Available rules: https://github.com/securego/gosec#available-rules + # Default: [] - means include all rules + includes: + - G102 + - G106 + - G108 + - G109 + - G111 + - G112 + - G201 + - G203 + issues: exclude-rules: - linters: @@ -37,3 +53,6 @@ issues: - path: _test\.go linters: - gosec # security is not make sense in tests + - linters: + - revive + path: _test\.go diff --git a/README.md b/README.md index cba54ab8..e007bf2f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ [![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) [![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) -[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![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) @@ -176,4 +175,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor Gin is the work of hundreds of contributors. We appreciate your help! -Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. \ No newline at end of file +Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index c9c9a522..00000000 --- a/codecov.yml +++ /dev/null @@ -1,5 +0,0 @@ -coverage: - notify: - gitter: - default: - url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165 diff --git a/fs.go b/fs.go index 64274735..f17d7434 100644 --- a/fs.go +++ b/fs.go @@ -39,7 +39,7 @@ func (fs onlyFilesFS) Open(name string) (http.File, error) { } // Readdir overrides the http.File default implementation. -func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) { +func (f neuteredReaddirFile) Readdir(_ int) ([]os.FileInfo, error) { // this disables directory listing return nil, nil } diff --git a/recovery.go b/recovery.go index 037be51a..515f9d2a 100644 --- a/recovery.go +++ b/recovery.go @@ -103,7 +103,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } } -func defaultHandleRecovery(c *Context, err any) { +func defaultHandleRecovery(c *Context, _ any) { c.AbortWithStatus(http.StatusInternalServerError) } From eac2daac64811197970b5d2f6406e4ae6c31cb5e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 27 Apr 2023 10:16:59 +0800 Subject: [PATCH 068/332] chore: update dependencies for various packages and libraries (#3585) - Update bytedance/sonic to v1.8.8 - Update go-playground/validator/v10 to v10.12.0 - Update goccy/go-json to v0.10.2 - Update mattn/go-isatty to v0.0.18 - Update pelletier/go-toml/v2 to v2.0.7 - Update ugorji/go/codec to v1.2.11 - Update golang.org/x/net to v0.9.0 - Update google.golang.org/protobuf to v1.30.0 - Update klauspost/cpuid/v2 to v2.2.4 - Update leodido/go-urn to v1.2.3 - Update modern-go/concurrent to v0.0.0-20180306012644-bacd9c7ef1dd - Update golang.org/x/arch to v0.3.0 - Update golang.org/x/crypto to v0.8.0 - Update golang.org/x/sys to v0.7.0 - Update golang.org/x/text to v0.9.0 Signed-off-by: appleboy --- go.mod | 31 +++++++++++++------------- go.sum | 68 ++++++++++++++++++++++++++++------------------------------ 2 files changed, 48 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index 0358006d..5fa62004 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,17 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.8.2 + github.com/bytedance/sonic v1.8.8 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.11.2 - github.com/goccy/go-json v0.10.0 + github.com/go-playground/validator/v10 v10.12.0 + github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 - github.com/mattn/go-isatty v0.0.17 - github.com/pelletier/go-toml/v2 v2.0.6 + github.com/mattn/go-isatty v0.0.18 + github.com/pelletier/go-toml/v2 v2.0.7 github.com/stretchr/testify v1.8.2 - github.com/ugorji/go/codec v1.2.10 - golang.org/x/net v0.7.0 - google.golang.org/protobuf v1.28.1 + github.com/ugorji/go/codec v1.2.11 + golang.org/x/net v0.9.0 + google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -22,15 +22,14 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.3 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.8.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect ) diff --git a/go.sum b/go.sum index 6193e008..90704f4e 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,9 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY= -github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.8.8 h1:Kj4AYbZSeENfyXicsYppYKO0K2YWab+i2UTSY7Ukz9Q= +github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -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= @@ -15,39 +14,36 @@ 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.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= -github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= +github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= +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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.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/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA= +github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= +github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -56,26 +52,28 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= -github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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/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 6a0556ed5a67d1d12ae3e7ea2c0121b6c3b894e2 Mon Sep 17 00:00:00 2001 From: ccpro <92025731+CCpro10@users.noreply.github.com> Date: Wed, 10 May 2023 17:19:26 +0800 Subject: [PATCH 069/332] improve render code coverage (#3525) --- render/render_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/render/render_test.go b/render/render_test.go index 19255251..86dc362d 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -15,6 +15,7 @@ import ( "strings" "testing" + "github.com/gin-gonic/gin/internal/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/proto" @@ -136,6 +137,51 @@ func TestRenderJsonpJSON(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) } +type errorWriter struct { + bufString string + *httptest.ResponseRecorder +} + +var _ http.ResponseWriter = (*errorWriter)(nil) + +func (w *errorWriter) Write(buf []byte) (int, error) { + if string(buf) == w.bufString { + return 0, errors.New(`write "` + w.bufString + `" error`) + } + return w.ResponseRecorder.Write(buf) +} + +func TestRenderJsonpJSONError(t *testing.T) { + ew := &errorWriter{ + ResponseRecorder: httptest.NewRecorder(), + } + + jsonpJSON := JsonpJSON{ + Callback: "foo", + Data: map[string]string{ + "foo": "bar", + }, + } + + cb := template.JSEscapeString(jsonpJSON.Callback) + ew.bufString = cb + err := jsonpJSON.Render(ew) // error was returned while writing callback + assert.Equal(t, `write "`+cb+`" error`, err.Error()) + + ew.bufString = `(` + err = jsonpJSON.Render(ew) + assert.Equal(t, `write "`+`(`+`" error`, err.Error()) + + data, _ := json.Marshal(jsonpJSON.Data) // error was returned while writing data + ew.bufString = string(data) + err = jsonpJSON.Render(ew) + assert.Equal(t, `write "`+string(data)+`" error`, err.Error()) + + ew.bufString = `);` + err = jsonpJSON.Render(ew) + assert.Equal(t, `write "`+`);`+`" error`, err.Error()) +} + func TestRenderJsonpJSONError2(t *testing.T) { w := httptest.NewRecorder() data := map[string]any{ From 1ab268989db62a6dd86264cb20e14160e25a6a6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 16:45:22 +0800 Subject: [PATCH 070/332] chore(deps): bump golang.org/x/net from 0.9.0 to 0.10.0 (#3599) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.10.0. - [Commits](https://github.com/golang/net/compare/v0.9.0...v0.10.0) --- updated-dependencies: - dependency-name: golang.org/x/net 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 | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5fa62004..3f3aa0eb 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.7 github.com/stretchr/testify v1.8.2 github.com/ugorji/go/codec v1.2.11 - golang.org/x/net v0.9.0 + golang.org/x/net v0.10.0 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,6 +30,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.8.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect ) diff --git a/go.sum b/go.sum index 90704f4e..84502906 100644 --- a/go.sum +++ b/go.sum @@ -59,12 +59,12 @@ golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 6bdc725c8dfdc203530f1c64c7ea1aaaf4aeaa40 Mon Sep 17 00:00:00 2001 From: Hiroki Nakano <33213547+hirokinakano@users.noreply.github.com> Date: Fri, 26 May 2023 12:45:46 +0900 Subject: [PATCH 071/332] Fix typos in ISSUE_TEMPLATE.md (#3616) --- .github/ISSUE_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6f8288d5..864787ca 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -30,7 +30,7 @@ func main() { ``` -$ curl http://localhost:8201/hello/world +$ curl http://localhost:9000/hello/world Hello world ``` @@ -38,7 +38,7 @@ Hello world ``` -$ curl -i http://localhost:8201/hello/world +$ curl -i http://localhost:9000/hello/world ``` From 20cd6bcfc41148ae4acb01290496f818a61306aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 11:47:59 +0800 Subject: [PATCH 072/332] chore(deps): bump github.com/go-playground/validator/v10 (#3610) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.12.0 to 10.14.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.12.0...v10.14.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 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 | 5 +++-- go.sum | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 3f3aa0eb..7ec3e4fd 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/bytedance/sonic v1.8.8 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.12.0 + github.com/go-playground/validator/v10 v10.14.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.18 @@ -20,10 +20,11 @@ require ( require ( github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // 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.2.4 // indirect - github.com/leodido/go-urn v1.2.3 // indirect + github.com/leodido/go-urn v1.2.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 84502906..36d6b84e 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j 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.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -14,8 +16,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.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= -github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -27,8 +29,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA= -github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= From 9f5ecd4be440f2789db917aa93c1b8afa2276917 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 11:50:31 +0800 Subject: [PATCH 073/332] chore(deps): bump actions/setup-go from 3 to 4 (#3543) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-go 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 b758c7fa..df6e194e 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: '^1.18' - name: Checkout repository @@ -46,7 +46,7 @@ jobs: GOPROXY: https://proxy.golang.org steps: - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index baf02af5..5b205bab 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: 1.20 - From 2d4bbec941551479b1fdf1e54ece03e6e82a7e72 Mon Sep 17 00:00:00 2001 From: Motoyasu Saburi Date: Mon, 29 May 2023 10:57:53 +0900 Subject: [PATCH 074/332] fix lack of escaping of filename in Content-Disposition (#3556) * fix lack of escaping of filename in Content-Disposition * add test for Content-Disposition filename escaping process * fix filename escape bypass problem fix backslashes before backquotes were not properly escaped problem. --- context.go | 8 +++++++- context_test.go | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 5716318e..cb360879 100644 --- a/context.go +++ b/context.go @@ -1052,11 +1052,17 @@ func (c *Context) FileFromFS(filepath string, fs http.FileSystem) { http.FileServer(fs).ServeHTTP(c.Writer, c.Request) } +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +func escapeQuotes(s string) string { + return quoteEscaper.Replace(s) +} + // FileAttachment writes the specified file into the body stream in an efficient way // On the client side, the file will typically be downloaded with the given filename func (c *Context) FileAttachment(filepath, filename string) { if isASCII(filename) { - c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`) + c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+escapeQuotes(filename)+`"`) } else { c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename)) } diff --git a/context_test.go b/context_test.go index 1dec902c..18051235 100644 --- a/context_test.go +++ b/context_test.go @@ -1032,6 +1032,20 @@ func TestContextRenderAttachment(t *testing.T) { assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition")) } +func TestContextRenderAndEscapeAttachment(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + maliciousFilename := "tampering_field.sh\"; \\\"; dummy=.go" + actualEscapedResponseFilename := "tampering_field.sh\\\"; \\\\\\\"; dummy=.go" + + c.Request, _ = http.NewRequest("GET", "/", nil) + c.FileAttachment("./gin.go", maliciousFilename) + + assert.Equal(t, 200, w.Code) + assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", actualEscapedResponseFilename), w.Header().Get("Content-Disposition")) +} + func TestContextRenderUTF8Attachment(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From bb1fc2e0fe97c63dab1527baab88d01183853b8f Mon Sep 17 00:00:00 2001 From: Bence Vidosits <38434845+bvidosits@users.noreply.github.com> Date: Mon, 29 May 2023 01:59:35 +0000 Subject: [PATCH 075/332] fix Request.Context() checks (#3512) Co-authored-by: Bence Vidosits --- context.go | 15 +++++++++++---- context_test.go | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/context.go b/context.go index cb360879..420ff167 100644 --- a/context.go +++ b/context.go @@ -1180,9 +1180,16 @@ func (c *Context) SetAccepted(formats ...string) { /***** GOLANG.ORG/X/NET/CONTEXT *****/ /************************************/ +// hasRequestContext returns whether c.Request has Context and fallback. +func (c *Context) hasRequestContext() bool { + hasFallback := c.engine != nil && c.engine.ContextWithFallback + hasRequestContext := c.Request != nil && c.Request.Context() != nil + return hasFallback && hasRequestContext +} + // Deadline returns that there is no deadline (ok==false) when c.Request has no Context. func (c *Context) Deadline() (deadline time.Time, ok bool) { - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return } return c.Request.Context().Deadline() @@ -1190,7 +1197,7 @@ func (c *Context) Deadline() (deadline time.Time, ok bool) { // Done returns nil (chan which will wait forever) when c.Request has no Context. func (c *Context) Done() <-chan struct{} { - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return nil } return c.Request.Context().Done() @@ -1198,7 +1205,7 @@ func (c *Context) Done() <-chan struct{} { // Err returns nil when c.Request has no Context. func (c *Context) Err() error { - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return nil } return c.Request.Context().Err() @@ -1219,7 +1226,7 @@ func (c *Context) Value(key any) any { return val } } - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return nil } return c.Request.Context().Value(key) diff --git a/context_test.go b/context_test.go index 18051235..70d47583 100644 --- a/context_test.go +++ b/context_test.go @@ -2176,6 +2176,24 @@ func TestRemoteIPFail(t *testing.T) { assert.False(t, trust) } +func TestHasRequestContext(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + assert.False(t, c.hasRequestContext(), "no request, no fallback") + c.engine.ContextWithFallback = true + assert.False(t, c.hasRequestContext(), "no request, has fallback") + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + assert.True(t, c.hasRequestContext(), "has request, has fallback") + c.Request, _ = http.NewRequestWithContext(nil, "", "", nil) //nolint:staticcheck + assert.False(t, c.hasRequestContext(), "has request with nil ctx, has fallback") + c.engine.ContextWithFallback = false + assert.False(t, c.hasRequestContext(), "has request, no fallback") + + c = &Context{} + assert.False(t, c.hasRequestContext(), "no request, no engine") + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + assert.False(t, c.hasRequestContext(), "has request, no engine") +} + func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag From 4ea0e648e38a63d6caff14100f5eab5c50912bcd Mon Sep 17 00:00:00 2001 From: Adriano Sela Aviles Date: Wed, 31 May 2023 19:26:20 -0700 Subject: [PATCH 076/332] Ready release gin 1.9.1 (by: thinkerou) (#3630) * upgrade deps version * update change log * update version * update go mod * fix cr --------- Co-authored-by: thinkerou --- CHANGELOG.md | 21 +++++++++++++++++++++ go.mod | 10 +++++----- go.sum | 26 +++++++++++++++++--------- version.go | 2 +- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf24ec28..79685205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Gin ChangeLog +## Gin v1.9.1 + +### BUG FIXES + +* fix Request.Context() checks [#3512](https://github.com/gin-gonic/gin/pull/3512) + +### SECURITY + +* fix lack of escaping of filename in Content-Disposition [#3556](https://github.com/gin-gonic/gin/pull/3556) + +### ENHANCEMENTS + +* refactor: use bytes.ReplaceAll directly [#3455](https://github.com/gin-gonic/gin/pull/3455) +* convert strings and slices using the officially recommended way [#3344](https://github.com/gin-gonic/gin/pull/3344) +* improve render code coverage [#3525](https://github.com/gin-gonic/gin/pull/3525) + +### DOCS + +* docs: changed documentation link for trusted proxies [#3575](https://github.com/gin-gonic/gin/pull/3575) +* chore: improve linting, testing, and GitHub Actions setup [#3583](https://github.com/gin-gonic/gin/pull/3583) + ## Gin v1.9.0 ### BREAK CHANGES diff --git a/go.mod b/go.mod index 7ec3e4fd..e37698ea 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.8.8 + github.com/bytedance/sonic v1.9.1 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.14.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 - github.com/mattn/go-isatty v0.0.18 - github.com/pelletier/go-toml/v2 v2.0.7 - github.com/stretchr/testify v1.8.2 + github.com/mattn/go-isatty v0.0.19 + github.com/pelletier/go-toml/v2 v2.0.8 + github.com/stretchr/testify v1.8.3 github.com/ugorji/go/codec v1.2.11 golang.org/x/net v0.10.0 google.golang.org/protobuf v1.30.0 @@ -30,7 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.8.0 // indirect + golang.org/x/crypto v0.9.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect ) diff --git a/go.sum b/go.sum index 36d6b84e..0a91a3e6 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.8 h1:Kj4AYbZSeENfyXicsYppYKO0K2YWab+i2UTSY7Ukz9Q= -github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.9.0 h1:iwLYBds8bYtzwOX7kmdYwtS+aY2GgekVoIs2/IxR0tM= +github.com/bytedance/sonic v1.9.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= @@ -26,20 +28,22 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= -github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -50,17 +54,21 @@ 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.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/version.go b/version.go index 390da4f3..85462e55 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.9.0" +const Version = "v1.9.1" From d4a64265f21993368c90602c18e778bf04ef36db Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 5 Jun 2023 09:52:39 +0800 Subject: [PATCH 077/332] chore(CI): update release args (#3595) --- .github/workflows/goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 5b205bab..07a05483 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -29,6 +29,6 @@ jobs: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser version: latest - args: release --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 02e754be9c4889f7ee56db0660cc611eb82b61d6 Mon Sep 17 00:00:00 2001 From: C <6714828+cpcf@users.noreply.github.com> Date: Fri, 4 Aug 2023 03:58:46 +0100 Subject: [PATCH 078/332] Upgrade golang.org/x/net -> v0.13.0 (#3684) Patches https://security.snyk.io/vuln/SNYK-GOLANG-GOLANGORGXNETHTML-5816820 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e37698ea..ded1334a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 github.com/stretchr/testify v1.8.3 github.com/ugorji/go/codec v1.2.11 - golang.org/x/net v0.10.0 + golang.org/x/net v0.13.0 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) From 62b50cfbc0de877207ff74c160a23dff6394f563 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 10 Aug 2023 17:06:34 +0800 Subject: [PATCH 079/332] chore: update dependencies to latest versions (#3694) - Update the version of `golang.org/x/crypto` from `v0.9.0` to `v0.11.0` - Update the version of `golang.org/x/sys` from `v0.8.0` to `v0.10.0` - Update the version of `golang.org/x/text` from `v0.9.0` to `v0.11.0` Signed-off-by: Bo-Yi Wu --- go.mod | 6 +++--- go.sum | 23 ++++++++--------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index ded1334a..e1295485 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.9.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect ) diff --git a/go.sum b/go.sum index 0a91a3e6..147a110a 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,4 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.0 h1:iwLYBds8bYtzwOX7kmdYwtS+aY2GgekVoIs2/IxR0tM= -github.com/bytedance/sonic v1.9.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= @@ -28,7 +26,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -36,7 +33,6 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -61,22 +57,19 @@ 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.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= From d16fdb15fa54ba898bf6f6ed757397282ed9e496 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:23:47 +0800 Subject: [PATCH 080/332] chore(deps): bump golang.org/x/net from 0.13.0 to 0.14.0 (#3688) 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 e1295485..b133475d 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 github.com/stretchr/testify v1.8.3 github.com/ugorji/go/codec v1.2.11 - golang.org/x/net v0.13.0 + golang.org/x/net v0.14.0 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,7 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect ) diff --git a/go.sum b/go.sum index 147a110a..a2d587ab 100644 --- a/go.sum +++ b/go.sum @@ -60,16 +60,16 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= From bb2d8cf486bde2dc69bf05ea917095260ac13723 Mon Sep 17 00:00:00 2001 From: Leonardo de Araujo <46436462+araujo88@users.noreply.github.com> Date: Sat, 12 Aug 2023 11:21:56 -0300 Subject: [PATCH 081/332] test(render): increased unit tests coverage (#3691) --- render/render_test.go | 13 +++++++++++++ response_writer_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/render/render_test.go b/render/render_test.go index 86dc362d..c9db635f 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -578,3 +578,16 @@ func TestRenderReaderNoContentLength(t *testing.T) { assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) } + +func TestRenderWriteError(t *testing.T) { + data := []interface{}{"value1", "value2"} + prefix := "my-prefix:" + r := SecureJSON{Data: data, Prefix: prefix} + ew := &errorWriter{ + bufString: prefix, + ResponseRecorder: httptest.NewRecorder(), + } + err := r.Render(ew) + assert.NotNil(t, err) + assert.Equal(t, `write "my-prefix:" error`, err.Error()) +} diff --git a/response_writer_test.go b/response_writer_test.go index 9fd5e87c..964aa307 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -156,3 +156,33 @@ func TestResponseWriterStatusCode(t *testing.T) { // status must be 200 although we tried to change it assert.Equal(t, http.StatusOK, w.Status()) } + +// mockPusherResponseWriter is an http.ResponseWriter that implements http.Pusher. +type mockPusherResponseWriter struct { + http.ResponseWriter +} + +func (m *mockPusherResponseWriter) Push(target string, opts *http.PushOptions) error { + return nil +} + +// nonPusherResponseWriter is an http.ResponseWriter that does not implement http.Pusher. +type nonPusherResponseWriter struct { + http.ResponseWriter +} + +func TestPusherWithPusher(t *testing.T) { + rw := &mockPusherResponseWriter{} + w := &responseWriter{ResponseWriter: rw} + + pusher := w.Pusher() + assert.NotNil(t, pusher, "Expected pusher to be non-nil") +} + +func TestPusherWithoutPusher(t *testing.T) { + rw := &nonPusherResponseWriter{} + w := &responseWriter{ResponseWriter: rw} + + pusher := w.Pusher() + assert.Nil(t, pusher, "Expected pusher to be nil") +} From e32b5e3a47c1aa238dc312fcddc45182a5b90032 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Aug 2023 16:58:10 +0800 Subject: [PATCH 082/332] chore(deps): bump golangci/golangci-lint-action from 3.4.0 to 3.7.0 (#3703) 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 df6e194e..54d76bb4 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.4.0 + uses: golangci/golangci-lint-action@v3.7.0 with: version: v1.52.2 args: --verbose From dc9cff732e27ce4ac21b25772a83c462a28b8b80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Aug 2023 16:58:36 +0800 Subject: [PATCH 083/332] chore(deps): bump github.com/go-playground/validator/v10 from 10.14.0 to 10.15.1 (#3702) 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 b133475d..5c2ec05d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/bytedance/sonic v1.9.1 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.14.0 + github.com/go-playground/validator/v10 v10.15.1 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.19 diff --git a/go.sum b/go.sum index a2d587ab..b992f594 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,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.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= +github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= From c2ba8f19ec19914b73290c53a32de479cd463555 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 22:18:00 +0800 Subject: [PATCH 084/332] chore(deps): bump actions/checkout from 3 to 4 (#3712) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 2 +- .github/workflows/gin.yml | 4 ++-- .github/workflows/goreleaser.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e27022d1..b717a003 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 54d76bb4..645616bc 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -20,7 +20,7 @@ jobs: with: go-version: '^1.18' - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3.7.0 with: @@ -51,7 +51,7 @@ jobs: go-version: ${{ matrix.go }} - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.ref }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 07a05483..40609266 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - From a481ee2897af1e368de5c919fbeb21b89aa26fc7 Mon Sep 17 00:00:00 2001 From: Viral Parmar Date: Wed, 27 Sep 2023 12:47:11 +0530 Subject: [PATCH 085/332] chore(http): use white color for HTTP 1XX (#3741) --- logger.go | 2 ++ logger_test.go | 1 + 2 files changed, 3 insertions(+) diff --git a/logger.go b/logger.go index cd1e7fa6..1e6cf77a 100644 --- a/logger.go +++ b/logger.go @@ -83,6 +83,8 @@ func (p *LogFormatterParams) StatusCodeColor() string { code := p.StatusCode switch { + case code >= http.StatusContinue && code < http.StatusOK: + return white case code >= http.StatusOK && code < http.StatusMultipleChoices: return green case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: diff --git a/logger_test.go b/logger_test.go index 5f78708f..b93e1e04 100644 --- a/logger_test.go +++ b/logger_test.go @@ -310,6 +310,7 @@ func TestColorForStatus(t *testing.T) { return p.StatusCodeColor() } + assert.Equal(t, white, colorForStatus(http.StatusContinue), "1xx should be white") assert.Equal(t, green, colorForStatus(http.StatusOK), "2xx should be green") assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white") assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow") From bdde009dbbbae890db4e6ffdd252e2b4e63a1b85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 20:23:37 +0800 Subject: [PATCH 086/332] chore(deps): bump golang.org/x/net from 0.14.0 to 0.18.0 (#3774) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.14.0 to 0.18.0. - [Commits](https://github.com/golang/net/compare/v0.14.0...v0.18.0) --- updated-dependencies: - dependency-name: golang.org/x/net 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 5c2ec05d..7ebadde7 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 github.com/stretchr/testify v1.8.3 github.com/ugorji/go/codec v1.2.11 - golang.org/x/net v0.14.0 + golang.org/x/net v0.18.0 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,7 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index b992f594..75848b48 100644 --- a/go.sum +++ b/go.sum @@ -60,16 +60,16 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= From 0aeac86b05cd51c993304e7bcfc2e11cef025c83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 23:45:24 +0800 Subject: [PATCH 087/332] chore(deps): bump github.com/go-playground/validator/v10 from 10.15.1 to 10.16.0 (#3769) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.15.1 to 10.16.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.15.1...v10.16.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 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 7ebadde7..c365e77d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/bytedance/sonic v1.9.1 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.15.1 + github.com/go-playground/validator/v10 v10.16.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.19 diff --git a/go.sum b/go.sum index 75848b48..4bd36dbb 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,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.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= -github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= +github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= From 49f45a542719df661bd71dd48f1595f0bc1ff6f7 Mon Sep 17 00:00:00 2001 From: WeiTheShinobi <43955151+WeiTheShinobi@users.noreply.github.com> Date: Thu, 16 Nov 2023 23:46:11 +0800 Subject: [PATCH 088/332] docs: remove redundant comments (#3765) --- gin.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gin.go b/gin.go index ed8b6dad..5a605cf1 100644 --- a/gin.go +++ b/gin.go @@ -334,7 +334,6 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { } root.addRoute(path, handlers) - // Update maxParams if paramsCount := countParams(path); paramsCount > engine.maxParams { engine.maxParams = paramsCount } From 44d0dd70924dd154e3b98bc340accc53484efa9c Mon Sep 17 00:00:00 2001 From: Omkar P <45419097+omkar-foss@users.noreply.github.com> Date: Thu, 16 Nov 2023 21:16:43 +0530 Subject: [PATCH 089/332] fix: Add pointer support for url query params (#3659) (#3666) The pointer support in url query params (using []*Struct for binding query params) was previously available in Gin, but was removed in commit 0d50ce8 since there wasn't a test case for such a scenario, and so the case block was removed as a redundant one. --- binding/form_mapping.go | 5 +++++ binding/form_mapping_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 540bbbb8..55435b94 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -239,6 +239,11 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Map: return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) + case reflect.Ptr: + if !value.Elem().IsValid() { + value.Set(reflect.New(value.Type().Elem())) + } + return setWithProperType(val, value.Elem(), field) default: return errUnknownType } diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 93d6a92f..acea8f77 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -269,6 +269,39 @@ func TestMappingStructField(t *testing.T) { assert.Equal(t, 9, s.J.I) } +func TestMappingPtrField(t *testing.T) { + type ptrStruct struct { + Key int64 `json:"key"` + } + + type ptrRequest struct { + Items []*ptrStruct `json:"items" form:"items"` + } + + var err error + + // With 0 items. + var req0 ptrRequest + err = mappingByPtr(&req0, formSource{}, "form") + assert.NoError(t, err) + assert.Empty(t, req0.Items) + + // With 1 item. + var req1 ptrRequest + err = mappingByPtr(&req1, formSource{"items": {`{"key": 1}`}}, "form") + assert.NoError(t, err) + assert.Len(t, req1.Items, 1) + assert.EqualValues(t, 1, req1.Items[0].Key) + + // With 2 items. + var req2 ptrRequest + err = mappingByPtr(&req2, formSource{"items": {`{"key": 1}`, `{"key": 2}`}}, "form") + assert.NoError(t, err) + assert.Len(t, req2.Items, 2) + assert.EqualValues(t, 1, req2.Items[0].Key) + assert.EqualValues(t, 2, req2.Items[1].Key) +} + func TestMappingMapField(t *testing.T) { var s struct { M map[string]int From 386d244068db3693f938db4ead6d1f5f85942e3f Mon Sep 17 00:00:00 2001 From: Georgi Dimitrov <82881135+georgijd-form3@users.noreply.github.com> Date: Thu, 7 Dec 2023 00:38:55 +0000 Subject: [PATCH 090/332] fix(tree): correctly expand the capacity of params (#3502) --- routes_test.go | 39 +++++++++++++++++++++++++++++++++++++++ tree.go | 16 +++++++++++++++- tree_test.go | 34 +++++++++++++++++++++++++++++++--- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/routes_test.go b/routes_test.go index 633c0aba..7a51f817 100644 --- a/routes_test.go +++ b/routes_test.go @@ -337,6 +337,45 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) { assert.Equal(t, "/is/super/great", wild) } +// TestRouteParamsNotEmpty tests that context parameters will be set +// even if a route with params/wildcards is registered after the context +// initialisation (which happened in a previous requets). +func TestRouteParamsNotEmpty(t *testing.T) { + name := "" + lastName := "" + wild := "" + router := New() + + w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great") + + assert.Equal(t, http.StatusNotFound, w.Code) + + router.GET("/test/:name/:last_name/*wild", func(c *Context) { + name = c.Params.ByName("name") + lastName = c.Params.ByName("last_name") + var ok bool + wild, ok = c.Params.Get("wild") + + assert.True(t, ok) + assert.Equal(t, name, c.Param("name")) + assert.Equal(t, lastName, c.Param("last_name")) + + assert.Empty(t, c.Param("wtf")) + assert.Empty(t, c.Params.ByName("wtf")) + + wtf, ok := c.Params.Get("wtf") + assert.Empty(t, wtf) + assert.False(t, ok) + }) + + w = PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great") + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "john", name) + assert.Equal(t, "smith", lastName) + assert.Equal(t, "/is/super/great", wild) +} + // TestHandleStaticFile - ensure the static file handles properly func TestRouteStaticFile(t *testing.T) { // SETUP file diff --git a/tree.go b/tree.go index dda8f4f7..7b1e008b 100644 --- a/tree.go +++ b/tree.go @@ -497,7 +497,14 @@ walk: // Outer loop for walking the tree } // Save param value - if params != nil && cap(*params) > 0 { + if params != nil { + // Preallocate capacity if necessary + if cap(*params) < int(globalParamsCount) { + newParams := make(Params, len(*params), globalParamsCount) + copy(newParams, *params) + *params = newParams + } + if value.params == nil { value.params = params } @@ -544,6 +551,13 @@ walk: // Outer loop for walking the tree case catchAll: // Save param value if params != nil { + // Preallocate capacity if necessary + if cap(*params) < int(globalParamsCount) { + newParams := make(Params, len(*params), globalParamsCount) + copy(newParams, *params) + *params = newParams + } + if value.params == nil { value.params = params } diff --git a/tree_test.go b/tree_test.go index 2005738e..aacc914c 100644 --- a/tree_test.go +++ b/tree_test.go @@ -893,9 +893,9 @@ func TestTreeInvalidNodeType(t *testing.T) { func TestTreeInvalidParamsType(t *testing.T) { tree := &node{} - tree.wildChild = true - tree.children = append(tree.children, &node{}) - tree.children[0].nType = 2 + // add a child with wildcard + route := "/:path" + tree.addRoute(route, fakeHandler(route)) // set invalid Params type params := make(Params, 0) @@ -904,6 +904,34 @@ func TestTreeInvalidParamsType(t *testing.T) { tree.getValue("/test", ¶ms, getSkippedNodes(), false) } +func TestTreeExpandParamsCapacity(t *testing.T) { + data := []struct { + path string + }{ + {"/:path"}, + {"/*path"}, + } + + for _, item := range data { + tree := &node{} + tree.addRoute(item.path, fakeHandler(item.path)) + params := make(Params, 0) + + value := tree.getValue("/test", ¶ms, getSkippedNodes(), false) + + if value.params == nil { + t.Errorf("Expected %s params to be set, but they weren't", item.path) + continue + } + + if len(*value.params) != 1 { + t.Errorf("Wrong number of %s params: got %d, want %d", + item.path, len(*value.params), 1) + continue + } + } +} + func TestTreeWildcardConflictEx(t *testing.T) { conflicts := [...]struct { route string From 081b36ebdbf3635143dacd36b92e48529a98e34e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 10:27:25 +0800 Subject: [PATCH 091/332] chore(deps): bump actions/setup-go from 4 to 5 (#3798) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go 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 645616bc..5f8c0c08 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '^1.18' - name: Checkout repository @@ -46,7 +46,7 @@ jobs: GOPROXY: https://proxy.golang.org steps: - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 40609266..5364a913 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: 1.20 - From 811f271a0491b3d74ce3c9948c10f95fe6f64206 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 10:27:57 +0800 Subject: [PATCH 092/332] chore(deps): bump goreleaser/goreleaser-action from 4 to 5 (#3721) Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 4 to 5. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v4...v5) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action 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/goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 5364a913..01803232 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -24,7 +24,7 @@ jobs: go-version: 1.20 - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser From 53fbf4dbfbf465b552057e6f8d199a275151b7a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 10:28:51 +0800 Subject: [PATCH 093/332] chore(deps): bump github.com/pelletier/go-toml/v2 from 2.0.8 to 2.1.1 (#3797) Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.8 to 2.1.1. - [Release notes](https://github.com/pelletier/go-toml/releases) - [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml) - [Commits](https://github.com/pelletier/go-toml/compare/v2.0.8...v2.1.1) --- updated-dependencies: - dependency-name: github.com/pelletier/go-toml/v2 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 | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c365e77d..6b348fe2 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.19 - github.com/pelletier/go-toml/v2 v2.0.8 - github.com/stretchr/testify v1.8.3 + github.com/pelletier/go-toml/v2 v2.1.1 + github.com/stretchr/testify v1.8.4 github.com/ugorji/go/codec v1.2.11 golang.org/x/net v0.18.0 google.golang.org/protobuf v1.30.0 diff --git a/go.sum b/go.sum index 4bd36dbb..39d74fc9 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -51,8 +51,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= From 160c1730efd30046239c802d5b9f895a708c3f4c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 19 Jan 2024 00:35:08 +0800 Subject: [PATCH 094/332] chore: update GitHub Actions configuration (#3792) - Change the cron schedule from `'0 17 * * 5'` to `"0 17 * * 5"` in the file `.github/workflows/codeql.yml` - Change the value of `language` from `['go']` to `["go"]` in the file `.github/workflows/codeql.yml` - Change the value of `go-version` from `'^1.18'` to `"^1.18"` in the file `.github/workflows/gin.yml` - Add `1.21` to the list of `go` versions and change the value of `test-tags` in the file `.github/workflows/gin.yml` - Change the value of `if` condition from `matrix.go-version == '1.20.x'` to `matrix.go-version == '1.21.x'` in the file `.github/workflows/gin.yml` - Change the value of `on` from `'*'` to `"*"` in the file `.github/workflows/goreleaser.yml` - Change the name of the job from `name: Checkout` to `name: Checkout` in the file `.github/workflows/goreleaser.yml` - Change the name of the job from `name: Set up Go` to `name: Set up Go` in the file `.github/workflows/goreleaser.yml` - Change the value of `go-version` from `1.20` to `"^1"` in Signed-off-by: Bo-Yi Wu --- .github/workflows/codeql.yml | 8 ++++---- .github/workflows/gin.yml | 8 ++++---- .github/workflows/goreleaser.yml | 7 +++---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b717a003..858124e1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -7,12 +7,12 @@ name: "CodeQL" on: push: - branches: [ master ] + branches: [master] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [master] schedule: - - cron: '0 17 * * 5' + - cron: "0 17 * * 5" jobs: analyze: @@ -29,7 +29,7 @@ jobs: # Override automatic language detection by changing the below list # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] # TODO: Enable for javascript later - language: [ 'go'] + language: ["go"] steps: - name: Checkout repository diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 5f8c0c08..2149a21e 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -18,7 +18,7 @@ jobs: - name: Setup go uses: actions/setup-go@v5 with: - go-version: '^1.18' + go-version: "^1.18" - name: Checkout repository uses: actions/checkout@v4 - name: Setup golangci-lint @@ -31,8 +31,8 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ['1.18', '1.19', '1.20'] - test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] + go: ["1.18", "1.19", "1.20", "1.21"] + test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json"] include: - os: ubuntu-latest go-build: ~/.cache/go-build @@ -73,5 +73,5 @@ jobs: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - name: Format - if: matrix.go-version == '1.20.x' + if: matrix.go-version == '1.21.x' run: diff -u <(echo -n) <(gofmt -d .) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 01803232..cbd5d418 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -3,7 +3,7 @@ name: Goreleaser on: push: tags: - - '*' + - "*" permissions: contents: write @@ -12,8 +12,7 @@ jobs: goreleaser: runs-on: ubuntu-latest steps: - - - name: Checkout + - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 @@ -21,7 +20,7 @@ jobs: name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.20 + go-version: "^1" - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 From 857db39f82fb82456af2906ccea972ae1d65ff57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 08:18:57 +0800 Subject: [PATCH 095/332] chore(deps): bump github/codeql-action from 2 to 3 (#3806) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [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/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action 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/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 858124e1..9a4c40d7 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@v2 + uses: github/codeql-action/init@v3 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@v2 + uses: github/codeql-action/analyze@v3 From 4a40f8f1a49b9086b461d97e167c3b9628d8b923 Mon Sep 17 00:00:00 2001 From: caption <101684156+chncaption@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:00:17 +0800 Subject: [PATCH 096/332] fix(sec): upgrade golang.org/x/crypto to 0.17.0 (#3832) --- go.mod | 4 ++-- go.sum | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 6b348fe2..7f560453 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.15.0 // indirect - golang.org/x/sys v0.14.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 39d74fc9..f4bf8ff0 100644 --- a/go.sum +++ b/go.sum @@ -62,12 +62,15 @@ golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 8ab47c694ea93fdb442b617961ce9b3171151749 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 1 Feb 2024 11:03:26 +0800 Subject: [PATCH 097/332] ci(lint): update tooling and workflows for consistency (#3834) * chore: update tooling and workflows for consistency - Update the version of a tool in the GitHub workflow from `v1.52.2` to `v1.55.2` Signed-off-by: Bo-Yi Wu * chore: refactor linter configuration in CI - Remove the `depguard` linter from the `.golangci.yml` configuration Signed-off-by: Bo-Yi Wu * ci: refine CI workflow and test configurations - Disable caching in the GitHub Actions workflow for `gin.yml` Signed-off-by: Bo-Yi Wu * refactor: refactor return logic in tree operations - Modify multiple return statements in `tree.go` to return a specific value instead of nothing Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 3 ++- .golangci.yml | 1 - go.sum | 5 +---- tree.go | 22 +++++++++++----------- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 2149a21e..4ec95435 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,7 +24,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3.7.0 with: - version: v1.52.2 + version: v1.55.2 args: --verbose test: needs: lint @@ -49,6 +49,7 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} + cache: false - name: Checkout Code uses: actions/checkout@v4 diff --git a/.golangci.yml b/.golangci.yml index 91dae02c..4a72f734 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,7 +3,6 @@ run: linters: enable: - asciicheck - - depguard - dogsled - durationcheck - errcheck diff --git a/go.sum b/go.sum index f4bf8ff0..8c4ef3fb 100644 --- a/go.sum +++ b/go.sum @@ -60,16 +60,13 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/tree.go b/tree.go index 7b1e008b..45646461 100644 --- a/tree.go +++ b/tree.go @@ -478,7 +478,7 @@ walk: // Outer loop for walking the tree // We can recommend to redirect to the same URL without a // trailing slash if a leaf exists for that path. value.tsr = path == "/" && n.handlers != nil - return + return value } // Handle wildcard child, which is always at the end of the array @@ -533,12 +533,12 @@ walk: // Outer loop for walking the tree // ... but we can't value.tsr = len(path) == end+1 - return + return value } if value.handlers = n.handlers; value.handlers != nil { value.fullPath = n.fullPath - return + return value } if len(n.children) == 1 { // No handle found. Check if a handle for this path + a @@ -546,7 +546,7 @@ walk: // Outer loop for walking the tree n = n.children[0] value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/") } - return + return value case catchAll: // Save param value @@ -578,7 +578,7 @@ walk: // Outer loop for walking the tree value.handlers = n.handlers value.fullPath = n.fullPath - return + return value default: panic("invalid node type") @@ -609,7 +609,7 @@ walk: // Outer loop for walking the tree // Check if this node has a handle registered. if value.handlers = n.handlers; value.handlers != nil { value.fullPath = n.fullPath - return + return value } // If there is no handle for this route, but this route has a @@ -617,12 +617,12 @@ walk: // Outer loop for walking the tree // additional trailing slash if path == "/" && n.wildChild && n.nType != root { value.tsr = true - return + return value } if path == "/" && n.nType == static { value.tsr = true - return + return value } // No handle found. Check if a handle for this path + a @@ -632,11 +632,11 @@ walk: // Outer loop for walking the tree n = n.children[i] value.tsr = (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) - return + return value } } - return + return value } // Nothing found. We can recommend to redirect to the same URL with an @@ -662,7 +662,7 @@ walk: // Outer loop for walking the tree } } - return + return value } } From a64286a7760be2031209686ce4d36e99d42dd419 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 1 Feb 2024 12:17:36 +0800 Subject: [PATCH 098/332] chore(deps): update dependencies to latest versions (#3835) * chore: update dependencies to latest versions - Update `sonic` library from `v1.9.1` to `v1.10.2` - Update `validator` library from `v10.16.0` to `v10.17.0` - Update `go-isatty` library from `v0.0.19` to `v0.0.20` - Update `go/codec`, `x/net`, and `protobuf` libraries to newer versions - Update `base64x` to a newer commit and add `iasm` library as an indirect dependency - Update `mimetype`, `cpuid`, `go-urn`, `x/arch`, `x/crypto`, and `x/sys` libraries to newer versions Signed-off-by: Bo-Yi Wu * ci: refactor CI workflows and improve robustness - Update GitHub Actions cache from v3 to v4 in the workflow configuration Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 2 +- go.mod | 27 +++++++++-------- go.sum | 64 ++++++++++++++++++++------------------- 3 files changed, 48 insertions(+), 45 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 4ec95435..b36e1010 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -56,7 +56,7 @@ jobs: with: ref: ${{ github.ref }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: | ${{ matrix.go-build }} diff --git a/go.mod b/go.mod index 7f560453..0b60c5d7 100644 --- a/go.mod +++ b/go.mod @@ -3,34 +3,35 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.9.1 + github.com/bytedance/sonic v1.10.2 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.16.0 + github.com/go-playground/validator/v10 v10.17.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 - github.com/mattn/go-isatty v0.0.19 + github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.1.1 github.com/stretchr/testify v1.8.4 - github.com/ugorji/go/codec v1.2.11 - golang.org/x/net v0.18.0 - google.golang.org/protobuf v1.30.0 + github.com/ugorji/go/codec v1.2.12 + golang.org/x/net v0.20.0 + google.golang.org/protobuf v1.32.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // 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.2.4 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/leodido/go-urn v1.3.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/arch v0.7.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 8c4ef3fb..e360d9d2 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,19 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= +github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= 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.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -16,23 +21,22 @@ 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.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= +github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.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/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.3.0 h1:jX8FDLfW4ThVXctBNZ+3cIWnCSnrACDV73r76dy0aQQ= +github.com/leodido/go-urn v1.3.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -50,34 +54,32 @@ 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.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.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= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From c6ae2e69666a2b36203b29650ee75d172c725c66 Mon Sep 17 00:00:00 2001 From: Ghobad Date: Fri, 2 Feb 2024 05:22:26 +0330 Subject: [PATCH 099/332] feat(logger): ability to skip logs based on user-defined logic (#3593) * log skipper * do not call time.now() if logging should be skipped * do not ignore skip func delay in latency calculation * write docs * write test --- docs/doc.md | 38 +++++++++++++++++++++++++++++++ logger.go | 61 +++++++++++++++++++++++++++++--------------------- logger_test.go | 20 +++++++++++++++++ 3 files changed, 93 insertions(+), 26 deletions(-) diff --git a/docs/doc.md b/docs/doc.md index e48c2ba1..520d105c 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -508,6 +508,44 @@ Sample Output ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767ยตs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " ``` +### Skip logging + +```go +func main() { + router := gin.New() + + // skip logging for desired paths by setting SkipPaths in LoggerConfig + loggerConfig := gin.LoggerConfig{SkipPaths: []string{"/metrics"}} + + // skip logging based on your logic by setting Skip func in LoggerConfig + loggerConfig.Skip = func(c *gin.Context) bool { + // as an example skip non server side errors + return c.Writer.Status() < http.StatusInternalServerError + } + + engine.Use(gin.LoggerWithConfig(loggerConfig)) + router.Use(gin.Recovery()) + + // skipped + router.GET("/metrics", func(c *gin.Context) { + c.Status(http.StatusNotImplemented) + }) + + // skipped + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + // not skipped + router.GET("/data", func(c *gin.Context) { + c.Status(http.StatusNotImplemented) + }) + + router.Run(":8080") +} + +``` + ### Controlling Log output coloring By default, logs output on console should be colorized depending on the detected TTY. diff --git a/logger.go b/logger.go index 1e6cf77a..db2c6832 100644 --- a/logger.go +++ b/logger.go @@ -47,8 +47,15 @@ type LoggerConfig struct { // SkipPaths is an url path array which logs are not written. // Optional. SkipPaths []string + + // Skip is a Skipper that indicates which logs should not be written. + // Optional. + Skip Skipper } +// Skipper is a function to skip logs based on provided Context +type Skipper func(c *Context) bool + // LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter type LogFormatter func(params LogFormatterParams) string @@ -241,32 +248,34 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { // Process request c.Next() - // Log only when path is not being skipped - if _, ok := skip[path]; !ok { - param := LogFormatterParams{ - Request: c.Request, - isTerm: isTerm, - Keys: c.Keys, - } - - // Stop timer - param.TimeStamp = time.Now() - param.Latency = param.TimeStamp.Sub(start) - - param.ClientIP = c.ClientIP() - param.Method = c.Request.Method - param.StatusCode = c.Writer.Status() - param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() - - param.BodySize = c.Writer.Size() - - if raw != "" { - path = path + "?" + raw - } - - param.Path = path - - fmt.Fprint(out, formatter(param)) + // Log only when it is not being skipped + if _, ok := skip[path]; ok || (conf.Skip != nil && conf.Skip(c)) { + return } + + param := LogFormatterParams{ + Request: c.Request, + isTerm: isTerm, + Keys: c.Keys, + } + + // Stop timer + param.TimeStamp = time.Now() + param.Latency = param.TimeStamp.Sub(start) + + param.ClientIP = c.ClientIP() + param.Method = c.Request.Method + param.StatusCode = c.Writer.Status() + param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() + + param.BodySize = c.Writer.Size() + + if raw != "" { + path = path + "?" + raw + } + + param.Path = path + + fmt.Fprint(out, formatter(param)) } } diff --git a/logger_test.go b/logger_test.go index b93e1e04..6c1814dc 100644 --- a/logger_test.go +++ b/logger_test.go @@ -415,6 +415,26 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { assert.Contains(t, buffer.String(), "") } +func TestLoggerWithConfigSkipper(t *testing.T) { + buffer := new(strings.Builder) + router := New() + router.Use(LoggerWithConfig(LoggerConfig{ + Output: buffer, + Skip: func(c *Context) bool { + return c.Writer.Status() == http.StatusNoContent + }, + })) + router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) }) + router.GET("/skipped", func(c *Context) { c.Status(http.StatusNoContent) }) + + PerformRequest(router, "GET", "/logged") + assert.Contains(t, buffer.String(), "200") + + buffer.Reset() + PerformRequest(router, "GET", "/skipped") + assert.Contains(t, buffer.String(), "") +} + func TestDisableConsoleColor(t *testing.T) { New() assert.Equal(t, autoColor, consoleColorMode) From 9f598a31aafb92d675f38f1c8371e4ac76f858bf Mon Sep 17 00:00:00 2001 From: Prakhar Gurunani Date: Sun, 4 Feb 2024 18:44:29 +0530 Subject: [PATCH 100/332] fix(router): catch-all conflicting wildcard (#3812) * fix: catch-all conflicting wildcard * add: test cases * chore: update GitHub Actions configuration (#3792) - Change the cron schedule from `'0 17 * * 5'` to `"0 17 * * 5"` in the file `.github/workflows/codeql.yml` - Change the value of `language` from `['go']` to `["go"]` in the file `.github/workflows/codeql.yml` - Change the value of `go-version` from `'^1.18'` to `"^1.18"` in the file `.github/workflows/gin.yml` - Add `1.21` to the list of `go` versions and change the value of `test-tags` in the file `.github/workflows/gin.yml` - Change the value of `if` condition from `matrix.go-version == '1.20.x'` to `matrix.go-version == '1.21.x'` in the file `.github/workflows/gin.yml` - Change the value of `on` from `'*'` to `"*"` in the file `.github/workflows/goreleaser.yml` - Change the name of the job from `name: Checkout` to `name: Checkout` in the file `.github/workflows/goreleaser.yml` - Change the name of the job from `name: Set up Go` to `name: Set up Go` in the file `.github/workflows/goreleaser.yml` - Change the value of `go-version` from `1.20` to `"^1"` in Signed-off-by: Bo-Yi Wu * chore(deps): bump github/codeql-action from 2 to 3 (#3806) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [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/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action 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> * fix(sec): upgrade golang.org/x/crypto to 0.17.0 (#3832) * ci(lint): update tooling and workflows for consistency (#3834) * chore: update tooling and workflows for consistency - Update the version of a tool in the GitHub workflow from `v1.52.2` to `v1.55.2` Signed-off-by: Bo-Yi Wu * chore: refactor linter configuration in CI - Remove the `depguard` linter from the `.golangci.yml` configuration Signed-off-by: Bo-Yi Wu * ci: refine CI workflow and test configurations - Disable caching in the GitHub Actions workflow for `gin.yml` Signed-off-by: Bo-Yi Wu * refactor: refactor return logic in tree operations - Modify multiple return statements in `tree.go` to return a specific value instead of nothing Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu * chore(deps): update dependencies to latest versions (#3835) * chore: update dependencies to latest versions - Update `sonic` library from `v1.9.1` to `v1.10.2` - Update `validator` library from `v10.16.0` to `v10.17.0` - Update `go-isatty` library from `v0.0.19` to `v0.0.20` - Update `go/codec`, `x/net`, and `protobuf` libraries to newer versions - Update `base64x` to a newer commit and add `iasm` library as an indirect dependency - Update `mimetype`, `cpuid`, `go-urn`, `x/arch`, `x/crypto`, and `x/sys` libraries to newer versions Signed-off-by: Bo-Yi Wu * ci: refactor CI workflows and improve robustness - Update GitHub Actions cache from v3 to v4 in the workflow configuration Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu * wip: fix tests * wip: fix tests --------- Signed-off-by: Bo-Yi Wu Signed-off-by: dependabot[bot] Co-authored-by: Bo-Yi Wu Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: caption <101684156+chncaption@users.noreply.github.com> --- tree.go | 5 ++++- tree_test.go | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tree.go b/tree.go index 45646461..878023d1 100644 --- a/tree.go +++ b/tree.go @@ -351,7 +351,10 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) } if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { - pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0] + pathSeg := "" + if len(n.children) != 0 { + pathSeg = strings.SplitN(n.children[0].path, "/", 2)[0] + } panic("catch-all wildcard '" + path + "' in new path '" + fullPath + "' conflicts with existing path segment '" + pathSeg + diff --git a/tree_test.go b/tree_test.go index aacc914c..c9b03130 100644 --- a/tree_test.go +++ b/tree_test.go @@ -417,6 +417,8 @@ func TestTreeWildcardConflict(t *testing.T) { {"/user_:name", false}, {"/id:id", false}, {"/id/:id", false}, + {"/static/*file", false}, + {"/static/", true}, } testRoutes(t, routes) } From 3dc1cd6572b4e3a0cd170a15debe546c2c72294f Mon Sep 17 00:00:00 2001 From: clearcode <34591322+clearcodecn@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:46:35 +0800 Subject: [PATCH 101/332] fix(binding): binding error while not upload file (#3819) (#3820) Co-authored-by: zhangmj --- binding/form_mapping.go | 3 +++ binding/form_mapping_test.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 55435b94..77a1bde6 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,6 +7,7 @@ package binding import ( "errors" "fmt" + "mime/multipart" "reflect" "strconv" "strings" @@ -235,6 +236,8 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel switch value.Interface().(type) { case time.Time: return setTimeField(val, field, value) + case multipart.FileHeader: + return nil } return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Map: diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index acea8f77..16527eb9 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -5,6 +5,7 @@ package binding import ( + "mime/multipart" "reflect" "testing" "time" @@ -43,6 +44,7 @@ func TestMappingBaseTypes(t *testing.T) { {"zero value", struct{ F uint }{}, "", uint(0)}, {"zero value", struct{ F bool }{}, "", false}, {"zero value", struct{ F float32 }{}, "", float32(0)}, + {"file value", struct{ F *multipart.FileHeader }{}, "", &multipart.FileHeader{}}, } { tp := reflect.TypeOf(tt.value) testName := tt.name + ":" + tp.Field(0).Type.String() From e957d1abf13846e458956d8c97e7b7c76c7ee9a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 10:39:24 +0800 Subject: [PATCH 102/332] chore(deps): bump codecov/codecov-action from 3 to 4 (#3838) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [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/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index b36e1010..75e6d05d 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -69,7 +69,7 @@ jobs: run: make test - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} From 86ff4a64c7efe1a1c875529835eeef9e15de1e86 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Tue, 6 Feb 2024 04:08:56 +0100 Subject: [PATCH 103/332] fix(header): Allow header according to RFC 7231 (HTTP 405) (#3759) Co-authored-by: Helios --- gin.go | 14 +++++++++++--- routes_test.go | 12 ++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/gin.go b/gin.go index 5a605cf1..b6ac5353 100644 --- a/gin.go +++ b/gin.go @@ -633,17 +633,25 @@ func (engine *Engine) handleHTTPRequest(c *Context) { } if engine.HandleMethodNotAllowed { + // According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response + // containing a list of the target resource's currently supported methods. + allowed := make([]string, 0, len(t)-1) for _, tree := range engine.trees { if tree.method == httpMethod { continue } if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil { - c.handlers = engine.allNoMethod - serveError(c, http.StatusMethodNotAllowed, default405Body) - return + allowed = append(allowed, tree.method) } } + if len(allowed) > 0 { + c.handlers = engine.allNoMethod + c.writermem.Header().Set("Allow", strings.Join(allowed, ", ")) + serveError(c, http.StatusMethodNotAllowed, default405Body) + return + } } + c.handlers = engine.allNoRoute serveError(c, http.StatusNotFound, default404Body) } diff --git a/routes_test.go b/routes_test.go index 7a51f817..a0ff695f 100644 --- a/routes_test.go +++ b/routes_test.go @@ -514,6 +514,18 @@ func TestRouteNotAllowedEnabled2(t *testing.T) { assert.Equal(t, http.StatusMethodNotAllowed, w.Code) } +func TestRouteNotAllowedEnabled3(t *testing.T) { + router := New() + router.HandleMethodNotAllowed = true + router.GET("/path", func(c *Context) {}) + router.POST("/path", func(c *Context) {}) + w := PerformRequest(router, http.MethodPut, "/path") + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) + allowed := w.Header().Get("Allow") + assert.Contains(t, allowed, "GET") + assert.Contains(t, allowed, "POST") +} + func TestRouteNotAllowedDisabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = false From 82bcd6d39bfe9c22032764ff3b0b6f8ef1673e49 Mon Sep 17 00:00:00 2001 From: Alonso Villegas Date: Wed, 7 Feb 2024 06:44:11 -0500 Subject: [PATCH 104/332] fix(binding): dereference pointer to struct (#3199) --- binding/default_validator.go | 5 ++++- binding/validate_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/binding/default_validator.go b/binding/default_validator.go index e216b854..ac43d7cc 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -54,7 +54,10 @@ func (v *defaultValidator) ValidateStruct(obj any) error { value := reflect.ValueOf(obj) switch value.Kind() { case reflect.Ptr: - return v.ValidateStruct(value.Elem().Interface()) + if value.Elem().Kind() != reflect.Struct { + return v.ValidateStruct(value.Elem().Interface()) + } + return v.validateStruct(obj) case reflect.Struct: return v.validateStruct(obj) case reflect.Slice, reflect.Array: diff --git a/binding/validate_test.go b/binding/validate_test.go index 801bd9b7..1fc15ff0 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -192,6 +192,30 @@ func TestValidatePrimitives(t *testing.T) { assert.Equal(t, "value", str) } +type structModifyValidation struct { + Integer int +} + +func toZero(sl validator.StructLevel) { + var s *structModifyValidation = sl.Top().Interface().(*structModifyValidation) + s.Integer = 0 +} + +func TestValidateAndModifyStruct(t *testing.T) { + // This validates that pointers to structs are passed to the validator + // giving us the ability to modify the struct being validated. + engine, ok := Validator.Engine().(*validator.Validate) + assert.True(t, ok) + + engine.RegisterStructValidation(toZero, structModifyValidation{}) + + s := structModifyValidation{Integer: 1} + errs := validate(&s) + + assert.Nil(t, errs) + assert.Equal(t, s, structModifyValidation{Integer: 0}) +} + // structCustomValidation is a helper struct we use to check that // custom validation can be registered on it. // The `notone` binding directive is for custom validation and registered later. From bb3519d26f52835cf00e5e430b52651a9c378c97 Mon Sep 17 00:00:00 2001 From: Andy Brody Date: Wed, 7 Feb 2024 07:18:53 -0500 Subject: [PATCH 105/332] chore(IP): add TrustedPlatform constant for Fly.io. (#3839) Also add some more detail to the docs for how to use TrustedPlatform. https://fly.io/docs/reference/runtime-environment/#fly-client-ip --- context_test.go | 7 +++++++ docs/doc.md | 12 +++++++++--- gin.go | 2 ++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/context_test.go b/context_test.go index 70d47583..88165c07 100644 --- a/context_test.go +++ b/context_test.go @@ -1569,6 +1569,12 @@ func TestContextClientIP(t *testing.T) { c.Request.Header.Del("CF-Connecting-IP") assert.Equal(t, "40.40.40.40", c.ClientIP()) + c.engine.TrustedPlatform = PlatformFlyIO + assert.Equal(t, "70.70.70.70", c.ClientIP()) + + c.Request.Header.Del("Fly-Client-IP") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + c.engine.TrustedPlatform = "" // no port @@ -1581,6 +1587,7 @@ func resetContextForClientIPTests(c *Context) { c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30") c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50") c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60") + c.Request.Header.Set("Fly-Client-IP", "70.70.70.70") c.Request.RemoteAddr = " 40.40.40.40:42123 " c.engine.TrustedPlatform = "" c.engine.trustedCIDRs = defaultTrustedCIDRs diff --git a/docs/doc.md b/docs/doc.md index 520d105c..df006e87 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -2214,10 +2214,16 @@ import ( func main() { router := gin.Default() // Use predefined header gin.PlatformXXX + // Google App Engine router.TrustedPlatform = gin.PlatformGoogleAppEngine - // Or set your own trusted request header for another trusted proxy service - // Don't set it to any suspect request header, it's unsafe - router.TrustedPlatform = "X-CDN-IP" + // Cloudflare + router.TrustedPlatform = gin.PlatformCloudflare + // Fly.io + router.TrustedPlatform = gin.PlatformFlyIO + // Or, you can set your own trusted request header. But be sure your CDN + // prevents users from passing this header! For example, if your CDN puts + // the client IP in X-CDN-Client-IP: + router.TrustedPlatform = "X-CDN-Client-IP" router.GET("/", func(c *gin.Context) { // If you set TrustedPlatform, ClientIP() will resolve the diff --git a/gin.go b/gin.go index b6ac5353..24a9864a 100644 --- a/gin.go +++ b/gin.go @@ -77,6 +77,8 @@ const ( // PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining // the client's IP PlatformCloudflare = "CF-Connecting-IP" + // PlatformFlyIO when running on Fly.io. Trust Fly-Client-IP for determining the client's IP + PlatformFlyIO = "Fly-Client-IP" ) // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. From 000fdb3ac95c7c318440afbd98eaf60f7430a1db Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 18 Feb 2024 17:32:06 +0800 Subject: [PATCH 106/332] ci(testing): add go1.22 version (#3842) * chore: update gin version and remove unnecessary steps in GitHub workflows - Update the version of gin to v1.56.1 in the `.github/workflows/gin.yml` file - Add go version 1.22 to the list of supported versions in the `.github/workflows/gin.yml` file - Remove the unnecessary step "Set up Go" in the `.github/workflows/goreleaser.yml` file - Update the step name "Run GoReleaser" in the `.github/workflows/goreleaser.yml` file Signed-off-by: appleboy * ci: update dependencies and CI configurations - Update conditional Go version check in GitHub Actions workflow from `1.21.x` to `1.22.x` Signed-off-by: Bo-Yi Wu * ci: improve CI Robustness and Test Reliability - Add `-race` flag to the test-tags list in GitHub Actions workflow configuration Signed-off-by: Bo-Yi Wu --------- Signed-off-by: appleboy Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 9 +++++---- .github/workflows/goreleaser.yml | 6 ++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 75e6d05d..9ab00ae3 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,15 +24,16 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3.7.0 with: - version: v1.55.2 + version: v1.56.1 args: --verbose test: needs: lint strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.18", "1.19", "1.20", "1.21"] - test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json"] + go: ["1.18", "1.19", "1.20", "1.21", "1.22"] + test-tags: + ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: - os: ubuntu-latest go-build: ~/.cache/go-build @@ -74,5 +75,5 @@ jobs: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - name: Format - if: matrix.go-version == '1.21.x' + if: matrix.go-version == '1.22.x' run: diff -u <(echo -n) <(gofmt -d .) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index cbd5d418..8ae11823 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -16,13 +16,11 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - - name: Set up Go + - name: Set up Go uses: actions/setup-go@v5 with: go-version: "^1" - - - name: Run GoReleaser + - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 with: # either 'goreleaser' (default) or 'goreleaser-pro' From ecdbbbe9483dd12222f2085f717a2c7cb5ac55fe Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 19 Feb 2024 10:34:48 +0800 Subject: [PATCH 107/332] chore: refactor CI and update dependencies (#3848) - Update GitHub Actions workflow to use a unified step for checking out the repository and setting up Go with dynamic versioning - Upgrade golangci-lint-action version from v3.7.0 to v4 and bump the lint version from v1.56.1 to v1.56.2 - Update dependencies in go.mod: sonic from v1.10.2 to v1.11.0, validator from v10.17.0 to v10.18.0, x/net from v0.20.0 to v0.21.0, go-urn from v1.3.0 to v1.4.0, x/crypto from v0.18.0 to v0.19.0, and x/sys from v0.16.0 to v0.17.0 Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 15 +++++++++------ go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 9ab00ae3..3fe007f1 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -15,16 +15,19 @@ jobs: lint: runs-on: ubuntu-latest steps: - - name: Setup go + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "^1.18" - - name: Checkout repository - uses: actions/checkout@v4 + go-version-file: "go.mod" + check-latest: true - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.7.0 + uses: golangci/golangci-lint-action@v4 with: - version: v1.56.1 + version: v1.56.2 args: --verbose test: needs: lint diff --git a/go.mod b/go.mod index 0b60c5d7..fbbce7c0 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,16 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.10.2 + github.com/bytedance/sonic v1.11.0 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.17.0 + github.com/go-playground/validator/v10 v10.18.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.1.1 github.com/stretchr/testify v1.8.4 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.20.0 + golang.org/x/net v0.21.0 google.golang.org/protobuf v1.32.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -25,13 +25,13 @@ 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.2.6 // indirect - github.com/leodido/go-urn v1.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index e360d9d2..ce6c7fe7 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= -github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.0 h1:FwNNv6Vu4z2Onf1++LNzxB/QhitD8wuTdpZzMTGITWo= +github.com/bytedance/sonic v1.11.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -21,8 +21,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.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= -github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= +github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= @@ -33,8 +33,8 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/leodido/go-urn v1.3.0 h1:jX8FDLfW4ThVXctBNZ+3cIWnCSnrACDV73r76dy0aQQ= -github.com/leodido/go-urn v1.3.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +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= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -63,14 +63,14 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 739d2d9c80e0298dafb5df1c30bae35d63935d6c Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Tue, 5 Mar 2024 14:07:11 +0800 Subject: [PATCH 108/332] chore(perf): Optimize the Copy method of the Context struct (#3859) * Optimize the Copy method of the Context struct: using 'make' to initialize the map('cp.Keys') with a length of 'c.Keys'; avoiding repeatedly assiging the 'params' to 'context'. * Using temporary variables to save c.Keys and c.Params to prevent them from changing during the copying process. --------- Co-authored-by: huangzw --- context.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 420ff167..126d35db 100644 --- a/context.go +++ b/context.go @@ -113,20 +113,24 @@ func (c *Context) Copy() *Context { cp := Context{ writermem: c.writermem, Request: c.Request, - Params: c.Params, engine: c.engine, } + cp.writermem.ResponseWriter = nil cp.Writer = &cp.writermem cp.index = abortIndex cp.handlers = nil - cp.Keys = map[string]any{} - for k, v := range c.Keys { + + cKeys := c.Keys + cp.Keys = make(map[string]any, len(cKeys)) + for k, v := range cKeys { cp.Keys[k] = v } - paramCopy := make([]Param, len(cp.Params)) - copy(paramCopy, cp.Params) - cp.Params = paramCopy + + cParams := c.Params + cp.Params = make([]Param, len(cParams)) + copy(cp.Params, cParams) + return &cp } From ae15646aba14cd8245fbebd263cc7740c6789ef3 Mon Sep 17 00:00:00 2001 From: guangwu Date: Tue, 5 Mar 2024 14:36:02 +0800 Subject: [PATCH 109/332] test(http): use constant instead of numeric literal (#3863) Signed-off-by: guoguangwu --- routes_test.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/routes_test.go b/routes_test.go index a0ff695f..185abd99 100644 --- a/routes_test.go +++ b/routes_test.go @@ -180,58 +180,58 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"}) assert.Equal(t, "/api/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api#?"}) assert.Equal(t, "/api/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api"}) assert.Equal(t, "/api/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../api"}) assert.Equal(t, "/api/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../api"}) assert.Equal(t, "/api/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../"}) assert.Equal(t, "//path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../../"}) assert.Equal(t, "/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../gin-gonic.com"}) assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../gin-gonic.com"}) assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"}) assert.Equal(t, "https/gin-goniccom/https/gin-goniccom/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#api"}) assert.Equal(t, "api/api/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/#?a=1"}) assert.Equal(t, "/nor-mal/a1/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/%2e%2e/"}) assert.Equal(t, "/nor-mal/2e2e/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) router.RedirectTrailingSlash = false @@ -619,11 +619,11 @@ func TestRouterNotFound(t *testing.T) { router = New() router.NoRoute(func(c *Context) { if c.Request.RequestURI == "/login" { - c.String(200, "login") + c.String(http.StatusOK, "login") } }) router.GET("/logout", func(c *Context) { - c.String(200, "logout") + c.String(http.StatusOK, "logout") }) w = PerformRequest(router, http.MethodGet, "/login") assert.Equal(t, "login", w.Body.String()) @@ -635,7 +635,7 @@ func TestRouterStaticFSNotFound(t *testing.T) { router := New() router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) router.NoRoute(func(c *Context) { - c.String(404, "non existent") + c.String(http.StatusNotFound, "non existent") }) w := PerformRequest(router, http.MethodGet, "/nonexistent") @@ -718,12 +718,12 @@ func TestRouteRawPathNoUnescape(t *testing.T) { func TestRouteServeErrorWithWriteHeader(t *testing.T) { route := New() route.Use(func(c *Context) { - c.Status(421) + c.Status(http.StatusMisdirectedRequest) c.Next() }) w := PerformRequest(route, http.MethodGet, "/NotFound") - assert.Equal(t, 421, w.Code) + assert.Equal(t, http.StatusMisdirectedRequest, w.Code) assert.Equal(t, 0, w.Body.Len()) } From 9c61295efeea99f6c9d1722294f1bf61d8e464d6 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Tue, 5 Mar 2024 14:54:35 +0100 Subject: [PATCH 110/332] chore(header): Add support for RFC 9512: application/yaml (#3851) * fix(binding): support application/yaml RFC 9512 defines application/yaml as the official YAML MIME type. application/x-yaml is deprecated. In this commit, we ensure it is recognized correctly in Content-Type. * fix(render): use application/yaml when rendering YAML As per RFC 9512, application/x-yaml is now deprecated and applications should use application/yaml. This commit fix the Content-Type header when rendering YAML. --- binding/binding.go | 3 ++- binding/binding_nomsgpack.go | 3 ++- binding/binding_test.go | 2 ++ context_test.go | 6 +++--- render/render_test.go | 4 ++-- render/yaml.go | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 40948529..036b329b 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -21,6 +21,7 @@ const ( MIMEMSGPACK = "application/x-msgpack" MIMEMSGPACK2 = "application/msgpack" MIMEYAML = "application/x-yaml" + MIMEYAML2 = "application/yaml" MIMETOML = "application/toml" ) @@ -102,7 +103,7 @@ func Default(method, contentType string) Binding { return ProtoBuf case MIMEMSGPACK, MIMEMSGPACK2: return MsgPack - case MIMEYAML: + case MIMEYAML, MIMEYAML2: return YAML case MIMETOML: return TOML diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index 93ad8ba3..552a86b2 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -19,6 +19,7 @@ const ( MIMEMultipartPOSTForm = "multipart/form-data" MIMEPROTOBUF = "application/x-protobuf" MIMEYAML = "application/x-yaml" + MIMEYAML2 = "application/yaml" MIMETOML = "application/toml" ) @@ -96,7 +97,7 @@ func Default(method, contentType string) Binding { return XML case MIMEPROTOBUF: return ProtoBuf - case MIMEYAML: + case MIMEYAML, MIMEYAML2: return YAML case MIMEMultipartPOSTForm: return FormMultipart diff --git a/binding/binding_test.go b/binding/binding_test.go index 9af4f88a..feb8eed5 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -164,6 +164,8 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, YAML, Default("POST", MIMEYAML)) assert.Equal(t, YAML, Default("PUT", MIMEYAML)) + assert.Equal(t, YAML, Default("POST", MIMEYAML2)) + assert.Equal(t, YAML, Default("PUT", MIMEYAML2)) assert.Equal(t, TOML, Default("POST", MIMETOML)) assert.Equal(t, TOML, Default("PUT", MIMETOML)) diff --git a/context_test.go b/context_test.go index 88165c07..d060ccf0 100644 --- a/context_test.go +++ b/context_test.go @@ -1060,7 +1060,7 @@ func TestContextRenderUTF8Attachment(t *testing.T) { } // TestContextRenderYAML tests that the response is serialized as YAML -// and Content-Type is set to application/x-yaml +// and Content-Type is set to application/yaml func TestContextRenderYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -1069,7 +1069,7 @@ func TestContextRenderYAML(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo: bar\n", w.Body.String()) - assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextRenderTOML tests that the response is serialized as TOML @@ -1217,7 +1217,7 @@ func TestContextNegotiationWithYAML(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "foo: bar\n", w.Body.String()) - assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationWithTOML(t *testing.T) { diff --git a/render/render_test.go b/render/render_test.go index c9db635f..145f1316 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -280,12 +280,12 @@ b: d: [3, 4] ` (YAML{data}).WriteContentType(w) - assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) err := (YAML{data}).Render(w) assert.NoError(t, err) assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String()) - assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) } type fail struct{} diff --git a/render/yaml.go b/render/yaml.go index fc927c1f..042bb821 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -15,7 +15,7 @@ type YAML struct { Data any } -var yamlContentType = []string{"application/x-yaml; charset=utf-8"} +var yamlContentType = []string{"application/yaml; charset=utf-8"} // Render (YAML) marshals the given interface object and writes data with custom ContentType. func (r YAML) Render(w http.ResponseWriter) error { From f75144a356e57c95bd21a048f0a40492dcdb33c5 Mon Sep 17 00:00:00 2001 From: guangwu Date: Tue, 5 Mar 2024 21:55:25 +0800 Subject: [PATCH 111/332] docs: fix typo in comment (#3868) Signed-off-by: guoguangwu --- routes_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes_test.go b/routes_test.go index 185abd99..73f393e7 100644 --- a/routes_test.go +++ b/routes_test.go @@ -339,7 +339,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) { // TestRouteParamsNotEmpty tests that context parameters will be set // even if a route with params/wildcards is registered after the context -// initialisation (which happened in a previous requets). +// initialisation (which happened in a previous requests). func TestRouteParamsNotEmpty(t *testing.T) { name := "" lastName := "" From 09f8224593e31edf3c58ab3f13bc31ef53473733 Mon Sep 17 00:00:00 2001 From: Karthik Reddy Puli <47525322+KarthikReddyPuli@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:16:53 +0530 Subject: [PATCH 112/332] fix(route): Add fullPath in context copy (#3784) * fix: Add fullPath in context copy * Update context.go --------- Co-authored-by: Bo-Yi Wu --- context.go | 1 + context_test.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/context.go b/context.go index 126d35db..609827dd 100644 --- a/context.go +++ b/context.go @@ -120,6 +120,7 @@ func (c *Context) Copy() *Context { cp.Writer = &cp.writermem cp.index = abortIndex cp.handlers = nil + cp.fullPath = c.fullPath cKeys := c.Keys cp.Keys = make(map[string]any, len(cKeys)) diff --git a/context_test.go b/context_test.go index d060ccf0..ac766e2b 100644 --- a/context_test.go +++ b/context_test.go @@ -324,6 +324,7 @@ func TestContextCopy(t *testing.T) { c.handlers = HandlersChain{func(c *Context) {}} c.Params = Params{Param{Key: "foo", Value: "bar"}} c.Set("foo", "bar") + c.fullPath = "/hola" cp := c.Copy() assert.Nil(t, cp.handlers) @@ -336,6 +337,7 @@ func TestContextCopy(t *testing.T) { assert.Equal(t, cp.Params, c.Params) cp.Set("foo", "notBar") assert.False(t, cp.Keys["foo"] == c.Keys["foo"]) + assert.Equal(t, cp.fullPath, c.fullPath) } func TestContextHandlerName(t *testing.T) { From 3ea8bd99fbb4e499d70a0c8e1ce2ce4b7c6348b6 Mon Sep 17 00:00:00 2001 From: jessetang <1430482733@qq.com> Date: Wed, 6 Mar 2024 22:27:21 +0800 Subject: [PATCH 113/332] chore(refactor): modify interface check way (#3855) Signed-off-by: demoManito <1430482733@qq.com> --- render/render.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/render/render.go b/render/render.go index 7955000c..4bdcfa23 100644 --- a/render/render.go +++ b/render/render.go @@ -15,22 +15,22 @@ type Render interface { } var ( - _ Render = JSON{} - _ Render = IndentedJSON{} - _ Render = SecureJSON{} - _ Render = JsonpJSON{} - _ Render = XML{} - _ Render = String{} - _ Render = Redirect{} - _ Render = Data{} - _ Render = HTML{} - _ HTMLRender = HTMLDebug{} - _ HTMLRender = HTMLProduction{} - _ Render = YAML{} - _ Render = Reader{} - _ Render = AsciiJSON{} - _ Render = ProtoBuf{} - _ Render = TOML{} + _ Render = (*JSON)(nil) + _ Render = (*IndentedJSON)(nil) + _ Render = (*SecureJSON)(nil) + _ Render = (*JsonpJSON)(nil) + _ Render = (*XML)(nil) + _ Render = (*String)(nil) + _ Render = (*Redirect)(nil) + _ Render = (*Data)(nil) + _ Render = (*HTML)(nil) + _ HTMLRender = (*HTMLDebug)(nil) + _ HTMLRender = (*HTMLProduction)(nil) + _ Render = (*YAML)(nil) + _ Render = (*Reader)(nil) + _ Render = (*AsciiJSON)(nil) + _ Render = (*ProtoBuf)(nil) + _ Render = (*TOML)(nil) ) func writeContentType(w http.ResponseWriter, value []string) { From 97eab7d09a8b048cab4a3d8ebd6c0ea78284c716 Mon Sep 17 00:00:00 2001 From: jessetang <1430482733@qq.com> Date: Fri, 8 Mar 2024 15:56:00 +0800 Subject: [PATCH 114/332] test(git): gitignore add develop tools (#3370) --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index bdd50c95..1ea0e2b9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ count.out test profile.out tmp.out + +# Develop tools +.idea/ +.vscode/ From 5f458dd1a6d631f324e4af9a4f5429ffdf199342 Mon Sep 17 00:00:00 2001 From: Endless Paradox <129645532+EndlessParadox1@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:22:58 +0800 Subject: [PATCH 115/332] feat(auth): add proxy-server authentication (#3877) --- auth.go | 21 +++++++++++++++++++++ auth_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/auth.go b/auth.go index 2503c515..cc6c5a7e 100644 --- a/auth.go +++ b/auth.go @@ -15,6 +15,7 @@ import ( // AuthUserKey is the cookie name for user credential in basic auth. const AuthUserKey = "user" +const AuthProxyUserKey = "proxy_user" // Accounts defines a key/value for user/pass list of authorized logins. type Accounts map[string]string @@ -89,3 +90,23 @@ func authorizationHeader(user, password string) string { base := user + ":" + password return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base)) } + +func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc { + if realm == "" { + realm = "Proxy Authorization Required" + } + realm = "Basic realm=" + strconv.Quote(realm) + pairs := processAccounts(accounts) + return func(c *Context) { + proxyUser, found := pairs.searchCredential(c.requestHeader("Proxy-Authorization")) + if !found { + // Credentials doesn't match, we return 407 and abort handlers chain. + c.Header("Proxy-Authenticate", realm) + c.AbortWithStatus(http.StatusProxyAuthRequired) + return + } + // The proxy_user credentials was found, set proxy_user's id to key AuthProxyUserKey in this context, the proxy_user's id can be read later using + // c.MustGet(gin.AuthProxyUserKey). + c.Set(AuthProxyUserKey, proxyUser) + } +} diff --git a/auth_test.go b/auth_test.go index 42b6f8fd..f7175929 100644 --- a/auth_test.go +++ b/auth_test.go @@ -137,3 +137,40 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.Header().Get("WWW-Authenticate")) } + +func TestBasicAuthForProxySucceed(t *testing.T) { + accounts := Accounts{"admin": "password"} + router := New() + router.Use(BasicAuthForProxy(accounts, "")) + router.Any("/*proxyPath", func(c *Context) { + c.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string)) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("Proxy-Authorization", authorizationHeader("admin", "password")) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "admin", w.Body.String()) +} + +func TestBasicAuthForProxy407(t *testing.T) { + called := false + accounts := Accounts{"foo": "bar"} + router := New() + router.Use(BasicAuthForProxy(accounts, "")) + router.Any("/*proxyPath", func(c *Context) { + called = true + c.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string)) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) + router.ServeHTTP(w, req) + + assert.False(t, called) + assert.Equal(t, http.StatusProxyAuthRequired, w.Code) + assert.Equal(t, "Basic realm=\"Proxy Authorization Required\"", w.Header().Get("Proxy-Authenticate")) +} From 646312aef6a34095476ac846b0920db5fb24b2ea Mon Sep 17 00:00:00 2001 From: qingmu Date: Mon, 11 Mar 2024 22:24:36 +0800 Subject: [PATCH 116/332] fix: protect Context.Keys map when call Copy method (#3873) --- context.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/context.go b/context.go index 609827dd..59abee17 100644 --- a/context.go +++ b/context.go @@ -124,9 +124,11 @@ func (c *Context) Copy() *Context { cKeys := c.Keys cp.Keys = make(map[string]any, len(cKeys)) + c.mu.RLock() for k, v := range cKeys { cp.Keys[k] = v } + c.mu.RUnlock() cParams := c.Params cp.Params = make([]Param, len(cParams)) From 83fc7673f9797b4c7d8d1c41b94e9922303e6275 Mon Sep 17 00:00:00 2001 From: TotomiEcio <63461656+TotomiEcio@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:25:28 -0300 Subject: [PATCH 117/332] docs: fix typo in function documentation (#3872) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 59abee17..a17a58e9 100644 --- a/context.go +++ b/context.go @@ -393,7 +393,7 @@ func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) // // router.GET("/user/:id", func(c *gin.Context) { // // a GET request to /user/john -// id := c.Param("id") // id == "/john" +// id := c.Param("id") // id == "john" // // a GET request to /user/john/ // id := c.Param("id") // id == "/john/" // }) From ac5e84d93ce34359bfd2f346cb2971ea754d83e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Mon, 11 Mar 2024 22:35:30 +0800 Subject: [PATCH 118/332] feat(engine): Added `OptionFunc` and `With` (#3572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Added `OptionFunc` and `With` * fix: `With(opts...)` must be after `New` * feat: improve New with * fix: test * optimize code * optimize nolint * optimize code Signed-off-by: Flcใ‚› --------- Signed-off-by: Flcใ‚› --- context_test.go | 8 ++++---- gin.go | 20 ++++++++++++++++---- gin_test.go | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/context_test.go b/context_test.go index ac766e2b..33cc43f9 100644 --- a/context_test.go +++ b/context_test.go @@ -1000,7 +1000,7 @@ func TestContextRenderFile(t *testing.T) { c.File("./gin.go") assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' assert.NotEqual(t, "", w.Header().Get("Content-Type")) @@ -1014,7 +1014,7 @@ func TestContextRenderFileFromFS(t *testing.T) { c.FileFromFS("./gin.go", Dir(".", false)) assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' assert.NotEqual(t, "", w.Header().Get("Content-Type")) @@ -1030,7 +1030,7 @@ func TestContextRenderAttachment(t *testing.T) { c.FileAttachment("./gin.go", newFilename) assert.Equal(t, 200, w.Code) - assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition")) } @@ -1057,7 +1057,7 @@ func TestContextRenderUTF8Attachment(t *testing.T) { c.FileAttachment("./gin.go", newFilename) assert.Equal(t, 200, w.Code) - assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") assert.Equal(t, `attachment; filename*=UTF-8''`+url.QueryEscape(newFilename), w.Header().Get("Content-Disposition")) } diff --git a/gin.go b/gin.go index 24a9864a..1633fe13 100644 --- a/gin.go +++ b/gin.go @@ -47,6 +47,9 @@ var regRemoveRepeatedChar = regexp.MustCompile("/{2,}") // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) +// OptionFunc defines the function to change the default configuration +type OptionFunc func(*Engine) + // HandlersChain defines a HandlerFunc slice. type HandlersChain []HandlerFunc @@ -182,7 +185,7 @@ var _ IRouter = (*Engine)(nil) // - ForwardedByClientIP: true // - UseRawPath: false // - UnescapePathValues: true -func New() *Engine { +func New(opts ...OptionFunc) *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ @@ -211,15 +214,15 @@ func New() *Engine { engine.pool.New = func() any { return engine.allocateContext(engine.maxParams) } - return engine + return engine.With(opts...) } // Default returns an Engine instance with the Logger and Recovery middleware already attached. -func Default() *Engine { +func Default(opts ...OptionFunc) *Engine { debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) - return engine + return engine.With(opts...) } func (engine *Engine) Handler() http.Handler { @@ -313,6 +316,15 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { return engine } +// With returns a new Engine instance with the provided options. +func (engine *Engine) With(opts ...OptionFunc) *Engine { + for _, opt := range opts { + opt(engine) + } + + return engine +} + func (engine *Engine) rebuild404Handlers() { engine.allNoRoute = engine.combineHandlers(engine.noRoute) } diff --git a/gin_test.go b/gin_test.go index 8825ac7e..4550a7e5 100644 --- a/gin_test.go +++ b/gin_test.go @@ -696,3 +696,37 @@ func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) func handlerTest1(c *Context) {} func handlerTest2(c *Context) {} + +func TestNewOptionFunc(t *testing.T) { + var fc = func(e *Engine) { + e.GET("/test1", handlerTest1) + e.GET("/test2", handlerTest2) + + e.Use(func(c *Context) { + c.Next() + }) + } + + r := New(fc) + + routes := r.Routes() + assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"}) +} + +func TestWithOptionFunc(t *testing.T) { + r := New() + + r.With(func(e *Engine) { + e.GET("/test1", handlerTest1) + e.GET("/test2", handlerTest2) + + e.Use(func(c *Context) { + c.Next() + }) + }) + + routes := r.Routes() + assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"}) +} From 1b3c0859693fc85290c01ba098b1440d4776549f Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 11 Mar 2024 10:41:07 -0400 Subject: [PATCH 119/332] chore(debug): add ability to override the debugPrint statement (#2337) * feat: add ability to override the debugPrint statement This allows users to use a single logger within their application for all printing, regardless of level. * chore: make the code more readable, as per review comment * fix: use tab instead of space for indentation --- debug.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/debug.go b/debug.go index 1fc0cafe..1761fe32 100644 --- a/debug.go +++ b/debug.go @@ -23,6 +23,9 @@ func IsDebugging() bool { // DebugPrintRouteFunc indicates debug log output format. var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int) +// DebugPrintFunc indicates debug log output format. +var DebugPrintFunc func(format string, values ...interface{}) + func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { if IsDebugging() { nuHandlers := len(handlers) @@ -48,12 +51,19 @@ func debugPrintLoadTemplate(tmpl *template.Template) { } func debugPrint(format string, values ...any) { - if IsDebugging() { - if !strings.HasSuffix(format, "\n") { - format += "\n" - } - fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...) + if !IsDebugging() { + return } + + if DebugPrintFunc != nil { + DebugPrintFunc(format, values...) + return + } + + if !strings.HasSuffix(format, "\n") { + format += "\n" + } + fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...) } func getMinVer(v string) (uint64, error) { From ab8042e9e5370bbe0e93ea5adc6e74ae4c5df95e Mon Sep 17 00:00:00 2001 From: Noah Yao Date: Mon, 11 Mar 2024 22:44:28 +0800 Subject: [PATCH 120/332] chore(request): check reader if it's nil before reading (#3419) --- context.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/context.go b/context.go index a17a58e9..0c73a49f 100644 --- a/context.go +++ b/context.go @@ -880,6 +880,9 @@ func (c *Context) GetHeader(key string) string { // GetRawData returns stream data. func (c *Context) GetRawData() ([]byte, error) { + if c.Request.Body == nil { + return nil, errors.New("cannot read nil body") + } return io.ReadAll(c.Request.Body) } From f70dd00b00bc0a46cb18b55bfe1f918d5d29b511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Tue, 12 Mar 2024 13:49:23 +0800 Subject: [PATCH 121/332] fix(engine): fix unit test (#3878) * fix(engine): fix unit test * fix(engine): fix unit test --- context_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context_test.go b/context_test.go index 33cc43f9..089047c2 100644 --- a/context_test.go +++ b/context_test.go @@ -1044,7 +1044,7 @@ func TestContextRenderAndEscapeAttachment(t *testing.T) { c.FileAttachment("./gin.go", maliciousFilename) assert.Equal(t, 200, w.Code) - assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", actualEscapedResponseFilename), w.Header().Get("Content-Disposition")) } From 861ffb9181dc811dc5d76fc450b36d3e68850b95 Mon Sep 17 00:00:00 2001 From: Endless Paradox <129645532+EndlessParadox1@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:51:04 +0800 Subject: [PATCH 122/332] docs(middleware): comments to function `BasicAuthForProxy` (#3881) --- auth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/auth.go b/auth.go index cc6c5a7e..2ed33ac0 100644 --- a/auth.go +++ b/auth.go @@ -91,6 +91,7 @@ func authorizationHeader(user, password string) string { return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base)) } +// BasicAuthForProxy returns a Basic HTTP Proxy-Authorization middleware. func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc { if realm == "" { realm = "Proxy Authorization Required" From 990c44aebf20f0796d99051e53d6ee75b7ed52fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Tue, 12 Mar 2024 13:55:52 +0800 Subject: [PATCH 123/332] docs(context): Added deprecation comments to BindWith (#3880) --- deprecated.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deprecated.go b/deprecated.go index 9521308f..b4c6cd88 100644 --- a/deprecated.go +++ b/deprecated.go @@ -12,6 +12,8 @@ import ( // BindWith binds the passed struct pointer using the specified binding engine. // See the binding package. +// +// Deprecated: Use MustBindWith or ShouldBindWith. func (c *Context) BindWith(obj any, b binding.Binding) error { log.Println(`BindWith(\"any, binding.Binding\") error is going to be deprecated, please check issue #662 and either use MustBindWith() if you From ee70b30a97205ac1f32889f41d8a494b3b2c81a5 Mon Sep 17 00:00:00 2001 From: Endless Paradox <129645532+EndlessParadox1@users.noreply.github.com> Date: Wed, 13 Mar 2024 23:22:05 +0800 Subject: [PATCH 124/332] docs: Add document to constant `AuthProxyUserKey` and `BasicAuthForProxy`. (#3887) --- auth.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/auth.go b/auth.go index 2ed33ac0..5d3222d5 100644 --- a/auth.go +++ b/auth.go @@ -15,6 +15,8 @@ import ( // AuthUserKey is the cookie name for user credential in basic auth. const AuthUserKey = "user" + +// AuthProxyUserKey is the cookie name for proxy_user credential in basic auth for proxy. const AuthProxyUserKey = "proxy_user" // Accounts defines a key/value for user/pass list of authorized logins. @@ -92,6 +94,7 @@ func authorizationHeader(user, password string) string { } // BasicAuthForProxy returns a Basic HTTP Proxy-Authorization middleware. +// If the realm is empty, "Proxy Authorization Required" will be used by default. func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc { if realm == "" { realm = "Proxy Authorization Required" From fd60a24ab76c3c92955ba253c1f7eda9e4981c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Thu, 14 Mar 2024 11:22:54 +0800 Subject: [PATCH 125/332] test(path): Optimize unit test execution results (#3883) * test(path): Add a GC recycle validation * test(path): Optimize unit test execution results * test(path): Optimize unit test execution results --- path_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/path_test.go b/path_test.go index caefd63a..864302f4 100644 --- a/path_test.go +++ b/path_test.go @@ -6,6 +6,7 @@ package gin import ( + "runtime" "strings" "testing" @@ -80,6 +81,10 @@ func TestPathCleanMallocs(t *testing.T) { t.Skip("skipping malloc count in short mode") } + if runtime.GOMAXPROCS(0) > 1 { + t.Skip("skipping malloc count; GOMAXPROCS>1") + } + for _, test := range cleanTests { allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) }) assert.EqualValues(t, allocs, 0) From 0d9dbbb44551a872d30fd89d4d55ba0515d646fd Mon Sep 17 00:00:00 2001 From: Guilherme Aleixo Date: Mon, 18 Mar 2024 11:14:06 -0300 Subject: [PATCH 126/332] chore(security): upgrade Protobuf for CVE-2024-24786 (#3893) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fbbce7c0..11ce23e8 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.21.0 - google.golang.org/protobuf v1.32.0 + google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index ce6c7fe7..49eae343 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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 2b1da2b0b38dfc5d5841266037c0c8b249eca1dd Mon Sep 17 00:00:00 2001 From: "Farmer.Chillax" <48387781+FarmerChillax@users.noreply.github.com> Date: Thu, 21 Mar 2024 21:08:41 +0800 Subject: [PATCH 127/332] fix(context): make context Value method adhere to Go standards (#3897) --- context.go | 6 +++++- context_test.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 0c73a49f..3a9608d5 100644 --- a/context.go +++ b/context.go @@ -43,6 +43,10 @@ const BodyBytesKey = "_gin-gonic/gin/bodybyteskey" // ContextKey is the key that a Context returns itself for. const ContextKey = "_gin-gonic/gin/contextkey" +type ContextKeyType int + +const ContextRequestKey ContextKeyType = 0 + // abortIndex represents a typical value used in abort functions. const abortIndex int8 = math.MaxInt8 >> 1 @@ -1225,7 +1229,7 @@ func (c *Context) Err() error { // if no value is associated with key. Successive calls to Value with // the same key returns the same result. func (c *Context) Value(key any) any { - if key == 0 { + if key == ContextRequestKey { return c.Request } if key == ContextKey { diff --git a/context_test.go b/context_test.go index 089047c2..9c1717ed 100644 --- a/context_test.go +++ b/context_test.go @@ -1985,7 +1985,7 @@ func TestContextGolangContext(t *testing.T) { ti, ok := c.Deadline() assert.Equal(t, ti, time.Time{}) assert.False(t, ok) - assert.Equal(t, c.Value(0), c.Request) + assert.Equal(t, c.Value(ContextRequestKey), c.Request) assert.Equal(t, c.Value(ContextKey), c) assert.Nil(t, c.Value("foo")) From 78f4687875d72d10392f8a77008cbefdec4c0aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Thu, 21 Mar 2024 21:13:56 +0800 Subject: [PATCH 128/332] build(codecov): Added a codecov configuration (#3891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Flcใ‚› --- codecov.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..47782e50 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +coverage: + require_ci_to_pass: true + + status: + project: + default: + target: 99% + threshold: 99% + + patch: + default: + target: 99% + threshold: 95% \ No newline at end of file From 8790d08909fc4d193c6c787c9c72f3089168f411 Mon Sep 17 00:00:00 2001 From: illiafox <61962654+illiafox@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:28:42 +0200 Subject: [PATCH 129/332] fix(uri): query binding bug (#3236) * fix query mapping * query binding test --- binding/query.go | 4 ++-- binding/query_test.go | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 binding/query_test.go diff --git a/binding/query.go b/binding/query.go index c958b88b..baa4aead 100644 --- a/binding/query.go +++ b/binding/query.go @@ -12,9 +12,9 @@ func (queryBinding) Name() string { return "query" } -func (queryBinding) Bind(req *http.Request, obj any) error { +func (q queryBinding) Bind(req *http.Request, obj any) error { values := req.URL.Query() - if err := mapForm(obj, values); err != nil { + if err := mapFormByTag(obj, values, q.Name()); err != nil { return err } return validate(obj) diff --git a/binding/query_test.go b/binding/query_test.go new file mode 100644 index 00000000..72102040 --- /dev/null +++ b/binding/query_test.go @@ -0,0 +1,23 @@ +package binding + +import ( + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryBinding(t *testing.T) { + var s struct { + Foo string `query:"foo"` + } + + request := &http.Request{URL: &url.URL{RawQuery: "foo=BAR"}} + + err := queryBinding{}.Bind(request, &s) + require.NoError(t, err) + + assert.Equal(t, "BAR", s.Foo) +} From d4e413648824333726ef65de5defc457e9dbf095 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 22 Mar 2024 10:01:27 +0800 Subject: [PATCH 130/332] Revert "fix(uri): query binding bug (#3236)" (#3899) This reverts commit 8790d08909fc4d193c6c787c9c72f3089168f411. --- binding/query.go | 4 ++-- binding/query_test.go | 23 ----------------------- 2 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 binding/query_test.go diff --git a/binding/query.go b/binding/query.go index baa4aead..c958b88b 100644 --- a/binding/query.go +++ b/binding/query.go @@ -12,9 +12,9 @@ func (queryBinding) Name() string { return "query" } -func (q queryBinding) Bind(req *http.Request, obj any) error { +func (queryBinding) Bind(req *http.Request, obj any) error { values := req.URL.Query() - if err := mapFormByTag(obj, values, q.Name()); err != nil { + if err := mapForm(obj, values); err != nil { return err } return validate(obj) diff --git a/binding/query_test.go b/binding/query_test.go deleted file mode 100644 index 72102040..00000000 --- a/binding/query_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package binding - -import ( - "net/http" - "net/url" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestQueryBinding(t *testing.T) { - var s struct { - Foo string `query:"foo"` - } - - request := &http.Request{URL: &url.URL{RawQuery: "foo=BAR"}} - - err := queryBinding{}.Bind(request, &s) - require.NoError(t, err) - - assert.Equal(t, "BAR", s.Foo) -} From fd1faaded01aef14a3955ec076f1cbeb9cb87775 Mon Sep 17 00:00:00 2001 From: ssfyn Date: Sat, 23 Mar 2024 08:50:25 +0800 Subject: [PATCH 131/332] feat(binding): support override default binding implement (#3514) --- binding/binding.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 036b329b..94723879 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -73,18 +73,18 @@ var Validator StructValidator = &defaultValidator{} // These implement the Binding interface and can be used to bind the data // present in the request to struct instances. var ( - JSON = jsonBinding{} - XML = xmlBinding{} - Form = formBinding{} - Query = queryBinding{} - FormPost = formPostBinding{} - FormMultipart = formMultipartBinding{} - ProtoBuf = protobufBinding{} - MsgPack = msgpackBinding{} - YAML = yamlBinding{} - Uri = uriBinding{} - Header = headerBinding{} - TOML = tomlBinding{} + JSON BindingBody = jsonBinding{} + XML BindingBody = xmlBinding{} + Form Binding = formBinding{} + Query Binding = queryBinding{} + FormPost Binding = formPostBinding{} + FormMultipart Binding = formMultipartBinding{} + ProtoBuf BindingBody = protobufBinding{} + MsgPack BindingBody = msgpackBinding{} + YAML BindingBody = yamlBinding{} + Uri BindingUri = uriBinding{} + Header Binding = headerBinding{} + TOML BindingBody = tomlBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method From 7a865dcf1dbe6ec52e074b1ddce830d278eb72cf Mon Sep 17 00:00:00 2001 From: RedCrazyGhost <49381700+RedCrazyGhost@users.noreply.github.com> Date: Sat, 23 Mar 2024 22:09:02 +0800 Subject: [PATCH 132/332] feat(bind): ShouldBindBodyWith shortcut and change doc (#3871) * feat: ShouldBindBodyWith shortcut and change doc * fix: yaml can parse json test case * style: fix new test case in context_test.go * chore: modify the code style to specify binding type * chroe: gofmt modifies the code format --- context.go | 20 ++++ context_test.go | 257 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/doc.md | 9 +- 3 files changed, 284 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 3a9608d5..afc3c353 100644 --- a/context.go +++ b/context.go @@ -774,6 +774,26 @@ func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error return bb.BindBody(body, obj) } +// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). +func (c *Context) ShouldBindBodyWithJSON(obj any) error { + return c.ShouldBindBodyWith(obj, binding.JSON) +} + +// ShouldBindBodyWithXML is a shortcut for c.ShouldBindBodyWith(obj, binding.XML). +func (c *Context) ShouldBindBodyWithXML(obj any) error { + return c.ShouldBindBodyWith(obj, binding.XML) +} + +// ShouldBindBodyWithYAML is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML). +func (c *Context) ShouldBindBodyWithYAML(obj any) error { + return c.ShouldBindBodyWith(obj, binding.YAML) +} + +// ShouldBindBodyWithTOML is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML). +func (c *Context) ShouldBindBodyWithTOML(obj any) error { + return c.ShouldBindBodyWith(obj, binding.TOML) +} + // ClientIP implements one best effort algorithm to return the real client IP. // It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. // If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). diff --git a/context_test.go b/context_test.go index 9c1717ed..e9bbae52 100644 --- a/context_test.go +++ b/context_test.go @@ -1977,6 +1977,263 @@ func TestContextShouldBindBodyWith(t *testing.T) { } } +func TestContextShouldBindBodyWithJSON(t *testing.T) { + for _, tt := range []struct { + name string + bindingBody binding.BindingBody + body string + }{ + { + name: " JSON & JSON-BODY ", + bindingBody: binding.JSON, + body: `{"foo":"FOO"}`, + }, + { + name: " JSON & XML-BODY ", + bindingBody: binding.XML, + body: ` + +FOO +`, + }, + { + name: " JSON & YAML-BODY ", + bindingBody: binding.YAML, + body: `foo: FOO`, + }, + { + name: " JSON & TOM-BODY ", + bindingBody: binding.TOML, + body: `foo=FOO`, + }, + } { + t.Logf("testing: %s", tt.name) + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + + type typeJSON struct { + Foo string `json:"foo" binding:"required"` + } + objJSON := typeJSON{} + + if tt.bindingBody == binding.JSON { + assert.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{"FOO"}, objJSON) + } + + if tt.bindingBody == binding.XML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + + if tt.bindingBody == binding.YAML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + + if tt.bindingBody == binding.TOML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + } +} + +func TestContextShouldBindBodyWithXML(t *testing.T) { + for _, tt := range []struct { + name string + bindingBody binding.BindingBody + body string + }{ + { + name: " XML & JSON-BODY ", + bindingBody: binding.JSON, + body: `{"foo":"FOO"}`, + }, + { + name: " XML & XML-BODY ", + bindingBody: binding.XML, + body: ` + +FOO +`, + }, + { + name: " XML & YAML-BODY ", + bindingBody: binding.YAML, + body: `foo: FOO`, + }, + { + name: " XML & TOM-BODY ", + bindingBody: binding.TOML, + body: `foo=FOO`, + }, + } { + t.Logf("testing: %s", tt.name) + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + + type typeXML struct { + Foo string `xml:"foo" binding:"required"` + } + objXML := typeXML{} + + if tt.bindingBody == binding.JSON { + assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + assert.Equal(t, typeXML{}, objXML) + } + + if tt.bindingBody == binding.XML { + assert.NoError(t, c.ShouldBindBodyWithXML(&objXML)) + assert.Equal(t, typeXML{"FOO"}, objXML) + } + + if tt.bindingBody == binding.YAML { + assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + assert.Equal(t, typeXML{}, objXML) + } + + if tt.bindingBody == binding.TOML { + assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + assert.Equal(t, typeXML{}, objXML) + } + } +} + +func TestContextShouldBindBodyWithYAML(t *testing.T) { + for _, tt := range []struct { + name string + bindingBody binding.BindingBody + body string + }{ + { + name: " YAML & JSON-BODY ", + bindingBody: binding.JSON, + body: `{"foo":"FOO"}`, + }, + { + name: " YAML & XML-BODY ", + bindingBody: binding.XML, + body: ` + +FOO +`, + }, + { + name: " YAML & YAML-BODY ", + bindingBody: binding.YAML, + body: `foo: FOO`, + }, + { + name: " YAML & TOM-BODY ", + bindingBody: binding.TOML, + body: `foo=FOO`, + }, + } { + t.Logf("testing: %s", tt.name) + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + + type typeYAML struct { + Foo string `yaml:"foo" binding:"required"` + } + objYAML := typeYAML{} + + // YAML belongs to a super collection of JSON, so JSON can be parsed by YAML + if tt.bindingBody == binding.JSON { + assert.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) + assert.Equal(t, typeYAML{"FOO"}, objYAML) + } + + if tt.bindingBody == binding.XML { + assert.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) + assert.Equal(t, typeYAML{}, objYAML) + } + + if tt.bindingBody == binding.YAML { + assert.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) + assert.Equal(t, typeYAML{"FOO"}, objYAML) + } + + if tt.bindingBody == binding.TOML { + assert.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) + assert.Equal(t, typeYAML{}, objYAML) + } + } +} + +func TestContextShouldBindBodyWithTOML(t *testing.T) { + for _, tt := range []struct { + name string + bindingBody binding.BindingBody + body string + }{ + { + name: " TOML & JSON-BODY ", + bindingBody: binding.JSON, + body: `{"foo":"FOO"}`, + }, + { + name: " TOML & XML-BODY ", + bindingBody: binding.XML, + body: ` + +FOO +`, + }, + { + name: " TOML & YAML-BODY ", + bindingBody: binding.YAML, + body: `foo: FOO`, + }, + { + name: " TOML & TOM-BODY ", + bindingBody: binding.TOML, + body: `foo = 'FOO'`, + }, + } { + t.Logf("testing: %s", tt.name) + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + + type typeTOML struct { + Foo string `toml:"foo" binding:"required"` + } + objTOML := typeTOML{} + + if tt.bindingBody == binding.JSON { + assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + assert.Equal(t, typeTOML{}, objTOML) + } + + if tt.bindingBody == binding.XML { + assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + assert.Equal(t, typeTOML{}, objTOML) + } + + if tt.bindingBody == binding.YAML { + assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + assert.Equal(t, typeTOML{}, objTOML) + } + + if tt.bindingBody == binding.TOML { + assert.NoError(t, c.ShouldBindBodyWithTOML(&objTOML)) + assert.Equal(t, typeTOML{"FOO"}, objTOML) + } + } +} + func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) diff --git a/docs/doc.md b/docs/doc.md index df006e87..70c9f275 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -1956,7 +1956,12 @@ func SomeHandler(c *gin.Context) { } ``` -For this, you can use `c.ShouldBindBodyWith`. +For this, you can use `c.ShouldBindBodyWith` or shortcuts. + +- `c.ShouldBindBodyWithJSON` is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). +- `c.ShouldBindBodyWithXML` is a shortcut for c.ShouldBindBodyWith(obj, binding.XML). +- `c.ShouldBindBodyWithYAML` is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML). +- `c.ShouldBindBodyWithTOML` is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML). ```go func SomeHandler(c *gin.Context) { @@ -1969,7 +1974,7 @@ func SomeHandler(c *gin.Context) { } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { c.String(http.StatusOK, `the body should be formB JSON`) // And it can accepts other formats - } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { + } else if errB2 := c.ShouldBindBodyWithXML(&objB); errB2 == nil { c.String(http.StatusOK, `the body should be formB XML`) } else { ... From c964ad370bbe007f1b18a7570f058a66f05fbe1f Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Mon, 1 Apr 2024 12:58:01 +0800 Subject: [PATCH 133/332] chore(optimize): the ShouldBindUri method of the Context struct (#3911) Co-authored-by: huangzw --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index afc3c353..391adafe 100644 --- a/context.go +++ b/context.go @@ -739,7 +739,7 @@ func (c *Context) ShouldBindHeader(obj any) error { // ShouldBindUri binds the passed struct pointer using the specified binding engine. func (c *Context) ShouldBindUri(obj any) error { - m := make(map[string][]string) + m := make(map[string][]string, len(c.Params)) for _, v := range c.Params { m[v.Key] = []string{v.Value} } From 56dc72c4d5b1076fc9c6b81f57299739c11910b8 Mon Sep 17 00:00:00 2001 From: imalasong <55082705+imalasong@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:58:00 +0800 Subject: [PATCH 134/332] ci(Makefile): vet command add .PHONY (#3915) --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index ebde4ee8..b58f24f3 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ fmt-check: exit 1; \ fi; +.PHONY: vet vet: $(GO) vet $(VETPACKAGES) From 8acbe657f1c140e3fba38f869978cab2376500c9 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 2 Apr 2024 16:20:48 +0800 Subject: [PATCH 135/332] ci(release): refactor changelog regex patterns and exclusions (#3914) * chore: refactor changelog regex patterns and exclusions - Update the build configuration to skip the build using a comment - Change the `changelog` use from `git` to `github` - Modify the regex patterns for `Features`, `Bug fixes`, and `Enhancements` titles in the changelog - Add a new regex pattern for the `Refactor` title in the changelog - Update the excluded items in the changelog to include `docs` and `CICD` with corrected quotes Signed-off-by: appleboy * chore: update configuration file field names - Change the `skip` field to `disable` in the `.goreleaser.yaml` file. Signed-off-by: appleboy * update Signed-off-by: appleboy * chore: refine changelog categorization rules - Update regular expressions for `feat`, `fix`, and `chore` categories in `.goreleaser.yaml` - Remove `Refactor` category from changelog configuration - Add `Refactor` category with updated regular expression and order to changelog configuration Signed-off-by: Bo-Yi Wu --------- Signed-off-by: appleboy Signed-off-by: Bo-Yi Wu --- .goreleaser.yaml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e435e56a..1cc0bee1 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,8 +1,7 @@ project_name: gin builds: - - - # If true, skip the build. + - # If true, skip the build. # Useful for library projects. # Default is false skip: true @@ -10,7 +9,7 @@ builds: changelog: # Set it to true if you wish to skip the changelog generation. # This may result in an empty release notes on GitHub/GitLab/Gitea. - skip: false + disable: false # Changelog generation implementation to use. # @@ -21,7 +20,7 @@ changelog: # - `github-native`: uses the GitHub release notes generation API, disables the groups feature. # # Defaults to `git`. - use: git + use: github # Sorts the changelog by the commit's messages. # Could either be asc, desc or empty @@ -38,12 +37,15 @@ changelog: - title: Features regexp: "^.*feat[(\\w)]*:+.*$" order: 0 - - title: 'Bug fixes' + - title: "Bug fixes" regexp: "^.*fix[(\\w)]*:+.*$" order: 1 - - title: 'Enhancements' + - title: "Enhancements" regexp: "^.*chore[(\\w)]*:+.*$" order: 2 + - title: "Refactor" + regexp: "^.*refactor[(\\w)]*:+.*$" + order: 3 - title: Others order: 999 @@ -52,6 +54,6 @@ changelog: # the changelog # Default is empty exclude: - - '^docs' - - 'CICD' + - "^docs" + - "CICD" - typo From c6f90df4e0c888c69524307cc35952ec2e7ead41 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 2 Apr 2024 18:57:22 +0800 Subject: [PATCH 136/332] chore: update various Go dependencies to latest versions (#3901) - Update `github.com/bytedance/sonic` from v1.11.0 to v1.11.3 - Update `github.com/go-playground/validator/v10` from v10.18.0 to v10.19.0 - Update `github.com/pelletier/go-toml/v2` from v2.1.1 to v2.2.0 - Update `github.com/stretchr/testify` from v1.8.4 to v1.9.0 - Update `golang.org/x/net` from v0.21.0 to v0.22.0 - Update `golang.org/x/crypto` from v0.19.0 to v0.21.0 - Update `golang.org/x/sys` from v0.17.0 to v0.18.0 Signed-off-by: appleboy --- go.mod | 16 ++++++++-------- go.sum | 32 +++++++++++++++++--------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 11ce23e8..13342ac2 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,16 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.11.0 + github.com/bytedance/sonic v1.11.3 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.18.0 + github.com/go-playground/validator/v10 v10.19.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 - github.com/pelletier/go-toml/v2 v2.1.1 - github.com/stretchr/testify v1.8.4 + github.com/pelletier/go-toml/v2 v2.2.0 + github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.21.0 + golang.org/x/net v0.22.0 google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -24,14 +24,14 @@ require ( github.com/gabriel-vasile/mimetype v1.4.3 // 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.2.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 49eae343..7a3aa22c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.11.0 h1:FwNNv6Vu4z2Onf1++LNzxB/QhitD8wuTdpZzMTGITWo= -github.com/bytedance/sonic v1.11.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= +github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -21,8 +21,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.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= -github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= @@ -30,8 +30,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ 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/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= -github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 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= @@ -42,20 +42,22 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/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.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= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= @@ -63,14 +65,14 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 0397e5e0c0f8f8176c29f7edd8f1bff8e45df780 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 7 Apr 2024 10:18:23 +0800 Subject: [PATCH 137/332] chore: update changelog categories and improve documentation (#3917) - Add new changelog categories for "Build process updates" and "Documentation updates" Signed-off-by: appleboy --- .goreleaser.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 1cc0bee1..ac2b462b 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -46,6 +46,12 @@ changelog: - title: "Refactor" regexp: "^.*refactor[(\\w)]*:+.*$" order: 3 + - title: "Build process updates" + regexp: ^.*?(build|ci)(\(.+\))??!?:.+$ + order: 4 + - title: "Documentation updates" + regexp: ^.*?docs?(\(.+\))??!?:.+$ + order: 4 - title: Others order: 999 From f80ade7a4b85b116d294c7610a89819905fa44d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 07:07:01 +0800 Subject: [PATCH 138/332] chore(deps): bump golangci/golangci-lint-action from 4 to 5 (#3941) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4 to 5. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v4...v5) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 3fe007f1..2e434341 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -25,7 +25,7 @@ jobs: go-version-file: "go.mod" check-latest: true - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v5 with: version: v1.56.2 args: --verbose From b4f66e965ba9d60257e0de4c25d4ad4bd6115927 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 07:07:23 +0800 Subject: [PATCH 139/332] chore(deps): bump github.com/bytedance/sonic from 1.11.3 to 1.11.6 (#3940) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.11.3 to 1.11.6. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.11.3...v1.11.6) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic 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 | 7 ++++--- go.sum | 19 ++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 13342ac2..44e02a30 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.11.3 + github.com/bytedance/sonic v1.11.6 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.19.0 github.com/goccy/go-json v0.10.2 @@ -18,8 +18,9 @@ require ( ) require ( - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect diff --git a/go.sum b/go.sum index 7a3aa22c..2028e88e 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,11 @@ -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= -github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= -github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 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= From a18219566ca25fc51e6d2886bed849c6c3a0cd12 Mon Sep 17 00:00:00 2001 From: dkkb <82504881+dkkb@users.noreply.github.com> Date: Tue, 7 May 2024 09:43:15 +0800 Subject: [PATCH 140/332] feat(binding): Support custom BindUnmarshaler for binding. (#3933) --- binding/form_mapping.go | 20 ++++++++ binding/form_mapping_test.go | 99 ++++++++++++++++++++++++++++++++++++ docs/doc.md | 41 +++++++++++++++ gin_test.go | 24 +++++++++ 4 files changed, 184 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 77a1bde6..db235e56 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -165,6 +165,23 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter return setter.TrySet(value, field, tagValue, setOpt) } +// BindUnmarshaler is the interface used to wrap the UnmarshalParam method. +type BindUnmarshaler interface { + // UnmarshalParam decodes and assigns a value from an form or query param. + UnmarshalParam(param string) error +} + +// trySetCustom tries to set a custom type value +// If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true` +// to skip the default value setting. +func trySetCustom(val string, value reflect.Value) (isSet bool, err error) { + switch v := value.Addr().Interface().(type) { + case BindUnmarshaler: + return true, v.UnmarshalParam(val) + } + return false, nil +} + func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) { vs, ok := form[tagValue] if !ok && !opt.isDefaultExists { @@ -194,6 +211,9 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ if len(vs) > 0 { val = vs[0] } + if ok, err := trySetCustom(val, value); ok { + return ok, err + } return true, setWithProperType(val, value, field) } } diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 16527eb9..ed01a086 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -5,8 +5,11 @@ package binding import ( + "fmt" "mime/multipart" "reflect" + "strconv" + "strings" "testing" "time" @@ -323,3 +326,99 @@ func TestMappingIgnoredCircularRef(t *testing.T) { err := mappingByPtr(&s, formSource{}, "form") assert.NoError(t, err) } + +type customUnmarshalParamHex int + +func (f *customUnmarshalParamHex) UnmarshalParam(param string) error { + v, err := strconv.ParseInt(param, 16, 64) + if err != nil { + return err + } + *f = customUnmarshalParamHex(v) + return nil +} + +func TestMappingCustomUnmarshalParamHexWithFormTag(t *testing.T) { + var s struct { + Foo customUnmarshalParamHex `form:"foo"` + } + err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "form") + assert.NoError(t, err) + + assert.EqualValues(t, 245, s.Foo) +} + +func TestMappingCustomUnmarshalParamHexWithURITag(t *testing.T) { + var s struct { + Foo customUnmarshalParamHex `uri:"foo"` + } + err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "uri") + assert.NoError(t, err) + + assert.EqualValues(t, 245, s.Foo) +} + +type customUnmarshalParamType struct { + Protocol string + Path string + Name string +} + +func (f *customUnmarshalParamType) UnmarshalParam(param string) error { + parts := strings.Split(param, ":") + if len(parts) != 3 { + return fmt.Errorf("invalid format") + } + f.Protocol = parts[0] + f.Path = parts[1] + f.Name = parts[2] + return nil +} + +func TestMappingCustomStructTypeWithFormTag(t *testing.T) { + var s struct { + FileData customUnmarshalParamType `form:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") + assert.NoError(t, err) + + assert.EqualValues(t, "file", s.FileData.Protocol) + assert.EqualValues(t, "/foo", s.FileData.Path) + assert.EqualValues(t, "happiness", s.FileData.Name) +} + +func TestMappingCustomStructTypeWithURITag(t *testing.T) { + var s struct { + FileData customUnmarshalParamType `uri:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") + assert.NoError(t, err) + + assert.EqualValues(t, "file", s.FileData.Protocol) + assert.EqualValues(t, "/foo", s.FileData.Path) + assert.EqualValues(t, "happiness", s.FileData.Name) +} + +func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) { + var s struct { + FileData *customUnmarshalParamType `form:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") + assert.NoError(t, err) + + assert.EqualValues(t, "file", s.FileData.Protocol) + assert.EqualValues(t, "/foo", s.FileData.Path) + assert.EqualValues(t, "happiness", s.FileData.Name) +} + +func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { + var s struct { + FileData *customUnmarshalParamType `uri:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") + assert.NoError(t, err) + + assert.EqualValues(t, "file", s.FileData.Protocol) + assert.EqualValues(t, "/foo", s.FileData.Path) + assert.EqualValues(t, "happiness", s.FileData.Name) +} diff --git a/docs/doc.md b/docs/doc.md index 70c9f275..177c4471 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -27,6 +27,7 @@ - [Only Bind Query String](#only-bind-query-string) - [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind Uri](#bind-uri) + - [Bind custom unmarshaler](#bind-custom-unmarshaler) - [Bind Header](#bind-header) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) @@ -899,6 +900,46 @@ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 curl -v localhost:8088/thinkerou/not-uuid ``` +### Bind custom unmarshaler + +```go +package main + +import ( + "github.com/gin-gonic/gin" + "strings" +) + +type Birthday string + +func (b *Birthday) UnmarshalParam(param string) error { + *b = Birthday(strings.Replace(param, "-", "/", -1)) + return nil +} + +func main() { + route := gin.Default() + var request struct { + Birthday Birthday `form:"birthday"` + } + route.GET("/test", func(ctx *gin.Context) { + _ = ctx.BindQuery(&request) + ctx.JSON(200, request.Birthday) + }) + route.Run(":8088") +} +``` + +Test it with: + +```sh +curl 'localhost:8088/test?birthday=2000-01-01' +``` +Result +```sh +"2000/01/01" +``` + ### Bind Header ```go diff --git a/gin_test.go b/gin_test.go index 4550a7e5..e68f1ce8 100644 --- a/gin_test.go +++ b/gin_test.go @@ -14,6 +14,7 @@ import ( "net/http/httptest" "reflect" "strconv" + "strings" "sync/atomic" "testing" "time" @@ -730,3 +731,26 @@ func TestWithOptionFunc(t *testing.T) { assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"}) assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"}) } + +type Birthday string + +func (b *Birthday) UnmarshalParam(param string) error { + *b = Birthday(strings.Replace(param, "-", "/", -1)) + return nil +} + +func TestCustomUnmarshalStruct(t *testing.T) { + route := Default() + var request struct { + Birthday Birthday `form:"birthday"` + } + route.GET("/test", func(ctx *Context) { + _ = ctx.BindQuery(&request) + ctx.JSON(200, request.Birthday) + }) + req := httptest.NewRequest("GET", "/test?birthday=2000-01-01", nil) + w := httptest.NewRecorder() + route.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + assert.Equal(t, `"2000/01/01"`, w.Body.String()) +} From 638aa19e7d30513f7bc777c62ff8558fd5f90ea5 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 7 May 2024 10:15:53 +0800 Subject: [PATCH 141/332] chore: update external dependencies to latest versions (#3950) - Update `github.com/go-playground/validator/v10` from v10.19.0 to v10.20.0 - Update `github.com/pelletier/go-toml/v2` from v2.2.0 to v2.2.2 - Update `golang.org/x/net` from v0.22.0 to v0.25.0 - Update `google.golang.org/protobuf` from v1.33.0 to v1.34.1 - Update `golang.org/x/arch` from v0.7.0 to v0.8.0 - Update `golang.org/x/crypto` from v0.21.0 to v0.23.0 - Update `golang.org/x/sys` from v0.18.0 to v0.20.0 - Update `golang.org/x/text` from v0.14.0 to v0.15.0 Signed-off-by: Bo-Yi Wu --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 44e02a30..3e94e505 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,15 @@ go 1.20 require ( github.com/bytedance/sonic v1.11.6 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.19.0 + github.com/go-playground/validator/v10 v10.20.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 - github.com/pelletier/go-toml/v2 v2.2.0 + github.com/pelletier/go-toml/v2 v2.2.2 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.22.0 - google.golang.org/protobuf v1.33.0 + golang.org/x/net v0.25.0 + google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -31,8 +31,8 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect ) diff --git a/go.sum b/go.sum index 2028e88e..ce905e70 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,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.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= -github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= @@ -39,8 +39,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -60,21 +60,21 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= -golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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 39089af62535b27aa63608f341c0a339aa88f64e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 7 May 2024 10:16:38 +0800 Subject: [PATCH 142/332] chore: refactor configuration files for better readability (#3951) - Remove filters from the `changelog` section in `.goreleaser.yaml` Signed-off-by: Bo-Yi Wu --- .goreleaser.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index ac2b462b..99b66fee 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -54,12 +54,3 @@ changelog: order: 4 - title: Others order: 999 - - filters: - # Commit messages matching the regexp listed here will be removed from - # the changelog - # Default is empty - exclude: - - "^docs" - - "CICD" - - typo From 75ccf94d605a05fe24817fc2f166f6f2959d5cea Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 7 May 2024 11:23:42 +0800 Subject: [PATCH 143/332] feat: update version constant to v1.10.0 (#3952) - Update the version constant from "v1.9.1" to "v1.10.0" Signed-off-by: Bo-Yi Wu --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 85462e55..93ad9654 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.9.1" +const Version = "v1.10.0" From 490accf5d7d49138f0af806318826d92513b1395 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 7 May 2024 12:50:01 +0800 Subject: [PATCH 144/332] docs: update documentation and release notes for Gin v1.10.0 (#3953) * docs: update documentation and release notes for Gin v1.10.0 - Add release notes for Gin v1.10.0 - Include new features and bug fixes in the changelog - Document enhancements and build process updates - Update documentation for context and middleware functions - Upgrade dependencies and optimize unit tests Signed-off-by: Bo-Yi Wu * feat: refactor CI, enhance file binding, and update dependencies - Add proxy-server authentication feature - Add support for custom BindUnmarshaler for binding - Fix binding error while not uploading file - Refactor CI and update dependencies - Add support for RFC 9512: application/yaml - Optimize the Copy method of the Context struct - Update various Go dependencies to latest versions Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu --- CHANGELOG.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79685205..de47c750 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,82 @@ # Gin ChangeLog +## Gin v1.10.0 + +### Features + +* feat(auth): add proxy-server authentication (#3877) (@EndlessParadox1) +* feat(bind): ShouldBindBodyWith shortcut and change doc (#3871) (@RedCrazyGhost) +* feat(binding): Support custom BindUnmarshaler for binding. (#3933) (@dkkb) +* feat(binding): support override default binding implement (#3514) (@ssfyn) +* feat(engine): Added `OptionFunc` and `With` (#3572) (@flc1125) +* feat(logger): ability to skip logs based on user-defined logic (#3593) (@palvaneh) + +### Bug fixes + +* Revert "fix(uri): query binding bug (#3236)" (#3899) (@appleboy) +* fix(binding): binding error while not upload file (#3819) (#3820) (@clearcodecn) +* fix(binding): dereference pointer to struct (#3199) (@echovl) +* fix(context): make context Value method adhere to Go standards (#3897) (@FarmerChillax) +* fix(engine): fix unit test (#3878) (@flc1125) +* fix(header): Allow header according to RFC 7231 (HTTP 405) (#3759) (@Crocmagnon) +* fix(route): Add fullPath in context copy (#3784) (@KarthikReddyPuli) +* fix(router): catch-all conflicting wildcard (#3812) (@FirePing32) +* fix(sec): upgrade golang.org/x/crypto to 0.17.0 (#3832) (@chncaption) +* fix(tree): correctly expand the capacity of params (#3502) (@georgijd-form3) +* 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) +* chore(IP): add TrustedPlatform constant for Fly.io. (#3839) (@ab) +* chore(debug): add ability to override the debugPrint statement (#2337) (@josegonzalez) +* chore(deps): update dependencies to latest versions (#3835) (@appleboy) +* chore(header): Add support for RFC 9512: application/yaml (#3851) (@vincentbernat) +* chore(http): use white color for HTTP 1XX (#3741) (@viralparmarme) +* chore(optimize): the ShouldBindUri method of the Context struct (#3911) (@1911860538) +* chore(perf): Optimize the Copy method of the Context struct (#3859) (@1911860538) +* chore(refactor): modify interface check way (#3855) (@demoManito) +* chore(request): check reader if it's nil before reading (#3419) (@noahyao1024) +* chore(security): upgrade Protobuf for CVE-2024-24786 (#3893) (@Fotkurz) +* chore: refactor CI and update dependencies (#3848) (@appleboy) +* chore: refactor configuration files for better readability (#3951) (@appleboy) +* chore: update GitHub Actions configuration (#3792) (@appleboy) +* chore: update changelog categories and improve documentation (#3917) (@appleboy) +* chore: update dependencies to latest versions (#3694) (@appleboy) +* chore: update external dependencies to latest versions (#3950) (@appleboy) +* chore: update various Go dependencies to latest versions (#3901) (@appleboy) + +### Build process updates + +* build(codecov): Added a codecov configuration (#3891) (@flc1125) +* ci(Makefile): vet command add .PHONY (#3915) (@imalasong) +* ci(lint): update tooling and workflows for consistency (#3834) (@appleboy) +* ci(release): refactor changelog regex patterns and exclusions (#3914) (@appleboy) +* ci(testing): add go1.22 version (#3842) (@appleboy) + +### Documentation updates + +* docs(context): Added deprecation comments to BindWith (#3880) (@flc1125) +* docs(middleware): comments to function `BasicAuthForProxy` (#3881) (@EndlessParadox1) +* docs: Add document to constant `AuthProxyUserKey` and `BasicAuthForProxy`. (#3887) (@EndlessParadox1) +* docs: fix typo in comment (#3868) (@testwill) +* docs: fix typo in function documentation (#3872) (@TotomiEcio) +* docs: remove redundant comments (#3765) (@WeiTheShinobi) +* feat: update version constant to v1.10.0 (#3952) (@appleboy) + +### Others + +* Upgrade golang.org/x/net -> v0.13.0 (#3684) (@cpcf) +* test(git): gitignore add develop tools (#3370) (@demoManito) +* test(http): use constant instead of numeric literal (#3863) (@testwill) +* test(path): Optimize unit test execution results (#3883) (@flc1125) +* test(render): increased unit tests coverage (#3691) (@araujo88) + ## Gin v1.9.1 -### BUG FIXES +### BUG FIXES * fix Request.Context() checks [#3512](https://github.com/gin-gonic/gin/pull/3512) From e60113dc9531779a15dcb761655d986a56273ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Wed, 8 May 2024 05:29:54 +0800 Subject: [PATCH 145/332] docs(engine): fix comments for the `With` (#3955) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Flcใ‚› --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 1633fe13..8f323788 100644 --- a/gin.go +++ b/gin.go @@ -316,7 +316,7 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { return engine } -// With returns a new Engine instance with the provided options. +// With returns a Engine with the configuration set in the OptionFunc. func (engine *Engine) With(opts ...OptionFunc) *Engine { for _, opt := range opts { opt(engine) From 8dd088927ab50b3b37be1f7ba14931c8eddafe07 Mon Sep 17 00:00:00 2001 From: lgbgbl <65756378+lgbgbl@users.noreply.github.com> Date: Wed, 8 May 2024 06:28:15 +0800 Subject: [PATCH 146/332] refactor(binding): use strings.Cut to replace strings.Index (#3522) --- binding/form_mapping.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index db235e56..108606fa 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -397,11 +397,8 @@ func setTimeDuration(val string, value reflect.Value) error { } func head(str, sep string) (head string, tail string) { - idx := strings.Index(str, sep) - if idx < 0 { - return str, "" - } - return str[:idx], str[idx+len(sep):] + head, tail, _ = strings.Cut(str, sep) + return head, tail } func setFormMap(ptr any, form map[string][]string) error { From f5f5da8fa09d12a22225c493fa8191fb14bdd5bf Mon Sep 17 00:00:00 2001 From: Pedro Aguiar <72931357+codespearhead@users.noreply.github.com> Date: Tue, 7 May 2024 18:31:01 -0400 Subject: [PATCH 147/332] docs(gin): update link to dont-trust-all-proxies section (#3938) (#3945) Update link [1] to [2] after PR #3449 was merged. [1] https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies [2] https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies Closes --- gin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index 8f323788..03edbfff 100644 --- a/gin.go +++ b/gin.go @@ -391,7 +391,7 @@ func (engine *Engine) Run(addr ...string) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } address := resolveAddress(addr) @@ -512,7 +512,7 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "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()) From 7d147928ee232fce156ea7ce8ae6329e148aeb41 Mon Sep 17 00:00:00 2001 From: Kostadin Plachkov <20207730+kplachkov@users.noreply.github.com> Date: Wed, 8 May 2024 04:13:36 +0300 Subject: [PATCH 148/332] fix(gin): data race warning for gin mode (#1580) * fix: data race warning (#1180) * Fix the tests * refactor: remove unnecessary imports and optimize codebase - Remove unnecessary import of `flag` Signed-off-by: Bo-Yi Wu * test: refactor test assertions for mode settings - Update test assertions for mode setting in `mode_test.go` Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu Co-authored-by: Bo-Yi Wu --- debug.go | 3 ++- mode.go | 20 +++++++++----------- mode_test.go | 19 ++++++------------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/debug.go b/debug.go index 1761fe32..0d808f10 100644 --- a/debug.go +++ b/debug.go @@ -10,6 +10,7 @@ import ( "runtime" "strconv" "strings" + "sync/atomic" ) const ginSupportMinGoVer = 18 @@ -17,7 +18,7 @@ const ginSupportMinGoVer = 18 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { - return ginMode == debugCode + return atomic.LoadInt32(&ginMode) == debugCode } // DebugPrintRouteFunc indicates debug log output format. diff --git a/mode.go b/mode.go index fd26d907..13aa3be0 100644 --- a/mode.go +++ b/mode.go @@ -8,6 +8,7 @@ import ( "flag" "io" "os" + "sync/atomic" "github.com/gin-gonic/gin/binding" ) @@ -43,10 +44,8 @@ var DefaultWriter io.Writer = os.Stdout // DefaultErrorWriter is the default io.Writer used by Gin to debug errors var DefaultErrorWriter io.Writer = os.Stderr -var ( - ginMode = debugCode - modeName = DebugMode -) +var ginMode int32 = debugCode +var modeName atomic.Value func init() { mode := os.Getenv(EnvGinMode) @@ -64,17 +63,16 @@ func SetMode(value string) { } switch value { - case DebugMode: - ginMode = debugCode + case DebugMode, "": + atomic.StoreInt32(&ginMode, debugCode) case ReleaseMode: - ginMode = releaseCode + atomic.StoreInt32(&ginMode, releaseCode) case TestMode: - ginMode = testCode + atomic.StoreInt32(&ginMode, testCode) default: panic("gin mode unknown: " + value + " (available mode: debug release test)") } - - modeName = value + modeName.Store(value) } // DisableBindValidation closes the default validator. @@ -96,5 +94,5 @@ func EnableJsonDecoderDisallowUnknownFields() { // Mode returns current gin mode. func Mode() string { - return modeName + return modeName.Load().(string) } diff --git a/mode_test.go b/mode_test.go index 2407f463..be03a9d0 100644 --- a/mode_test.go +++ b/mode_test.go @@ -5,8 +5,8 @@ package gin import ( - "flag" "os" + "sync/atomic" "testing" "github.com/gin-gonic/gin/binding" @@ -18,31 +18,24 @@ func init() { } func TestSetMode(t *testing.T) { - assert.Equal(t, testCode, ginMode) + assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode)) assert.Equal(t, TestMode, Mode()) os.Unsetenv(EnvGinMode) SetMode("") - assert.Equal(t, testCode, ginMode) + assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode)) assert.Equal(t, TestMode, Mode()) - tmp := flag.CommandLine - flag.CommandLine = flag.NewFlagSet("", flag.ContinueOnError) - SetMode("") - assert.Equal(t, debugCode, ginMode) - assert.Equal(t, DebugMode, Mode()) - flag.CommandLine = tmp - SetMode(DebugMode) - assert.Equal(t, debugCode, ginMode) + assert.Equal(t, int32(debugCode), atomic.LoadInt32(&ginMode)) assert.Equal(t, DebugMode, Mode()) SetMode(ReleaseMode) - assert.Equal(t, releaseCode, ginMode) + assert.Equal(t, int32(releaseCode), atomic.LoadInt32(&ginMode)) assert.Equal(t, ReleaseMode, Mode()) SetMode(TestMode) - assert.Equal(t, testCode, ginMode) + assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode)) assert.Equal(t, TestMode, Mode()) assert.Panics(t, func() { SetMode("unknown") }) From b1c1e7b572f76071fb0e0e7884a0697e0458aa7c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 8 May 2024 10:14:42 +0800 Subject: [PATCH 149/332] ci: update Go version requirements and remove test files (#3957) - Update the Go version requirements in `.github/workflows/gin.yml` - Remove test files for Go versions 1.18 and 1.19 - Update the required Go version in `debug.go` and `debug_test.go` - Rename and modify files related to Go version 1.19 and 1.20 in the `internal/bytesconv` directory Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 2 +- context_1.18_test.go | 37 ------------------- context_1.19_test.go | 30 --------------- context_test.go | 13 +++++++ debug.go | 2 +- debug_test.go | 2 +- .../{bytesconv_1.20.go => bytesconv.go} | 2 - internal/bytesconv/bytesconv_1.19.go | 26 ------------- 8 files changed, 16 insertions(+), 98 deletions(-) delete mode 100644 context_1.18_test.go delete mode 100644 context_1.19_test.go rename internal/bytesconv/{bytesconv_1.20.go => bytesconv.go} (97%) delete mode 100644 internal/bytesconv/bytesconv_1.19.go diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 2e434341..4a9dbc93 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -34,7 +34,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.18", "1.19", "1.20", "1.21", "1.22"] + go: ["1.20", "1.21", "1.22"] test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: diff --git a/context_1.18_test.go b/context_1.18_test.go deleted file mode 100644 index 6118beaa..00000000 --- a/context_1.18_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.19 - -package gin - -import ( - "bytes" - "mime/multipart" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestContextFormFileFailed18(t *testing.T) { - buf := new(bytes.Buffer) - mw := multipart.NewWriter(buf) - defer func(mw *multipart.Writer) { - err := mw.Close() - if err != nil { - assert.Error(t, err) - } - }(mw) - c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Set("Content-Type", mw.FormDataContentType()) - c.engine.MaxMultipartMemory = 8 << 20 - assert.Panics(t, func() { - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) - }) -} diff --git a/context_1.19_test.go b/context_1.19_test.go deleted file mode 100644 index dd75325b..00000000 --- a/context_1.19_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2022 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build go1.19 - -package gin - -import ( - "bytes" - "mime/multipart" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestContextFormFileFailed19(t *testing.T) { - buf := new(bytes.Buffer) - mw := multipart.NewWriter(buf) - mw.Close() - c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Set("Content-Type", mw.FormDataContentType()) - c.engine.MaxMultipartMemory = 8 << 20 - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) -} diff --git a/context_test.go b/context_test.go index e9bbae52..ae34c659 100644 --- a/context_test.go +++ b/context_test.go @@ -90,6 +90,19 @@ func TestContextFormFile(t *testing.T) { assert.NoError(t, c.SaveUploadedFile(f, "test")) } +func TestContextFormFileFailed(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + c.engine.MaxMultipartMemory = 8 << 20 + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) +} + func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) diff --git a/debug.go b/debug.go index 0d808f10..ae346e9c 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.18+. + debugPrint(`[WARNING] Now Gin requires Go 1.20+. `) } diff --git a/debug_test.go b/debug_test.go index 2d5e9a56..e3909729 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.18+.\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.20+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/internal/bytesconv/bytesconv_1.20.go b/internal/bytesconv/bytesconv.go similarity index 97% rename from internal/bytesconv/bytesconv_1.20.go rename to internal/bytesconv/bytesconv.go index 5b6040a6..a02c53c3 100644 --- a/internal/bytesconv/bytesconv_1.20.go +++ b/internal/bytesconv/bytesconv.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build go1.20 - package bytesconv import ( diff --git a/internal/bytesconv/bytesconv_1.19.go b/internal/bytesconv/bytesconv_1.19.go deleted file mode 100644 index 669c9c91..00000000 --- a/internal/bytesconv/bytesconv_1.19.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.20 - -package bytesconv - -import ( - "unsafe" -) - -// StringToBytes converts string to byte slice without a memory allocation. -func StringToBytes(s string) []byte { - return *(*[]byte)(unsafe.Pointer( - &struct { - string - Cap int - }{s, len(s)}, - )) -} - -// BytesToString converts byte slice to string without a memory allocation. -func BytesToString(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} From 8791c96960e719ff2f41e24163c5898656cee474 Mon Sep 17 00:00:00 2001 From: Johannes Eiglsperger Date: Wed, 8 May 2024 09:47:54 +0200 Subject: [PATCH 150/332] feat(fs): Export, test and document OnlyFilesFS (#3939) --- fs.go | 52 ++++++++++++++++++++---------------- fs_test.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ routergroup.go | 2 +- 3 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 fs_test.go diff --git a/fs.go b/fs.go index f17d7434..51c3db86 100644 --- a/fs.go +++ b/fs.go @@ -9,37 +9,43 @@ import ( "os" ) -type onlyFilesFS struct { - fs http.FileSystem +// OnlyFilesFS implements an http.FileSystem without `Readdir` functionality. +type OnlyFilesFS struct { + FileSystem http.FileSystem } -type neuteredReaddirFile struct { - http.File -} +// Open passes `Open` to the upstream implementation without `Readdir` functionality. +func (o OnlyFilesFS) Open(name string) (http.File, error) { + f, err := o.FileSystem.Open(name) -// Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally -// in router.Static(). -// if listDirectory == true, then it works the same as http.Dir() otherwise it returns -// a filesystem that prevents http.FileServer() to list the directory files. -func Dir(root string, listDirectory bool) http.FileSystem { - fs := http.Dir(root) - if listDirectory { - return fs - } - return &onlyFilesFS{fs} -} - -// Open conforms to http.Filesystem. -func (fs onlyFilesFS) Open(name string) (http.File, error) { - f, err := fs.fs.Open(name) if err != nil { return nil, err } - return neuteredReaddirFile{f}, nil + + return neutralizedReaddirFile{f}, nil } -// Readdir overrides the http.File default implementation. -func (f neuteredReaddirFile) Readdir(_ int) ([]os.FileInfo, error) { +// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`. +type neutralizedReaddirFile struct { + http.File +} + +// Readdir overrides the http.File default implementation and always returns nil. +func (n neutralizedReaddirFile) Readdir(_ int) ([]os.FileInfo, error) { // this disables directory listing return nil, nil } + +// Dir returns an http.FileSystem that can be used by http.FileServer(). +// It is used internally in router.Static(). +// if listDirectory == true, then it works the same as http.Dir(), +// otherwise it returns a filesystem that prevents http.FileServer() to list the directory files. +func Dir(root string, listDirectory bool) http.FileSystem { + fs := http.Dir(root) + + if listDirectory { + return fs + } + + return &OnlyFilesFS{FileSystem: fs} +} diff --git a/fs_test.go b/fs_test.go new file mode 100644 index 00000000..a1690cd9 --- /dev/null +++ b/fs_test.go @@ -0,0 +1,71 @@ +package gin + +import ( + "errors" + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +type mockFileSystem struct { + open func(name string) (http.File, error) +} + +func (m *mockFileSystem) Open(name string) (http.File, error) { + return m.open(name) +} + +func TestOnlyFilesFS_Open(t *testing.T) { + var testFile *os.File + mockFS := &mockFileSystem{ + open: func(name string) (http.File, error) { + return testFile, nil + }, + } + fs := &OnlyFilesFS{FileSystem: mockFS} + + file, err := fs.Open("foo") + + assert.NoError(t, err) + assert.Equal(t, testFile, file.(neutralizedReaddirFile).File) +} + +func TestOnlyFilesFS_Open_err(t *testing.T) { + testError := errors.New("mock") + mockFS := &mockFileSystem{ + open: func(_ string) (http.File, error) { + return nil, testError + }, + } + fs := &OnlyFilesFS{FileSystem: mockFS} + + file, err := fs.Open("foo") + + assert.ErrorIs(t, err, testError) + assert.Nil(t, file) +} + +func Test_neuteredReaddirFile_Readdir(t *testing.T) { + n := neutralizedReaddirFile{} + + res, err := n.Readdir(0) + + assert.NoError(t, err) + assert.Nil(t, res) +} + +func TestDir_listDirectory(t *testing.T) { + testRoot := "foo" + fs := Dir(testRoot, true) + + assert.Equal(t, http.Dir(testRoot), fs) +} + +func TestDir(t *testing.T) { + testRoot := "foo" + fs := Dir(testRoot, false) + + assert.Equal(t, &OnlyFilesFS{FileSystem: http.Dir(testRoot)}, fs) +} diff --git a/routergroup.go b/routergroup.go index c833fe8f..b2540ec1 100644 --- a/routergroup.go +++ b/routergroup.go @@ -218,7 +218,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { - if _, noListing := fs.(*onlyFilesFS); noListing { + if _, noListing := fs.(*OnlyFilesFS); noListing { c.Writer.WriteHeader(http.StatusNotFound) } From 3ac729dc4a497d360a23b9d7e766c622b3c99f51 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Thu, 9 May 2024 09:17:06 +0800 Subject: [PATCH 151/332] feat(gin): support http3 using quic-go/quic-go (#3210) * experimental support http3 * remove go1.14 and go1.15 * update quic-go package path * only support go1.19+ * remove go19 support * update gomod * chore: refine CI configuration and dependencies - Remove dynamic Go versioning in favor of pinning to major version `1` - Update linter version from `v1.56.2` to `v1.58.1` in GitHub Actions workflow Signed-off-by: Bo-Yi Wu * chore: refactor CI workflow and improve tests - Update the golangci-lint-action version from `v5` to `v6` in the GitHub workflow configuration Signed-off-by: Bo-Yi Wu * chore: update dependencies and CI configurations - Update Go version requirement from `1.20` to `1.21` in `go.mod` Signed-off-by: Bo-Yi Wu * style: refactor codebase and update tests - Add an empty line in the import section of `gin.go` Signed-off-by: Bo-Yi Wu * chore: enhance code quality and consistency - Add `gin.go` to the list of files with specific linters in `.golangci.yml`, applying the `gci` linter. Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu Co-authored-by: Bo-Yi Wu --- .github/workflows/gin.yml | 7 ++--- .golangci.yml | 3 ++ gin.go | 52 +++++++++++++++++++++++------------ gin_integration_test.go | 16 +++++++++++ go.mod | 18 +++++++++--- go.sum | 58 ++++++++++++++++++++++++++++++++------- 6 files changed, 119 insertions(+), 35 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 4a9dbc93..61210772 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -22,12 +22,11 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: "go.mod" - check-latest: true + go-version: "^1" - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v5 + uses: golangci/golangci-lint-action@v6 with: - version: v1.56.2 + version: v1.58.1 args: --verbose test: needs: lint diff --git a/.golangci.yml b/.golangci.yml index 4a72f734..5a65972a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -55,3 +55,6 @@ issues: - linters: - revive path: _test\.go + - path: gin.go + linters: + - gci diff --git a/gin.go b/gin.go index 03edbfff..57f8c2a3 100644 --- a/gin.go +++ b/gin.go @@ -17,6 +17,8 @@ import ( "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/render" + + "github.com/quic-go/quic-go/http3" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) @@ -383,23 +385,6 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { return routes } -// Run attaches the router to a http.Server and starts listening and serving HTTP requests. -// It is a shortcut for http.ListenAndServe(addr, router) -// Note: this method will block the calling goroutine indefinitely unless an error happens. -func (engine *Engine) Run(addr ...string) (err error) { - defer func() { debugPrintError(err) }() - - if engine.isUnsafeTrustedProxies() { - debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") - } - - address := resolveAddress(addr) - debugPrint("Listening and serving HTTP on %s\n", address) - err = http.ListenAndServe(address, engine.Handler()) - return -} - func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) { if engine.trustedProxies == nil { return nil, nil @@ -503,6 +488,23 @@ func parseIP(ip string) net.IP { return parsedIP } +// Run attaches the router to a http.Server and starts listening and serving HTTP requests. +// It is a shortcut for http.ListenAndServe(addr, router) +// Note: this method will block the calling goroutine indefinitely unless an error happens. +func (engine *Engine) Run(addr ...string) (err error) { + defer func() { debugPrintError(err) }() + + if engine.isUnsafeTrustedProxies() { + debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") + } + + address := resolveAddress(addr) + debugPrint("Listening and serving HTTP on %s\n", address) + err = http.ListenAndServe(address, engine.Handler()) + return +} + // RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. @@ -564,6 +566,22 @@ func (engine *Engine) RunFd(fd int) (err error) { return } +// RunQUIC attaches the router to a http.Server and starts listening and serving QUIC requests. +// It is a shortcut for http3.ListenAndServeQUIC(addr, certFile, keyFile, router) +// Note: this method will block the calling goroutine indefinitely unless an error happens. +func (engine *Engine) RunQUIC(addr, certFile, keyFile string) (err error) { + debugPrint("Listening and serving QUIC on %s\n", addr) + defer func() { debugPrintError(err) }() + + if engine.isUnsafeTrustedProxies() { + debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + + "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + } + + err = http3.ListenAndServeQUIC(addr, certFile, keyFile, engine.Handler()) + return +} + // RunListener attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified net.Listener func (engine *Engine) RunListener(listener net.Listener) (err error) { diff --git a/gin_integration_test.go b/gin_integration_test.go index 02b96221..2125df92 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -274,6 +274,22 @@ func TestBadUnixSocket(t *testing.T) { assert.Error(t, router.RunUnix("#/tmp/unix_unit_test")) } +func TestRunQUIC(t *testing.T) { + router := New() + go func() { + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + + assert.NoError(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + }() + + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + assert.Error(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + testRequest(t, "https://localhost:8443/example") +} + func TestFileDescriptor(t *testing.T) { router := New() diff --git a/go.mod b/go.mod index 3e94e505..6063f3bc 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gin-gonic/gin -go 1.20 +go 1.21 require ( github.com/bytedance/sonic v1.11.6 @@ -10,6 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.2 + github.com/quic-go/quic-go v0.43.1 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.25.0 @@ -25,14 +26,23 @@ require ( github.com/gabriel-vasile/mimetype v1.4.3 // 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.2.7 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.8.0 // indirect + go.uber.org/mock v0.4.0 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect + golang.org/x/mod v0.11.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.9.1 // indirect ) diff --git a/go.sum b/go.sum index ce905e70..44af4cc1 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,14 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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= @@ -13,41 +17,66 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +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= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ= +github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -59,24 +88,33 @@ 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.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +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.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -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-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 7e298066baab19316aa2ffc946f1bbc44a68a607 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 9 May 2024 13:45:03 +0800 Subject: [PATCH 152/332] build: update Gin minimum Go version to 1.21 (#3960) * build: update Gin minimum Go version to 1.21 - Update the minimum Go version requirement for Gin from `1.20` to `1.21` in both `debug.go` and `debug_test.go` - Modify the warning message to reflect the new minimum Go version requirement in `debug.go` - Adjust the test assertion to match the updated warning message in `debug_test.go` Signed-off-by: Bo-Yi Wu * docs: refine project documentation and CI configurations - Update supported Go versions for GitHub actions to `1.21` and `1.22` - Specify the required Go version as `1.21` or above in README - Change code block syntax to `sh` in installation and demo run instructions - Remove empty lines in README sections - Update project list formatting without changing the content Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 2 +- README.md | 25 ++++++++++--------------- debug.go | 4 ++-- debug_test.go | 2 +- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 61210772..947abf9c 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.20", "1.21", "1.22"] + go: ["1.21", "1.22"] test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: diff --git a/README.md b/README.md index e007bf2f..04217d64 100644 --- a/README.md +++ b/README.md @@ -25,18 +25,17 @@ Gin is a web framework written in [Go](https://go.dev/). It features a martini-l - Rendering built-in - Extendable - ## Getting started ### Prerequisites -- **[Go](https://go.dev/)**: any one of the **three latest major** [releases](https://go.dev/doc/devel/release) (we test it with these). +The required version of [Go](https://go.dev/) language is [1.21](https://go.dev/doc/devel/release#go1.21.0) or above. ### Getting Gin With [Go module](https://github.com/golang/go/wiki/Modules) support, simply add the following import -``` +```sh import "github.com/gin-gonic/gin" ``` @@ -45,7 +44,7 @@ to your code, and then `go [build|run|test]` will automatically fetch the necess Otherwise, run the following Go command to install the `gin` package: ```sh -$ go get -u github.com/gin-gonic/gin +go get -u github.com/gin-gonic/gin ``` ### Running Gin @@ -74,7 +73,7 @@ func main() { And use the Go command to run the demo: -``` +```sh # run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go ``` @@ -89,7 +88,6 @@ Learn and practice more examples, please read the [Gin Quick Start](docs/doc.md) A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. - ## Documentation See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. @@ -153,23 +151,20 @@ 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 - ## Middlewares You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). - ## Users Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. -* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. -* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. -* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. -* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. -* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. -* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. - +- [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. +- [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. +- [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. +- [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. +- [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. +- [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. ## Contributing diff --git a/debug.go b/debug.go index ae346e9c..62085c5d 100644 --- a/debug.go +++ b/debug.go @@ -13,7 +13,7 @@ import ( "sync/atomic" ) -const ginSupportMinGoVer = 18 +const ginSupportMinGoVer = 21 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -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.20+. + debugPrint(`[WARNING] Now Gin requires Go 1.21+. `) } diff --git a/debug_test.go b/debug_test.go index e3909729..1e576681 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.20+.\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.21+.\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 c677ccc40a60386565dd0d755efacb85d153feca Mon Sep 17 00:00:00 2001 From: thinkerou Date: Fri, 10 May 2024 07:27:42 +0800 Subject: [PATCH 153/332] fix(go): invalid Go toolchain version (#3961) --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 6063f3bc..4937d2b7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gin-gonic/gin -go 1.21 +go 1.21.0 require ( github.com/bytedance/sonic v1.11.6 From 40131af1243ef90e026859bf8ff9c30a5a230351 Mon Sep 17 00:00:00 2001 From: Mobin Mohanan <47410557+tr1sm0s1n@users.noreply.github.com> Date: Mon, 13 May 2024 06:59:21 +0530 Subject: [PATCH 154/332] ci(Makefile): added help and descriptions to targets (#3964) --- Makefile | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index b58f24f3..1a7de86b 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | gr TESTTAGS ?= "" .PHONY: test +# Run tests to verify code functionality. test: echo "mode: count" > coverage.out for d in $(TESTFOLDER); do \ @@ -30,10 +31,12 @@ test: done .PHONY: fmt +# Ensure consistent code formatting. fmt: $(GOFMT) -w $(GOFILES) .PHONY: fmt-check +# format (check only). fmt-check: @diff=$$($(GOFMT) -d $(GOFILES)); \ if [ -n "$$diff" ]; then \ @@ -43,31 +46,36 @@ fmt-check: fi; .PHONY: vet +# Examine packages and report suspicious constructs if any. vet: $(GO) vet $(VETPACKAGES) .PHONY: lint +# Inspect source code for stylistic errors or potential bugs. lint: @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u golang.org/x/lint/golint; \ fi for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; -.PHONY: misspell-check -misspell-check: - @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/client9/misspell/cmd/misspell; \ - fi - misspell -error $(GOFILES) - .PHONY: misspell +# Correct commonly misspelled English words in source code. misspell: @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi misspell -w $(GOFILES) +.PHONY: misspell-check +# misspell (check only). +misspell-check: + @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + $(GO) get -u github.com/client9/misspell/cmd/misspell; \ + fi + misspell -error $(GOFILES) + .PHONY: tools +# Install tools (golint and misspell). tools: @if [ $(GO_VERSION) -gt 15 ]; then \ $(GO) install golang.org/x/lint/golint@latest; \ @@ -76,3 +84,23 @@ tools: $(GO) install golang.org/x/lint/golint; \ $(GO) install github.com/client9/misspell/cmd/misspell; \ fi + +.PHONY: help +# Help. +help: + @echo '' + @echo 'Usage:' + @echo ' make [target]' + @echo '' + @echo 'Targets:' + @awk '/^[a-zA-Z\-\0-9]+:/ { \ + helpMessage = match(lastLine, /^# (.*)/); \ + if (helpMessage) { \ + helpCommand = substr($$1, 0, index($$1, ":")-1); \ + helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \ + printf " - \033[36m%-20s\033[0m %s\n", helpCommand, helpMessage; \ + } \ + } \ + { lastLine = $$0 }' $(MAKEFILE_LIST) + +.DEFAULT_GOAL := help From 6ca8ddb1aed78d9ffaf984e5489111838242fedb Mon Sep 17 00:00:00 2001 From: guonaihong Date: Mon, 13 May 2024 11:11:56 +0800 Subject: [PATCH 155/332] feat(binding): add BindPlain (#3904) * add BindPlain * fix ci/cd error --- binding/binding.go | 1 + binding/binding_nomsgpack.go | 1 + binding/binding_test.go | 40 +++++++++++ binding/plain.go | 56 ++++++++++++++++ context.go | 17 ++++- context_test.go | 124 +++++++++++++++++++++++++++++++++++ 6 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 binding/plain.go diff --git a/binding/binding.go b/binding/binding.go index 94723879..702d0e82 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -84,6 +84,7 @@ var ( YAML BindingBody = yamlBinding{} Uri BindingUri = uriBinding{} Header Binding = headerBinding{} + Plain BindingBody = plainBinding{} TOML BindingBody = tomlBinding{} ) diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index 552a86b2..c8e61310 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -81,6 +81,7 @@ var ( Uri = uriBinding{} Header = headerBinding{} TOML = tomlBinding{} + Plain = plainBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method diff --git a/binding/binding_test.go b/binding/binding_test.go index feb8eed5..c59e5e93 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1342,6 +1342,46 @@ func (h hook) Read([]byte) (int, error) { return 0, errors.New("error") } +type failRead struct{} + +func (f *failRead) Read(b []byte) (n int, err error) { + return 0, errors.New("my fail") +} + +func (f *failRead) Close() error { + return nil +} + +func TestPlainBinding(t *testing.T) { + p := Plain + assert.Equal(t, "plain", p.Name()) + + var s string + req := requestWithBody("POST", "/", "test string") + assert.NoError(t, p.Bind(req, &s)) + assert.Equal(t, s, "test string") + + var bs []byte + req = requestWithBody("POST", "/", "test []byte") + assert.NoError(t, p.Bind(req, &bs)) + assert.Equal(t, bs, []byte("test []byte")) + + var i int + req = requestWithBody("POST", "/", "test fail") + assert.Error(t, p.Bind(req, &i)) + + req = requestWithBody("POST", "/", "") + req.Body = &failRead{} + assert.Error(t, p.Bind(req, &s)) + + req = requestWithBody("POST", "/", "") + assert.Nil(t, p.Bind(req, nil)) + + var ptr *string + req = requestWithBody("POST", "/", "") + assert.Nil(t, p.Bind(req, ptr)) +} + func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) diff --git a/binding/plain.go b/binding/plain.go new file mode 100644 index 00000000..3b250bb0 --- /dev/null +++ b/binding/plain.go @@ -0,0 +1,56 @@ +package binding + +import ( + "fmt" + "io" + "net/http" + "reflect" + + "github.com/gin-gonic/gin/internal/bytesconv" +) + +type plainBinding struct{} + +func (plainBinding) Name() string { + return "plain" +} + +func (plainBinding) Bind(req *http.Request, obj interface{}) error { + all, err := io.ReadAll(req.Body) + if err != nil { + return err + } + + return decodePlain(all, obj) +} + +func (plainBinding) BindBody(body []byte, obj any) error { + return decodePlain(body, obj) +} + +func decodePlain(data []byte, obj any) error { + if obj == nil { + return nil + } + + v := reflect.ValueOf(obj) + + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return nil + } + v = v.Elem() + } + + if v.Kind() == reflect.String { + v.SetString(bytesconv.BytesToString(data)) + return nil + } + + if _, ok := v.Interface().([]byte); ok { + v.SetBytes(data) + return nil + } + + return fmt.Errorf("type (%T) unknown type", v) +} diff --git a/context.go b/context.go index 391adafe..35250667 100644 --- a/context.go +++ b/context.go @@ -614,7 +614,7 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error } defer src.Close() - if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil { + if err = os.MkdirAll(filepath.Dir(dst), 0o750); err != nil { return err } @@ -667,6 +667,11 @@ func (c *Context) BindTOML(obj any) error { return c.MustBindWith(obj, binding.TOML) } +// BindPlain is a shortcut for c.MustBindWith(obj, binding.Plain). +func (c *Context) BindPlain(obj any) error { + return c.MustBindWith(obj, binding.Plain) +} + // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). func (c *Context) BindHeader(obj any) error { return c.MustBindWith(obj, binding.Header) @@ -732,6 +737,11 @@ func (c *Context) ShouldBindTOML(obj any) error { return c.ShouldBindWith(obj, binding.TOML) } +// ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain). +func (c *Context) ShouldBindPlain(obj any) error { + return c.ShouldBindWith(obj, binding.Plain) +} + // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). func (c *Context) ShouldBindHeader(obj any) error { return c.ShouldBindWith(obj, binding.Header) @@ -794,6 +804,11 @@ func (c *Context) ShouldBindBodyWithTOML(obj any) error { return c.ShouldBindBodyWith(obj, binding.TOML) } +// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). +func (c *Context) ShouldBindBodyWithPlain(obj any) error { + return c.ShouldBindBodyWith(obj, binding.Plain) +} + // ClientIP implements one best effort algorithm to return the real client IP. // It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. // If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). diff --git a/context_test.go b/context_test.go index ae34c659..36d6e34a 100644 --- a/context_test.go +++ b/context_test.go @@ -1670,6 +1670,31 @@ func TestContextBindWithXML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextBindPlain(t *testing.T) { + + // string + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`)) + c.Request.Header.Add("Content-Type", MIMEPlain) + + var s string + + assert.NoError(t, c.BindPlain(&s)) + assert.Equal(t, "test string", s) + assert.Equal(t, 0, w.Body.Len()) + + // []byte + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`)) + c.Request.Header.Add("Content-Type", MIMEPlain) + + var bs []byte + + assert.NoError(t, c.BindPlain(&bs)) + assert.Equal(t, []byte("test []byte"), bs) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextBindHeader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -1816,6 +1841,31 @@ func TestContextShouldBindWithXML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextShouldBindPlain(t *testing.T) { + // string + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`)) + c.Request.Header.Add("Content-Type", MIMEPlain) + + var s string + + assert.NoError(t, c.ShouldBindPlain(&s)) + assert.Equal(t, "test string", s) + assert.Equal(t, 0, w.Body.Len()) + // []byte + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`)) + c.Request.Header.Add("Content-Type", MIMEPlain) + + var bs []byte + + assert.NoError(t, c.ShouldBindPlain(&bs)) + assert.Equal(t, []byte("test []byte"), bs) + assert.Equal(t, 0, w.Body.Len()) + +} + func TestContextShouldBindHeader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -2247,6 +2297,80 @@ func TestContextShouldBindBodyWithTOML(t *testing.T) { } } +func TestContextShouldBindBodyWithPlain(t *testing.T) { + for _, tt := range []struct { + name string + bindingBody binding.BindingBody + body string + }{ + { + name: " JSON & JSON-BODY ", + bindingBody: binding.JSON, + body: `{"foo":"FOO"}`, + }, + { + name: " JSON & XML-BODY ", + bindingBody: binding.XML, + body: ` + +FOO +`, + }, + { + name: " JSON & YAML-BODY ", + bindingBody: binding.YAML, + body: `foo: FOO`, + }, + { + name: " JSON & TOM-BODY ", + bindingBody: binding.TOML, + body: `foo=FOO`, + }, + { + name: " JSON & Plain-BODY ", + bindingBody: binding.Plain, + body: `foo=FOO`, + }, + } { + t.Logf("testing: %s", tt.name) + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + + type typeJSON struct { + Foo string `json:"foo" binding:"required"` + } + objJSON := typeJSON{} + + if tt.bindingBody == binding.Plain { + body := "" + assert.NoError(t, c.ShouldBindBodyWithPlain(&body)) + assert.Equal(t, body, "foo=FOO") + } + + if tt.bindingBody == binding.JSON { + assert.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{"FOO"}, objJSON) + } + + if tt.bindingBody == binding.XML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + + if tt.bindingBody == binding.YAML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + + if tt.bindingBody == binding.TOML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + } +} func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) From a569ed8f26a13b10c02920de103eba79c7102cd7 Mon Sep 17 00:00:00 2001 From: crunchyfrog <49813441+truecrunchyfrog@users.noreply.github.com> Date: Mon, 13 May 2024 05:12:55 +0200 Subject: [PATCH 156/332] docs(readme): fix language and moved link (#3962) * Update README.md * more fixes & fix moved link --- README.md | 62 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 04217d64..a595656c 100644 --- a/README.md +++ b/README.md @@ -11,37 +11,36 @@ [![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 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. -**The key features of Gin are:** +**Gin's key features are:** - Zero allocation router -- Fast +- Speed - Middleware support - Crash-free - JSON validation -- Routes grouping +- Route grouping - Error management -- Rendering built-in -- Extendable +- Built-in rendering +- Extensible ## Getting started ### Prerequisites -The required version of [Go](https://go.dev/) language is [1.21](https://go.dev/doc/devel/release#go1.21.0) or above. +Gin requires [Go](https://go.dev/) version [1.21](https://go.dev/doc/devel/release#go1.21.0) or above. ### Getting Gin -With [Go module](https://github.com/golang/go/wiki/Modules) support, simply add the following import +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: ```sh import "github.com/gin-gonic/gin" ``` -to your code, and then `go [build|run|test]` will automatically fetch the necessary dependencies. - -Otherwise, run the following Go command to install the `gin` package: +Alternatively, use `go get`: ```sh go get -u github.com/gin-gonic/gin @@ -49,7 +48,7 @@ go get -u github.com/gin-gonic/gin ### Running Gin -First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`: +A basic example: ```go package main @@ -71,28 +70,29 @@ func main() { } ``` -And use the Go command to run the demo: +To run the code, use the `go run` command, like: ```sh -# run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go ``` -### Learn more examples +Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response! + +### See more examples #### Quick Start -Learn and practice more examples, please read the [Gin Quick Start](docs/doc.md) which includes API examples and builds tag. +Learn and practice with the [Gin Quick Start](docs/doc.md), which includes API examples and builds tag. #### Examples -A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. +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. ## Documentation -See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. +See the [API documentation on godoc.org](https://godoc.org/github.com/gin-gonic/gin). -All documentation is available on the Gin website. +The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages: - [English](https://gin-gonic.com/docs/) - [็ฎ€ไฝ“ไธญๆ–‡](https://gin-gonic.com/zh-cn/docs/) @@ -103,15 +103,13 @@ All documentation is available on the Gin website. - [Turkish](https://gin-gonic.com/tr/docs/) - [Persian](https://gin-gonic.com/fa/docs/) -### Articles about Gin - -A curated list of awesome Gin framework. +### Articles - [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin) ## Benchmarks -Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks details](/BENCHMARKS.md). +Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks](/BENCHMARKS.md). | Benchmark name | (1) | (2) | (3) | (4) | | ------------------------------ | ---------:| ---------------:| ------------:| ---------------:| @@ -151,23 +149,23 @@ 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 -## Middlewares +## Middleware You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). -## Users +## Uses -Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. +Here are some awesome projects that are using the [Gin](https://github.com/gin-gonic/gin) web framework. -- [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. -- [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. -- [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. -- [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. -- [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. +- [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. ## Contributing Gin is the work of hundreds of contributors. We appreciate your help! -Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. +Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. From 3f5b0afa2ac85ea79638ca08f4140ce64b8246e5 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Mon, 13 May 2024 13:32:46 +0800 Subject: [PATCH 157/332] refactor(slice): simplify SliceValidationError Error method (#3910) * Simplify SliceValidationError Error method * Replace fmt.Fprintf with b.WriteString --------- Co-authored-by: huangzw Co-authored-by: 1911860538 --- binding/default_validator.go | 31 +++++++++------------ binding/default_validator_benchmark_test.go | 12 +++++--- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/binding/default_validator.go b/binding/default_validator.go index ac43d7cc..44b7a2ac 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -5,8 +5,8 @@ package binding import ( - "fmt" "reflect" + "strconv" "strings" "sync" @@ -22,25 +22,20 @@ type SliceValidationError []error // Error concatenates all error elements in SliceValidationError into a single string separated by \n. func (err SliceValidationError) Error() string { - n := len(err) - switch n { - case 0: + if len(err) == 0 { return "" - default: - var b strings.Builder - if err[0] != nil { - fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error()) - } - if n > 1 { - for i := 1; i < n; i++ { - if err[i] != nil { - b.WriteString("\n") - fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error()) - } - } - } - return b.String() } + + var b strings.Builder + for i := 0; i < len(err); i++ { + if err[i] != nil { + if b.Len() > 0 { + b.WriteString("\n") + } + b.WriteString("[" + strconv.Itoa(i) + "]: " + err[i].Error()) + } + } + return b.String() } var _ StructValidator = (*defaultValidator)(nil) diff --git a/binding/default_validator_benchmark_test.go b/binding/default_validator_benchmark_test.go index 9292e2aa..44547412 100644 --- a/binding/default_validator_benchmark_test.go +++ b/binding/default_validator_benchmark_test.go @@ -12,11 +12,15 @@ import ( func BenchmarkSliceValidationError(b *testing.B) { const size int = 100 + e := make(SliceValidationError, size) + for j := 0; j < size; j++ { + e[j] = errors.New(strconv.Itoa(j)) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { - e := make(SliceValidationError, size) - for j := 0; j < size; j++ { - e[j] = errors.New(strconv.Itoa(j)) - } if len(e.Error()) == 0 { b.Errorf("error") } From 36b0dede4b8c4a67d92c4107cebc5a068364321d Mon Sep 17 00:00:00 2001 From: 51pwn <18223385+hktalent@users.noreply.github.com> Date: Mon, 13 May 2024 14:55:41 +0800 Subject: [PATCH 158/332] fix(context): check handler is nil (#3413) * fixed #3404 2022-11-23 * up 2022-11-23 * refactor: refactor context handling and nil checks - Refactor nil checks to improve readability in `context.go` - Modify the control flow in `HandlerNames` and `Next` methods to continue on nil values before appending or invoking handlers in `context.go` Signed-off-by: Bo-Yi Wu * test: refactor context_test.go for clarity and efficiency - Insert a `nil` value into the `HandlersChain` array in `context_test.go` - Remove empty test functions in `context_test.go` Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu Co-authored-by: Bo-Yi Wu --- context.go | 6 ++++++ context_test.go | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 35250667..cd2645a9 100644 --- a/context.go +++ b/context.go @@ -152,6 +152,9 @@ func (c *Context) HandlerName() string { func (c *Context) HandlerNames() []string { hn := make([]string, 0, len(c.handlers)) for _, val := range c.handlers { + if val == nil { + continue + } hn = append(hn, nameOfFunction(val)) } return hn @@ -182,6 +185,9 @@ func (c *Context) FullPath() string { func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { + if c.handlers[c.index] == nil { + continue + } c.handlers[c.index](c) c.index++ } diff --git a/context_test.go b/context_test.go index 36d6e34a..517f73e2 100644 --- a/context_test.go +++ b/context_test.go @@ -362,7 +362,7 @@ func TestContextHandlerName(t *testing.T) { func TestContextHandlerNames(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.handlers = HandlersChain{func(c *Context) {}, handlerNameTest, func(c *Context) {}, handlerNameTest2} + c.handlers = HandlersChain{func(c *Context) {}, nil, handlerNameTest, func(c *Context) {}, handlerNameTest2} names := c.HandlerNames() @@ -1671,7 +1671,6 @@ func TestContextBindWithXML(t *testing.T) { } func TestContextBindPlain(t *testing.T) { - // string w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -1863,7 +1862,6 @@ func TestContextShouldBindPlain(t *testing.T) { assert.NoError(t, c.ShouldBindPlain(&bs)) assert.Equal(t, []byte("test []byte"), bs) assert.Equal(t, 0, w.Body.Len()) - } func TestContextShouldBindHeader(t *testing.T) { @@ -2371,6 +2369,7 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { } } } + func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) From 4f339e6a35b163d31b30916b37f4176d385f41bd Mon Sep 17 00:00:00 2001 From: RedCrazyGhost <49381700+RedCrazyGhost@users.noreply.github.com> Date: Tue, 14 May 2024 10:25:54 +0800 Subject: [PATCH 159/332] fix(context): YAML judgment logic in Negotiate (#3966) --- context.go | 3 ++- context_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index cd2645a9..d2d5497e 100644 --- a/context.go +++ b/context.go @@ -34,6 +34,7 @@ const ( MIMEPOSTForm = binding.MIMEPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm MIMEYAML = binding.MIMEYAML + MIMEYAML2 = binding.MIMEYAML2 MIMETOML = binding.MIMETOML ) @@ -1182,7 +1183,7 @@ func (c *Context) Negotiate(code int, config Negotiate) { data := chooseData(config.XMLData, config.Data) c.XML(code, data) - case binding.MIMEYAML: + case binding.MIMEYAML, binding.MIMEYAML2: data := chooseData(config.YAMLData, config.Data) c.YAML(code, data) diff --git a/context_test.go b/context_test.go index 517f73e2..b700389a 100644 --- a/context_test.go +++ b/context_test.go @@ -1196,7 +1196,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEJSON, MIMEXML, MIMEYAML}, + Offered: []string{MIMEJSON, MIMEXML, MIMEYAML, MIMEYAML2}, Data: H{"foo": "bar"}, }) @@ -1211,7 +1211,7 @@ func TestContextNegotiationWithXML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEXML, MIMEJSON, MIMEYAML}, + Offered: []string{MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2}, Data: H{"foo": "bar"}, }) @@ -1226,7 +1226,7 @@ func TestContextNegotiationWithYAML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML}, + Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML, MIMEYAML2}, Data: H{"foo": "bar"}, }) @@ -1241,7 +1241,7 @@ func TestContextNegotiationWithTOML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML}, + Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2}, Data: H{"foo": "bar"}, }) From e0d46ded6cb6974d55a255ab122d1aa6ca0cd60e Mon Sep 17 00:00:00 2001 From: Adriano Sela Aviles Date: Sat, 18 May 2024 19:48:07 -0700 Subject: [PATCH 160/332] fix(context): verify URL is Non-nil in initQueryCache() (#3969) --- context.go | 2 +- context_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index d2d5497e..baa4b0f9 100644 --- a/context.go +++ b/context.go @@ -475,7 +475,7 @@ func (c *Context) QueryArray(key string) (values []string) { func (c *Context) initQueryCache() { if c.queryCache == nil { - if c.Request != nil { + if c.Request != nil && c.Request.URL != nil { c.queryCache = c.Request.URL.Query() } else { c.queryCache = url.Values{} diff --git a/context_test.go b/context_test.go index b700389a..8bbf2700 100644 --- a/context_test.go +++ b/context_test.go @@ -423,6 +423,49 @@ func TestContextQuery(t *testing.T) { assert.Empty(t, c.PostForm("foo")) } +func TestContextInitQueryCache(t *testing.T) { + validURL, err := url.Parse("https://github.com/gin-gonic/gin/pull/3969?key=value&otherkey=othervalue") + assert.Nil(t, err) + + tests := []struct { + testName string + testContext *Context + expectedQueryCache url.Values + }{ + { + testName: "queryCache should remain unchanged if already not nil", + testContext: &Context{ + queryCache: url.Values{"a": []string{"b"}}, + Request: &http.Request{URL: validURL}, // valid request for evidence that values weren't extracted + }, + expectedQueryCache: url.Values{"a": []string{"b"}}, + }, + { + testName: "queryCache should be empty when Request is nil", + testContext: &Context{Request: nil}, // explicit nil for readability + expectedQueryCache: url.Values{}, + }, + { + testName: "queryCache should be empty when Request.URL is nil", + testContext: &Context{Request: &http.Request{URL: nil}}, // explicit nil for readability + expectedQueryCache: url.Values{}, + }, + { + testName: "queryCache should be populated when it not yet populated and Request + Request.URL are non nil", + testContext: &Context{Request: &http.Request{URL: validURL}}, // explicit nil for readability + expectedQueryCache: url.Values{"key": []string{"value"}, "otherkey": []string{"othervalue"}}, + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + test.testContext.initQueryCache() + assert.Equal(t, test.expectedQueryCache, test.testContext.queryCache) + }) + } + +} + func TestContextDefaultQueryOnEmptyRequest(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // here c.Request == nil assert.NotPanics(t, func() { From 24d67647cb9b4e0bbdcdec7f0c2086e8004e1572 Mon Sep 17 00:00:00 2001 From: bruceNu1l <144002160+bruceNu1l@users.noreply.github.com> Date: Thu, 23 May 2024 10:16:11 +0800 Subject: [PATCH 161/332] feat(form): add custom string slice for form tag unmarshal (#3970) (#3971) Co-authored-by: Bruce Lee --- binding/form_mapping.go | 11 +++++ binding/form_mapping_test.go | 87 ++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 108606fa..33389b28 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -193,14 +193,25 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ if !ok { vs = []string{opt.defaultValue} } + + if ok, err = trySetCustom(vs[0], value); ok { + return ok, err + } + return true, setSlice(vs, value, field) case reflect.Array: if !ok { vs = []string{opt.defaultValue} } + + if ok, err = trySetCustom(vs[0], value); ok { + return ok, err + } + if len(vs) != value.Len() { return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) } + return true, setArray(vs, value, field) default: var val string diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index ed01a086..afd51f9d 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -5,6 +5,7 @@ package binding import ( + "encoding/hex" "fmt" "mime/multipart" "reflect" @@ -422,3 +423,89 @@ func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { assert.EqualValues(t, "/foo", s.FileData.Path) assert.EqualValues(t, "happiness", s.FileData.Name) } + +type customPath []string + +func (p *customPath) UnmarshalParam(param string) error { + elems := strings.Split(param, "/") + n := len(elems) + if n < 2 { + return fmt.Errorf("invalid format") + } + + *p = elems + return nil +} + +func TestMappingCustomSliceUri(t *testing.T) { + var s struct { + FileData customPath `uri:"path"` + } + err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri") + assert.NoError(t, err) + + assert.EqualValues(t, "bar", s.FileData[0]) + assert.EqualValues(t, "foo", s.FileData[1]) +} + +func TestMappingCustomSliceForm(t *testing.T) { + var s struct { + FileData customPath `form:"path"` + } + err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form") + assert.NoError(t, err) + + assert.EqualValues(t, "bar", s.FileData[0]) + assert.EqualValues(t, "foo", s.FileData[1]) +} + +type objectID [12]byte + +func (o *objectID) UnmarshalParam(param string) error { + oid, err := convertTo(param) + if err != nil { + return err + } + + *o = oid + return nil +} + +func convertTo(s string) (objectID, error) { + var nilObjectID objectID + if len(s) != 24 { + return nilObjectID, fmt.Errorf("invalid format") + } + + var oid [12]byte + _, err := hex.Decode(oid[:], []byte(s)) + if err != nil { + return nilObjectID, err + } + + return oid, nil +} + +func TestMappingCustomArrayUri(t *testing.T) { + var s struct { + FileData objectID `uri:"id"` + } + val := `664a062ac74a8ad104e0e80f` + err := mappingByPtr(&s, formSource{"id": {val}}, "uri") + assert.NoError(t, err) + + expected, _ := convertTo(val) + assert.EqualValues(t, expected, s.FileData) +} + +func TestMappingCustomArrayForm(t *testing.T) { + var s struct { + FileData objectID `form:"id"` + } + val := `664a062ac74a8ad104e0e80f` + err := mappingByPtr(&s, formSource{"id": {val}}, "form") + assert.NoError(t, err) + + expected, _ := convertTo(val) + assert.EqualValues(t, expected, s.FileData) +} From 334160bab772f6f93767b870f9d07c176cd4aa2b Mon Sep 17 00:00:00 2001 From: Endless Paradox Date: Fri, 24 May 2024 14:55:25 +0800 Subject: [PATCH 162/332] chore(tree): replace the self-defined 'min' to official one (#3975) --- tree.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/tree.go b/tree.go index 878023d1..ce0f065c 100644 --- a/tree.go +++ b/tree.go @@ -65,17 +65,10 @@ func (trees methodTrees) get(method string) *node { return nil } -func min(a, b int) int { - if a <= b { - return a - } - return b -} - func longestCommonPrefix(a, b string) int { i := 0 - max := min(len(a), len(b)) - for i < max && a[i] == b[i] { + max_ := min(len(a), len(b)) + for i < max_ && a[i] == b[i] { i++ } return i @@ -205,7 +198,7 @@ walk: } // Check if a child with the next path byte exists - for i, max := 0, len(n.indices); i < max; i++ { + for i, max_ := 0, len(n.indices); i < max_; i++ { if c == n.indices[i] { parentFullPathIndex += len(n.path) i = n.incrementChildPrio(i) @@ -770,7 +763,7 @@ walk: // Outer loop for walking the tree // Runes are up to 4 byte long, // -4 would definitely be another rune. var off int - for max := min(npLen, 3); off < max; off++ { + for max_ := min(npLen, 3); off < max_; off++ { if i := npLen - off; utf8.RuneStart(oldPath[i]) { // read rune from cached path rv, _ = utf8.DecodeRuneInString(oldPath[i:]) From 4621b7ac982335d9a74432e182dd2bfc6d841431 Mon Sep 17 00:00:00 2001 From: wssccc Date: Sat, 1 Jun 2024 13:44:57 +0800 Subject: [PATCH 163/332] feat(router): add literal colon support (#1432) (#2857) --- gin.go | 25 ++++++++++++++++++++++++- gin_integration_test.go | 25 +++++++++++++++++++++++++ tree.go | 12 ++++++++++++ tree_test.go | 22 ++++++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 57f8c2a3..5ba1cf63 100644 --- a/gin.go +++ b/gin.go @@ -24,6 +24,9 @@ import ( ) const defaultMultipartMemory = 32 << 20 // 32 MB +const escapedColon = "\\:" +const colon = ":" +const backslash = "\\" var ( default404Body = []byte("404 page not found") @@ -474,6 +477,26 @@ func (engine *Engine) validateHeader(header string) (clientIP string, valid bool return "", false } +// updateRouteTree do update to the route tree recursively +func updateRouteTree(n *node) { + n.path = strings.ReplaceAll(n.path, escapedColon, colon) + n.fullPath = strings.ReplaceAll(n.fullPath, escapedColon, colon) + n.indices = strings.ReplaceAll(n.indices, backslash, colon) + if n.children == nil { + return + } + for _, child := range n.children { + updateRouteTree(child) + } +} + +// updateRouteTrees do update to the route trees +func (engine *Engine) updateRouteTrees() { + for _, tree := range engine.trees { + updateRouteTree(tree.root) + } +} + // parseIP parse a string representation of an IP and returns a net.IP with the // minimum byte representation or nil if input is invalid. func parseIP(ip string) net.IP { @@ -498,7 +521,7 @@ func (engine *Engine) Run(addr ...string) (err error) { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } - + engine.updateRouteTrees() address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) err = http.ListenAndServe(address, engine.Handler()) diff --git a/gin_integration_test.go b/gin_integration_test.go index 2125df92..53982712 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -577,3 +577,28 @@ func TestTreeRunDynamicRouting(t *testing.T) { func isWindows() bool { return runtime.GOOS == "windows" } + +func TestEscapedColon(t *testing.T) { + router := New() + f := func(u string) { + router.GET(u, func(c *Context) { c.String(http.StatusOK, u) }) + } + f("/r/r\\:r") + f("/r/r:r") + f("/r/r/:r") + f("/r/r/\\:r") + f("/r/r/r\\:r") + assert.Panics(t, func() { + f("\\foo:") + }) + + router.updateRouteTrees() + ts := httptest.NewServer(router) + defer ts.Close() + + testRequest(t, ts.URL+"/r/r123", "", "/r/r:r") + testRequest(t, ts.URL+"/r/r:r", "", "/r/r\\:r") + testRequest(t, ts.URL+"/r/r/r123", "", "/r/r/:r") + testRequest(t, ts.URL+"/r/r/:r", "", "/r/r/\\:r") + testRequest(t, ts.URL+"/r/r/r:r", "", "/r/r/r\\:r") +} diff --git a/tree.go b/tree.go index ce0f065c..b0a5f982 100644 --- a/tree.go +++ b/tree.go @@ -262,7 +262,19 @@ walk: // Returns -1 as index, if no wildcard was found. func findWildcard(path string) (wildcard string, i int, valid bool) { // Find start + escapeColon := false for start, c := range []byte(path) { + if escapeColon { + escapeColon = false + if c == ':' { + continue + } + panic("invalid escape string in path '" + path + "'") + } + if c == '\\' { + escapeColon = true + continue + } // A wildcard starts with ':' (param) or '*' (catch-all) if c != ':' && c != '*' { continue diff --git a/tree_test.go b/tree_test.go index c9b03130..3aa3a594 100644 --- a/tree_test.go +++ b/tree_test.go @@ -192,6 +192,7 @@ func TestTreeWildcard(t *testing.T) { "/get/abc/123abg/:param", "/get/abc/123abf/:param", "/get/abc/123abfff/:param", + "/get/abc/escaped_colon/test\\:param", } for _, route := range routes { tree.addRoute(route, fakeHandler(route)) @@ -315,6 +316,7 @@ func TestTreeWildcard(t *testing.T) { {"/get/abc/123abg/test", false, "/get/abc/123abg/:param", Params{Param{Key: "param", Value: "test"}}}, {"/get/abc/123abf/testss", false, "/get/abc/123abf/:param", Params{Param{Key: "param", Value: "testss"}}}, {"/get/abc/123abfff/te", false, "/get/abc/123abfff/:param", Params{Param{Key: "param", Value: "te"}}}, + {"/get/abc/escaped_colon/test\\:param", false, "/get/abc/escaped_colon/test\\:param", nil}, }) checkPriorities(t, tree) @@ -419,6 +421,9 @@ func TestTreeWildcardConflict(t *testing.T) { {"/id/:id", false}, {"/static/*file", false}, {"/static/", true}, + {"/escape/test\\:d1", false}, + {"/escape/test\\:d2", false}, + {"/escape/test:param", false}, } testRoutes(t, routes) } @@ -971,3 +976,20 @@ func TestTreeWildcardConflictEx(t *testing.T) { } } } + +func TestTreeInvalidEscape(t *testing.T) { + routes := map[string]bool{ + "/r1/r": true, + "/r2/:r": true, + "/r3/\\:r": true, + } + tree := &node{} + for route, valid := range routes { + recv := catchPanic(func() { + tree.addRoute(route, fakeHandler(route)) + }) + if recv == nil != valid { + t.Fatalf("%s should be %t but got %v", route, valid, recv) + } + } +} From 64ead9e6bd924d431f4dd612349bc5e13300e6fc Mon Sep 17 00:00:00 2001 From: Meng Zhuo Date: Thu, 6 Jun 2024 17:10:03 +0800 Subject: [PATCH 164/332] docs(readme): replace godoc with pkg (#3985) * Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a595656c..faeb4952 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Build Status](https://github.com/gin-gonic/gin/workflows/Run%20Tests/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions?query=branch%3Amaster) [![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) -[![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) +[![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) [![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) @@ -90,7 +90,7 @@ A number of ready-to-run examples demonstrating various use cases of Gin are ava ## Documentation -See the [API documentation on godoc.org](https://godoc.org/github.com/gin-gonic/gin). +See the [API documentation on go.dev](https://pkg.go.dev/github.com/gin-gonic/gin). The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages: From 9c081de9cdd1948f521d47d170d18cbc2981c33a Mon Sep 17 00:00:00 2001 From: demouth <1133178+demouth@users.noreply.github.com> Date: Sun, 16 Jun 2024 01:28:08 +0900 Subject: [PATCH 165/332] docs: fix typo in Gin Quick Start (#3997) --- docs/doc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doc.md b/docs/doc.md index 177c4471..51366409 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -524,7 +524,7 @@ func main() { return c.Writer.Status() < http.StatusInternalServerError } - engine.Use(gin.LoggerWithConfig(loggerConfig)) + router.Use(gin.LoggerWithConfig(loggerConfig)) router.Use(gin.Recovery()) // skipped From 626d55b0c02937645c21774cacc021713de88604 Mon Sep 17 00:00:00 2001 From: Pierre-Henri Symoneaux Date: Sat, 22 Jun 2024 16:19:04 +0200 Subject: [PATCH 166/332] fix(gin): Do not panic when handling method not allowed on empty tree (#4003) Signed-off-by: Pierre-Henri Symoneaux --- gin.go | 2 +- gin_test.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 5ba1cf63..48cc15c9 100644 --- a/gin.go +++ b/gin.go @@ -687,7 +687,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { break } - if engine.HandleMethodNotAllowed { + if engine.HandleMethodNotAllowed && len(t) > 0 { // According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response // containing a list of the target resource's currently supported methods. allowed := make([]string, 0, len(t)-1) diff --git a/gin_test.go b/gin_test.go index e68f1ce8..db70a8c5 100644 --- a/gin_test.go +++ b/gin_test.go @@ -754,3 +754,14 @@ func TestCustomUnmarshalStruct(t *testing.T) { assert.Equal(t, 200, w.Code) assert.Equal(t, `"2000/01/01"`, w.Body.String()) } + +// Test the fix for https://github.com/gin-gonic/gin/issues/4002 +func TestMethodNotAllowedNoRoute(t *testing.T) { + g := New() + g.HandleMethodNotAllowed = true + + req := httptest.NewRequest("GET", "/", nil) + resp := httptest.NewRecorder() + assert.NotPanics(t, func() { g.ServeHTTP(resp, req) }) + assert.Equal(t, http.StatusNotFound, resp.Code) +} From 5f55c6a711376c77834bc6b25d35c8985de1d311 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sun, 14 Jul 2024 14:33:08 +0200 Subject: [PATCH 167/332] ci(lint): enable testifylint linter (#4010) Signed-off-by: Matthieu MOREL --- .golangci.yml | 3 + binding/binding_msgpack_test.go | 7 +- binding/binding_test.go | 259 +++++++++++++------------ binding/form_mapping_test.go | 79 ++++---- binding/multipart_form_mapping_test.go | 21 +- binding/validate_test.go | 39 ++-- context_test.go | 239 +++++++++++------------ debug_test.go | 9 +- errors_test.go | 5 +- fs_test.go | 7 +- gin_integration_test.go | 89 ++++----- gin_test.go | 27 +-- githubapi_test.go | 15 +- logger_test.go | 12 +- path_test.go | 2 +- render/render_msgpack_test.go | 5 +- render/render_test.go | 75 +++---- response_writer_test.go | 9 +- routes_test.go | 7 +- utils_test.go | 4 +- 20 files changed, 461 insertions(+), 452 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 5a65972a..8d58c989 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,6 +17,7 @@ linters: - nilerr - nolintlint - revive + - testifylint - wastedassign linters-settings: @@ -33,6 +34,8 @@ linters-settings: - G112 - G201 - G203 + testifylint: + enable-all: true issues: exclude-rules: diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go index a6cd6aa8..a8116391 100644 --- a/binding/binding_msgpack_test.go +++ b/binding/binding_msgpack_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/ugorji/go/codec" ) @@ -24,7 +25,7 @@ func TestBindingMsgPack(t *testing.T) { buf := bytes.NewBuffer([]byte{}) assert.NotNil(t, buf) err := codec.NewEncoder(buf, h).Encode(test) - assert.NoError(t, err) + require.NoError(t, err) data := buf.Bytes() @@ -41,14 +42,14 @@ func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEMSGPACK) err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEMSGPACK) err = MsgPack.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestBindingDefaultMsgPack(t *testing.T) { diff --git a/binding/binding_test.go b/binding/binding_test.go index c59e5e93..2036b59b 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -20,6 +20,7 @@ import ( "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) @@ -175,7 +176,7 @@ func TestBindingJSONNilBody(t *testing.T) { var obj FooStruct req, _ := http.NewRequest(http.MethodPost, "/", nil) err := JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestBindingJSON(t *testing.T) { @@ -376,7 +377,7 @@ func TestBindingFormStringSliceMap(t *testing.T) { req := requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") req.Header.Add("Content-Type", MIMEPOSTForm) err := Form.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, obj) assert.Len(t, obj, 2) target := map[string][]string{ @@ -389,7 +390,7 @@ func TestBindingFormStringSliceMap(t *testing.T) { req = requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") req.Header.Add("Content-Type", MIMEPOSTForm) err = Form.Bind(req, &objInvalid) - assert.Error(t, err) + require.Error(t, err) } func TestBindingQuery(t *testing.T) { @@ -428,7 +429,7 @@ func TestBindingQueryStringMap(t *testing.T) { obj := make(map[string]string) req := requestWithBody("GET", "/?foo=bar&hello=world", "") err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, obj) assert.Len(t, obj, 2) assert.Equal(t, "bar", obj["foo"]) @@ -437,7 +438,7 @@ func TestBindingQueryStringMap(t *testing.T) { obj = make(map[string]string) req = requestWithBody("GET", "/?foo=bar&foo=2&hello=world", "") // should pick last err = b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, obj) assert.Len(t, obj, 2) assert.Equal(t, "2", obj["foo"]) @@ -495,28 +496,28 @@ func TestBindingYAMLFail(t *testing.T) { func createFormPostRequest(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createDefaultFormPostRequest(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestForMap(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestForMapFail(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } @@ -527,20 +528,20 @@ func createFormFilesMultipartRequest(t *testing.T) *http.Request { mw := multipart.NewWriter(body) defer mw.Close() - assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("foo", "bar")) - assert.NoError(t, mw.WriteField("bar", "foo")) + require.NoError(t, mw.SetBoundary(boundary)) + require.NoError(t, mw.WriteField("foo", "bar")) + require.NoError(t, mw.WriteField("bar", "foo")) f, err := os.Open("form.go") - assert.NoError(t, err) + require.NoError(t, err) defer f.Close() fw, err1 := mw.CreateFormFile("file", "form.go") - assert.NoError(t, err1) + require.NoError(t, err1) _, err = io.Copy(fw, f) - assert.NoError(t, err) + require.NoError(t, err) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) - assert.NoError(t, err2) + require.NoError(t, err2) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -552,20 +553,20 @@ func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { mw := multipart.NewWriter(body) defer mw.Close() - assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("foo", "bar")) - assert.NoError(t, mw.WriteField("bar", "foo")) + require.NoError(t, mw.SetBoundary(boundary)) + require.NoError(t, mw.WriteField("foo", "bar")) + require.NoError(t, mw.WriteField("bar", "foo")) f, err := os.Open("form.go") - assert.NoError(t, err) + require.NoError(t, err) defer f.Close() fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go") - assert.NoError(t, err1) + require.NoError(t, err1) _, err = io.Copy(fw, f) - assert.NoError(t, err) + require.NoError(t, err) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) - assert.NoError(t, err2) + require.NoError(t, err2) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -577,11 +578,11 @@ func createFormMultipartRequest(t *testing.T) *http.Request { mw := multipart.NewWriter(body) defer mw.Close() - assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("foo", "bar")) - assert.NoError(t, mw.WriteField("bar", "foo")) + require.NoError(t, mw.SetBoundary(boundary)) + require.NoError(t, mw.WriteField("foo", "bar")) + require.NoError(t, mw.WriteField("bar", "foo")) req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -592,10 +593,10 @@ func createFormMultipartRequestForMap(t *testing.T) *http.Request { mw := multipart.NewWriter(body) defer mw.Close() - assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")) + require.NoError(t, mw.SetBoundary(boundary)) + require.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -606,10 +607,10 @@ func createFormMultipartRequestForMapFail(t *testing.T) *http.Request { mw := multipart.NewWriter(body) defer mw.Close() - assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("map_foo", "3.14")) + require.NoError(t, mw.SetBoundary(boundary)) + require.NoError(t, mw.WriteField("map_foo", "3.14")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -617,7 +618,7 @@ func createFormMultipartRequestForMapFail(t *testing.T) *http.Request { func TestBindingFormPost(t *testing.T) { req := createFormPostRequest(t) var obj FooBarStruct - assert.NoError(t, FormPost.Bind(req, &obj)) + require.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "form-urlencoded", FormPost.Name()) assert.Equal(t, "bar", obj.Foo) @@ -627,7 +628,7 @@ func TestBindingFormPost(t *testing.T) { func TestBindingDefaultValueFormPost(t *testing.T) { req := createDefaultFormPostRequest(t) var obj FooDefaultBarStruct - assert.NoError(t, FormPost.Bind(req, &obj)) + require.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "hello", obj.Bar) @@ -637,22 +638,22 @@ func TestBindingFormPostForMap(t *testing.T) { req := createFormPostRequestForMap(t) var obj FooStructForMapType err := FormPost.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) + require.NoError(t, err) + assert.InDelta(t, float64(123), obj.MapFoo["bar"].(float64), 0.01) } func TestBindingFormPostForMapFail(t *testing.T) { req := createFormPostRequestForMapFail(t) var obj FooStructForMapType err := FormPost.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestBindingFormFilesMultipart(t *testing.T) { req := createFormFilesMultipartRequest(t) var obj FooBarFileStruct err := FormMultipart.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) // file from os f, _ := os.Open("form.go") @@ -664,9 +665,9 @@ func TestBindingFormFilesMultipart(t *testing.T) { defer mf.Close() fileExpect, _ := io.ReadAll(mf) - assert.Equal(t, FormMultipart.Name(), "multipart/form-data") - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "multipart/form-data", FormMultipart.Name()) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) assert.Equal(t, fileExpect, fileActual) } @@ -674,13 +675,13 @@ func TestBindingFormFilesMultipartFail(t *testing.T) { req := createFormFilesMultipartRequestFail(t) var obj FooBarFileFailStruct err := FormMultipart.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestBindingFormMultipart(t *testing.T) { req := createFormMultipartRequest(t) var obj FooBarStruct - assert.NoError(t, FormMultipart.Bind(req, &obj)) + require.NoError(t, FormMultipart.Bind(req, &obj)) assert.Equal(t, "multipart/form-data", FormMultipart.Name()) assert.Equal(t, "bar", obj.Foo) @@ -691,17 +692,17 @@ func TestBindingFormMultipartForMap(t *testing.T) { req := createFormMultipartRequestForMap(t) var obj FooStructForMapType err := FormMultipart.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) + require.NoError(t, err) + assert.InDelta(t, float64(123), obj.MapFoo["bar"].(float64), 0.01) assert.Equal(t, "thinkerou", obj.MapFoo["name"].(string)) - assert.Equal(t, float64(3.14), obj.MapFoo["pai"].(float64)) + assert.InDelta(t, float64(3.14), obj.MapFoo["pai"].(float64), 0.01) } func TestBindingFormMultipartForMapFail(t *testing.T) { req := createFormMultipartRequestForMapFail(t) var obj FooStructForMapType err := FormMultipart.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestBindingProtoBuf(t *testing.T) { @@ -732,7 +733,7 @@ func TestValidationFails(t *testing.T) { var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) err := JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestValidationDisabled(t *testing.T) { @@ -743,7 +744,7 @@ func TestValidationDisabled(t *testing.T) { var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) err := JSON.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) } func TestRequiredSucceeds(t *testing.T) { @@ -754,7 +755,7 @@ func TestRequiredSucceeds(t *testing.T) { var obj HogeStruct req := requestWithBody("POST", "/", `{"hoge": 0}`) err := JSON.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) } func TestRequiredFails(t *testing.T) { @@ -765,7 +766,7 @@ func TestRequiredFails(t *testing.T) { var obj HogeStruct req := requestWithBody("POST", "/", `{"boen": 0}`) err := JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestHeaderBinding(t *testing.T) { @@ -779,7 +780,7 @@ func TestHeaderBinding(t *testing.T) { var theader tHeader req := requestWithBody("GET", "/", "") req.Header.Add("limit", "1000") - assert.NoError(t, h.Bind(req, &theader)) + require.NoError(t, h.Bind(req, &theader)) assert.Equal(t, 1000, theader.Limit) req = requestWithBody("GET", "/", "") @@ -790,7 +791,7 @@ func TestHeaderBinding(t *testing.T) { } err := h.Bind(req, &failStruct{}) - assert.Error(t, err) + require.Error(t, err) } func TestUriBinding(t *testing.T) { @@ -803,14 +804,14 @@ func TestUriBinding(t *testing.T) { var tag Tag m := make(map[string][]string) m["name"] = []string{"thinkerou"} - assert.NoError(t, b.BindUri(m, &tag)) + require.NoError(t, b.BindUri(m, &tag)) assert.Equal(t, "thinkerou", tag.Name) type NotSupportStruct struct { Name map[string]any `uri:"name"` } var not NotSupportStruct - assert.Error(t, b.BindUri(m, ¬)) + require.Error(t, b.BindUri(m, ¬)) assert.Equal(t, map[string]any(nil), not.Name) } @@ -831,9 +832,9 @@ func TestUriInnerBinding(t *testing.T) { } var tag Tag - assert.NoError(t, Uri.BindUri(m, &tag)) - assert.Equal(t, tag.Name, expectedName) - assert.Equal(t, tag.S.Age, expectedAge) + require.NoError(t, Uri.BindUri(m, &tag)) + assert.Equal(t, expectedName, tag.Name) + assert.Equal(t, expectedAge, tag.S.Age) } func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, badBody string) { @@ -846,7 +847,7 @@ func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, ba req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 1, obj.Page) assert.Equal(t, 2, obj.Size) assert.Equal(t, "test-appkey", obj.Appkey) @@ -862,14 +863,14 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo", obj.Bar) obj = FooBarStruct{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badBody string) { @@ -882,14 +883,14 @@ func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badB req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "hello", obj.Bar) obj = FooDefaultBarStruct{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestFormBindingFail(t *testing.T) { @@ -899,18 +900,18 @@ func TestFormBindingFail(t *testing.T) { obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestFormBindingMultipartFail(t *testing.T) { obj := FooBarStruct{} req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar")) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary") _, err = req.MultipartReader() - assert.NoError(t, err) + require.NoError(t, err) err = Form.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestFormPostBindingFail(t *testing.T) { @@ -920,7 +921,7 @@ func TestFormPostBindingFail(t *testing.T) { obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestFormMultipartBindingFail(t *testing.T) { @@ -930,7 +931,7 @@ func TestFormMultipartBindingFail(t *testing.T) { obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) { @@ -944,7 +945,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, int64(1510675200), obj.TimeFoo.Unix()) assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String()) assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix()) @@ -955,7 +956,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, body, badBody string) { @@ -968,12 +969,12 @@ func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, bo req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) obj = FooStructForTimeTypeNotUnixFormat{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { @@ -986,12 +987,12 @@ func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) obj = FooStructForTimeTypeNotFormat{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) { @@ -1004,12 +1005,12 @@ func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) obj = FooStructForTimeTypeFailFormat{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) { @@ -1022,12 +1023,12 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) obj = FooStructForTimeTypeFailLocation{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBody string) { @@ -1040,7 +1041,7 @@ func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBo req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Nil(t, obj.Foo) } @@ -1055,13 +1056,13 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "", obj.TestName) obj = InvalidNameType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) { @@ -1074,12 +1075,12 @@ func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badB req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) obj = InvalidNameMapType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) { @@ -1094,17 +1095,17 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s case "Slice": obj := FooStructForSliceType{} err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, []int{1, 2}, obj.SliceFoo) obj = FooStructForSliceType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) case "Struct": obj := FooStructForStructType{} err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, struct { Idx int "form:\"idx\"" @@ -1113,7 +1114,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s case "StructPointer": obj := FooStructForStructPointerType{} err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, struct { Name string "form:\"name\"" @@ -1122,33 +1123,33 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s case "Map": obj := FooStructForMapType{} err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) + require.NoError(t, err) + assert.InDelta(t, float64(123), obj.MapFoo["bar"].(float64), 0.01) case "SliceMap": obj := FooStructForSliceMapType{} err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) case "Ptr": obj := FooStructForStringPtrType{} err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Nil(t, obj.PtrFoo) assert.Equal(t, "test", *obj.PtrBar) obj = FooStructForStringPtrType{} obj.PtrBar = new(string) err = b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "test", *obj.PtrBar) objErr := FooStructForMapPtrType{} err = b.Bind(req, &objErr) - assert.Error(t, err) + require.Error(t, err) obj = FooStructForStringPtrType{} req = requestWithBody(method, badPath, badBody) err = b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } } @@ -1162,7 +1163,7 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo", obj.Bar) } @@ -1177,7 +1178,7 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody string) { @@ -1190,7 +1191,7 @@ func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1199,13 +1200,13 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1214,12 +1215,12 @@ func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, ba var obj1 []FooStruct req := requestWithBody("POST", path, body) err := b.Bind(req, &obj1) - assert.NoError(t, err) + require.NoError(t, err) var obj2 []FooStruct req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj2) - assert.Error(t, err) + require.Error(t, err) } func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) { @@ -1229,7 +1230,7 @@ func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badB req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, obj) assert.Len(t, obj, 2) assert.Equal(t, "bar", obj["foo"]) @@ -1239,13 +1240,13 @@ func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badB obj = make(map[string]string) req = requestWithBody("POST", badPath, badBody) err = b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } objInt := make(map[string]int) req = requestWithBody("POST", path, body) err = b.Bind(req, &objInt) - assert.Error(t, err) + require.Error(t, err) } func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1255,16 +1256,16 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body req := requestWithBody("POST", path, body) EnableDecoderUseNumber = true err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) // we hope it is int64(123) v, e := obj.Foo.(json.Number).Int64() - assert.NoError(t, e) + require.NoError(t, e) assert.Equal(t, int64(123), v) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1274,15 +1275,15 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod req := requestWithBody("POST", path, body) EnableDecoderUseNumber = false err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) // it will return float64(123) if not use EnableDecoderUseNumber // maybe it is not hoped - assert.Equal(t, float64(123), obj.Foo) + assert.InDelta(t, float64(123), obj.Foo, 0.01) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath, body, badBody string) { @@ -1294,13 +1295,13 @@ func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath obj := FooStructDisallowUnknownFields{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStructDisallowUnknownFields{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "what") } @@ -1310,13 +1311,13 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) assert.Equal(t, "", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1326,14 +1327,14 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "yes", *obj.Label) obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } type hook struct{} @@ -1358,28 +1359,28 @@ func TestPlainBinding(t *testing.T) { var s string req := requestWithBody("POST", "/", "test string") - assert.NoError(t, p.Bind(req, &s)) - assert.Equal(t, s, "test string") + require.NoError(t, p.Bind(req, &s)) + assert.Equal(t, "test string", s) var bs []byte req = requestWithBody("POST", "/", "test []byte") - assert.NoError(t, p.Bind(req, &bs)) + require.NoError(t, p.Bind(req, &bs)) assert.Equal(t, bs, []byte("test []byte")) var i int req = requestWithBody("POST", "/", "test fail") - assert.Error(t, p.Bind(req, &i)) + require.Error(t, p.Bind(req, &i)) req = requestWithBody("POST", "/", "") req.Body = &failRead{} - assert.Error(t, p.Bind(req, &s)) + require.Error(t, p.Bind(req, &s)) req = requestWithBody("POST", "/", "") - assert.Nil(t, p.Bind(req, nil)) + require.NoError(t, p.Bind(req, nil)) var ptr *string req = requestWithBody("POST", "/", "") - assert.Nil(t, p.Bind(req, ptr)) + require.NoError(t, p.Bind(req, ptr)) } func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1391,20 +1392,20 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body req.Body = io.NopCloser(&hook{}) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) invalidobj := FooStruct{} req.Body = io.NopCloser(strings.NewReader(`{"msg":"hello"}`)) req.Header.Add("Content-Type", MIMEPROTOBUF) err = b.Bind(req, &invalidobj) - assert.Error(t, err) - assert.Equal(t, err.Error(), "obj is not ProtoMessage") + require.Error(t, err) + assert.Equal(t, "obj is not ProtoMessage", err.Error()) obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func requestWithBody(method, path, body string) (req *http.Request) { diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index afd51f9d..9ea0895c 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -15,6 +15,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMappingBaseTypes(t *testing.T) { @@ -59,7 +60,7 @@ func TestMappingBaseTypes(t *testing.T) { field := val.Elem().Type().Field(0) _, err := mapping(val, emptyField, formSource{field.Name: {tt.form}}, "form") - assert.NoError(t, err, testName) + require.NoError(t, err, testName) actual := val.Elem().Field(0).Interface() assert.Equal(t, tt.expect, actual, testName) @@ -73,7 +74,7 @@ func TestMappingDefault(t *testing.T) { Array [1]int `form:",default=9"` } err := mappingByPtr(&s, formSource{}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 9, s.Int) assert.Equal(t, []int{9}, s.Slice) @@ -85,7 +86,7 @@ func TestMappingSkipField(t *testing.T) { A int } err := mappingByPtr(&s, formSource{}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 0, s.A) } @@ -96,7 +97,7 @@ func TestMappingIgnoreField(t *testing.T) { B int `form:"-"` } err := mappingByPtr(&s, formSource{"A": {"9"}, "B": {"9"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 9, s.A) assert.Equal(t, 0, s.B) @@ -108,7 +109,7 @@ func TestMappingUnexportedField(t *testing.T) { b int `form:"b"` } err := mappingByPtr(&s, formSource{"a": {"9"}, "b": {"9"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 9, s.A) assert.Equal(t, 0, s.b) @@ -119,7 +120,7 @@ func TestMappingPrivateField(t *testing.T) { f int `form:"field"` } err := mappingByPtr(&s, formSource{"field": {"6"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 0, s.f) } @@ -129,7 +130,7 @@ func TestMappingUnknownFieldType(t *testing.T) { } err := mappingByPtr(&s, formSource{"U": {"unknown"}}, "form") - assert.Error(t, err) + require.Error(t, err) assert.Equal(t, errUnknownType, err) } @@ -138,7 +139,7 @@ func TestMappingURI(t *testing.T) { F int `uri:"field"` } err := mapURI(&s, map[string][]string{"field": {"6"}}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 6, s.F) } @@ -147,7 +148,7 @@ func TestMappingForm(t *testing.T) { F int `form:"field"` } err := mapForm(&s, map[string][]string{"field": {"6"}}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 6, s.F) } @@ -156,7 +157,7 @@ func TestMapFormWithTag(t *testing.T) { F int `externalTag:"field"` } err := MapFormWithTag(&s, map[string][]string{"field": {"6"}}, "externalTag") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 6, s.F) } @@ -171,7 +172,7 @@ func TestMappingTime(t *testing.T) { var err error time.Local, err = time.LoadLocation("Europe/Berlin") - assert.NoError(t, err) + require.NoError(t, err) err = mapForm(&s, map[string][]string{ "Time": {"2019-01-20T16:02:58Z"}, @@ -180,7 +181,7 @@ func TestMappingTime(t *testing.T) { "CSTTime": {"2019-01-20"}, "UTCTime": {"2019-01-20"}, }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "2019-01-20 16:02:58 +0000 UTC", s.Time.String()) assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String()) @@ -195,14 +196,14 @@ func TestMappingTime(t *testing.T) { Time time.Time `time_location:"wrong"` } err = mapForm(&wrongLoc, map[string][]string{"Time": {"2019-01-20T16:02:58Z"}}) - assert.Error(t, err) + require.Error(t, err) // wrong time value var wrongTime struct { Time time.Time } err = mapForm(&wrongTime, map[string][]string{"Time": {"wrong"}}) - assert.Error(t, err) + require.Error(t, err) } func TestMappingTimeDuration(t *testing.T) { @@ -212,12 +213,12 @@ func TestMappingTimeDuration(t *testing.T) { // ok err := mappingByPtr(&s, formSource{"D": {"5s"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 5*time.Second, s.D) // error err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form") - assert.Error(t, err) + require.Error(t, err) } func TestMappingSlice(t *testing.T) { @@ -227,17 +228,17 @@ func TestMappingSlice(t *testing.T) { // default value err := mappingByPtr(&s, formSource{}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, []int{9}, s.Slice) // ok err = mappingByPtr(&s, formSource{"slice": {"3", "4"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, []int{3, 4}, s.Slice) // error err = mappingByPtr(&s, formSource{"slice": {"wrong"}}, "form") - assert.Error(t, err) + require.Error(t, err) } func TestMappingArray(t *testing.T) { @@ -247,20 +248,20 @@ func TestMappingArray(t *testing.T) { // wrong default err := mappingByPtr(&s, formSource{}, "form") - assert.Error(t, err) + require.Error(t, err) // ok err = mappingByPtr(&s, formSource{"array": {"3", "4"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, [2]int{3, 4}, s.Array) // error - not enough vals err = mappingByPtr(&s, formSource{"array": {"3"}}, "form") - assert.Error(t, err) + require.Error(t, err) // error - wrong value err = mappingByPtr(&s, formSource{"array": {"wrong"}}, "form") - assert.Error(t, err) + require.Error(t, err) } func TestMappingStructField(t *testing.T) { @@ -271,7 +272,7 @@ func TestMappingStructField(t *testing.T) { } err := mappingByPtr(&s, formSource{"J": {`{"I": 9}`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 9, s.J.I) } @@ -289,20 +290,20 @@ func TestMappingPtrField(t *testing.T) { // With 0 items. var req0 ptrRequest err = mappingByPtr(&req0, formSource{}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Empty(t, req0.Items) // With 1 item. var req1 ptrRequest err = mappingByPtr(&req1, formSource{"items": {`{"key": 1}`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, req1.Items, 1) assert.EqualValues(t, 1, req1.Items[0].Key) // With 2 items. var req2 ptrRequest err = mappingByPtr(&req2, formSource{"items": {`{"key": 1}`, `{"key": 2}`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, req2.Items, 2) assert.EqualValues(t, 1, req2.Items[0].Key) assert.EqualValues(t, 2, req2.Items[1].Key) @@ -314,7 +315,7 @@ func TestMappingMapField(t *testing.T) { } err := mappingByPtr(&s, formSource{"M": {`{"one": 1}`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, map[string]int{"one": 1}, s.M) } @@ -325,7 +326,7 @@ func TestMappingIgnoredCircularRef(t *testing.T) { var s S err := mappingByPtr(&s, formSource{}, "form") - assert.NoError(t, err) + require.NoError(t, err) } type customUnmarshalParamHex int @@ -344,7 +345,7 @@ func TestMappingCustomUnmarshalParamHexWithFormTag(t *testing.T) { Foo customUnmarshalParamHex `form:"foo"` } err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 245, s.Foo) } @@ -354,7 +355,7 @@ func TestMappingCustomUnmarshalParamHexWithURITag(t *testing.T) { Foo customUnmarshalParamHex `uri:"foo"` } err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "uri") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 245, s.Foo) } @@ -381,7 +382,7 @@ func TestMappingCustomStructTypeWithFormTag(t *testing.T) { FileData customUnmarshalParamType `form:"data"` } err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "file", s.FileData.Protocol) assert.EqualValues(t, "/foo", s.FileData.Path) @@ -393,7 +394,7 @@ func TestMappingCustomStructTypeWithURITag(t *testing.T) { FileData customUnmarshalParamType `uri:"data"` } err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "file", s.FileData.Protocol) assert.EqualValues(t, "/foo", s.FileData.Path) @@ -405,7 +406,7 @@ func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) { FileData *customUnmarshalParamType `form:"data"` } err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "file", s.FileData.Protocol) assert.EqualValues(t, "/foo", s.FileData.Path) @@ -417,7 +418,7 @@ func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { FileData *customUnmarshalParamType `uri:"data"` } err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "file", s.FileData.Protocol) assert.EqualValues(t, "/foo", s.FileData.Path) @@ -442,7 +443,7 @@ func TestMappingCustomSliceUri(t *testing.T) { FileData customPath `uri:"path"` } err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "bar", s.FileData[0]) assert.EqualValues(t, "foo", s.FileData[1]) @@ -453,7 +454,7 @@ func TestMappingCustomSliceForm(t *testing.T) { FileData customPath `form:"path"` } err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "bar", s.FileData[0]) assert.EqualValues(t, "foo", s.FileData[1]) @@ -492,7 +493,7 @@ func TestMappingCustomArrayUri(t *testing.T) { } val := `664a062ac74a8ad104e0e80f` err := mappingByPtr(&s, formSource{"id": {val}}, "uri") - assert.NoError(t, err) + require.NoError(t, err) expected, _ := convertTo(val) assert.EqualValues(t, expected, s.FileData) @@ -504,7 +505,7 @@ func TestMappingCustomArrayForm(t *testing.T) { } val := `664a062ac74a8ad104e0e80f` err := mappingByPtr(&s, formSource{"id": {val}}, "form") - assert.NoError(t, err) + require.NoError(t, err) expected, _ := convertTo(val) assert.EqualValues(t, expected, s.FileData) diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go index 4e97c0f0..9782b81d 100644 --- a/binding/multipart_form_mapping_test.go +++ b/binding/multipart_form_mapping_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestFormMultipartBindingBindOneFile(t *testing.T) { @@ -27,7 +28,7 @@ func TestFormMultipartBindingBindOneFile(t *testing.T) { req := createRequestMultipartFiles(t, file) err := FormMultipart.Bind(req, &s) - assert.NoError(t, err) + require.NoError(t, err) assertMultipartFileHeader(t, &s.FileValue, file) assertMultipartFileHeader(t, s.FilePtr, file) @@ -53,7 +54,7 @@ func TestFormMultipartBindingBindTwoFiles(t *testing.T) { req := createRequestMultipartFiles(t, files...) err := FormMultipart.Bind(req, &s) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, s.SliceValues, len(files)) assert.Len(t, s.SlicePtrs, len(files)) @@ -90,7 +91,7 @@ func TestFormMultipartBindingBindError(t *testing.T) { } { req := createRequestMultipartFiles(t, files...) err := FormMultipart.Bind(req, tt.s) - assert.Error(t, err) + require.Error(t, err) } } @@ -106,17 +107,17 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request mw := multipart.NewWriter(&body) for _, file := range files { fw, err := mw.CreateFormFile(file.Fieldname, file.Filename) - assert.NoError(t, err) + require.NoError(t, err) n, err := fw.Write(file.Content) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, len(file.Content), n) } err := mw.Close() - assert.NoError(t, err) + require.NoError(t, err) req, err := http.NewRequest("POST", "/", &body) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary()) return req @@ -127,12 +128,12 @@ func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file test assert.Equal(t, int64(len(file.Content)), fh.Size) fl, err := fh.Open() - assert.NoError(t, err) + require.NoError(t, err) body, err := io.ReadAll(fl) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, string(file.Content), string(body)) err = fl.Close() - assert.NoError(t, err) + require.NoError(t, err) } diff --git a/binding/validate_test.go b/binding/validate_test.go index 1fc15ff0..c9bbe601 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -11,6 +11,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type testInterface interface { @@ -113,10 +114,10 @@ func TestValidateNoValidationValues(t *testing.T) { test := createNoValidationValues() empty := structNoValidationValues{} - assert.Nil(t, validate(test)) - assert.Nil(t, validate(&test)) - assert.Nil(t, validate(empty)) - assert.Nil(t, validate(&empty)) + require.NoError(t, validate(test)) + require.NoError(t, validate(&test)) + require.NoError(t, validate(empty)) + require.NoError(t, validate(&empty)) assert.Equal(t, origin, test) } @@ -163,8 +164,8 @@ func TestValidateNoValidationPointers(t *testing.T) { //assert.Nil(t, validate(test)) //assert.Nil(t, validate(&test)) - assert.Nil(t, validate(empty)) - assert.Nil(t, validate(&empty)) + require.NoError(t, validate(empty)) + require.NoError(t, validate(&empty)) //assert.Equal(t, origin, test) } @@ -173,22 +174,22 @@ type Object map[string]any func TestValidatePrimitives(t *testing.T) { obj := Object{"foo": "bar", "bar": 1} - assert.NoError(t, validate(obj)) - assert.NoError(t, validate(&obj)) + require.NoError(t, validate(obj)) + require.NoError(t, validate(&obj)) assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj) obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}} - assert.NoError(t, validate(obj2)) - assert.NoError(t, validate(&obj2)) + require.NoError(t, validate(obj2)) + require.NoError(t, validate(&obj2)) nu := 10 - assert.NoError(t, validate(nu)) - assert.NoError(t, validate(&nu)) + require.NoError(t, validate(nu)) + require.NoError(t, validate(&nu)) assert.Equal(t, 10, nu) str := "value" - assert.NoError(t, validate(str)) - assert.NoError(t, validate(&str)) + require.NoError(t, validate(str)) + require.NoError(t, validate(&str)) assert.Equal(t, "value", str) } @@ -212,8 +213,8 @@ func TestValidateAndModifyStruct(t *testing.T) { s := structModifyValidation{Integer: 1} errs := validate(&s) - assert.Nil(t, errs) - assert.Equal(t, s, structModifyValidation{Integer: 0}) + require.NoError(t, errs) + assert.Equal(t, structModifyValidation{Integer: 0}, s) } // structCustomValidation is a helper struct we use to check that @@ -239,14 +240,14 @@ func TestValidatorEngine(t *testing.T) { err := engine.RegisterValidation("notone", notOne) // Check that we can register custom validation without error - assert.Nil(t, err) + require.NoError(t, err) // Create an instance which will fail validation withOne := structCustomValidation{Integer: 1} errs := validate(withOne) // Check that we got back non-nil errs - assert.NotNil(t, errs) + require.Error(t, errs) // Check that the error matches expectation - assert.Error(t, errs, "", "", "notone") + require.Error(t, errs, "", "", "notone") } diff --git a/context_test.go b/context_test.go index 8bbf2700..66190b30 100644 --- a/context_test.go +++ b/context_test.go @@ -27,6 +27,7 @@ import ( "github.com/gin-gonic/gin/binding" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) @@ -74,20 +75,18 @@ func TestContextFormFile(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") - if assert.NoError(t, err) { - _, err = w.Write([]byte("test")) - assert.NoError(t, err) - } + require.NoError(t, err) + _, err = w.Write([]byte("test")) + require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") - if assert.NoError(t, err) { - assert.Equal(t, "test", f.Filename) - } + require.NoError(t, err) + assert.Equal(t, "test", f.Filename) - assert.NoError(t, c.SaveUploadedFile(f, "test")) + require.NoError(t, c.SaveUploadedFile(f, "test")) } func TestContextFormFileFailed(t *testing.T) { @@ -99,29 +98,27 @@ func TestContextFormFileFailed(t *testing.T) { c.Request.Header.Set("Content-Type", mw.FormDataContentType()) c.engine.MaxMultipartMemory = 8 << 20 f, err := c.FormFile("file") - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, f) } func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) - assert.NoError(t, mw.WriteField("foo", "bar")) + require.NoError(t, mw.WriteField("foo", "bar")) w, err := mw.CreateFormFile("file", "test") - if assert.NoError(t, err) { - _, err = w.Write([]byte("test")) - assert.NoError(t, err) - } + require.NoError(t, err) + _, err = w.Write([]byte("test")) + require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.MultipartForm() - if assert.NoError(t, err) { - assert.NotNil(t, f) - } + require.NoError(t, err) + assert.NotNil(t, f) - assert.NoError(t, c.SaveUploadedFile(f.File["file"][0], "test")) + require.NoError(t, c.SaveUploadedFile(f.File["file"][0], "test")) } func TestSaveUploadedOpenFailed(t *testing.T) { @@ -136,27 +133,25 @@ func TestSaveUploadedOpenFailed(t *testing.T) { f := &multipart.FileHeader{ Filename: "file", } - assert.Error(t, c.SaveUploadedFile(f, "test")) + require.Error(t, c.SaveUploadedFile(f, "test")) } func TestSaveUploadedCreateFailed(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") - if assert.NoError(t, err) { - _, err = w.Write([]byte("test")) - assert.NoError(t, err) - } + require.NoError(t, err) + _, err = w.Write([]byte("test")) + require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") - if assert.NoError(t, err) { - assert.Equal(t, "test", f.Filename) - } + require.NoError(t, err) + assert.Equal(t, "test", f.Filename) - assert.Error(t, c.SaveUploadedFile(f, "/")) + require.Error(t, c.SaveUploadedFile(f, "/")) } func TestContextReset(t *testing.T) { @@ -174,10 +169,10 @@ func TestContextReset(t *testing.T) { assert.False(t, c.IsAborted()) assert.Nil(t, c.Keys) assert.Nil(t, c.Accepted) - assert.Len(t, c.Errors, 0) + assert.Empty(t, c.Errors) assert.Empty(t, c.Errors.Errors()) assert.Empty(t, c.Errors.ByType(ErrorTypeAny)) - assert.Len(t, c.Params, 0) + assert.Empty(t, c.Params) assert.EqualValues(t, c.index, -1) assert.Equal(t, c.Writer.(*responseWriter), &c.writermem) } @@ -230,13 +225,13 @@ func TestContextSetGetValues(t *testing.T) { var a any = 1 c.Set("intInterface", a) - assert.Exactly(t, c.MustGet("string").(string), "this is a string") + assert.Exactly(t, "this is a string", c.MustGet("string").(string)) assert.Exactly(t, c.MustGet("int32").(int32), int32(-42)) - assert.Exactly(t, c.MustGet("int64").(int64), int64(42424242424242)) - assert.Exactly(t, c.MustGet("uint64").(uint64), uint64(42)) - assert.Exactly(t, c.MustGet("float32").(float32), float32(4.2)) - assert.Exactly(t, c.MustGet("float64").(float64), 4.2) - assert.Exactly(t, c.MustGet("intInterface").(int), 1) + 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) + assert.InDelta(t, 4.2, c.MustGet("float64").(float64), 0.01) + assert.Exactly(t, 1, c.MustGet("intInterface").(int)) } func TestContextGetString(t *testing.T) { @@ -278,7 +273,7 @@ func TestContextGetUint64(t *testing.T) { func TestContextGetFloat64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("float64", 4.2) - assert.Equal(t, 4.2, c.GetFloat64("float64")) + assert.InDelta(t, 4.2, c.GetFloat64("float64"), 0.01) } func TestContextGetTime(t *testing.T) { @@ -344,12 +339,12 @@ func TestContextCopy(t *testing.T) { assert.Nil(t, cp.writermem.ResponseWriter) assert.Equal(t, &cp.writermem, cp.Writer.(*responseWriter)) assert.Equal(t, cp.Request, c.Request) - assert.Equal(t, cp.index, abortIndex) + assert.Equal(t, abortIndex, cp.index) assert.Equal(t, cp.Keys, c.Keys) assert.Equal(t, cp.engine, c.engine) assert.Equal(t, cp.Params, c.Params) cp.Set("foo", "notBar") - assert.False(t, cp.Keys["foo"] == c.Keys["foo"]) + assert.NotEqual(t, cp.Keys["foo"], c.Keys["foo"]) assert.Equal(t, cp.fullPath, c.fullPath) } @@ -366,7 +361,7 @@ func TestContextHandlerNames(t *testing.T) { names := c.HandlerNames() - assert.True(t, len(names) == 4) + assert.Len(t, names, 4) for _, name := range names { assert.Regexp(t, `^(.*/vendor/)?(github\.com/gin-gonic/gin\.){1}(TestContextHandlerNames\.func.*){0,1}(handlerNameTest.*){0,1}`, name) } @@ -425,7 +420,7 @@ func TestContextQuery(t *testing.T) { func TestContextInitQueryCache(t *testing.T) { validURL, err := url.Parse("https://github.com/gin-gonic/gin/pull/3969?key=value&otherkey=othervalue") - assert.Nil(t, err) + require.NoError(t, err) tests := []struct { testName string @@ -531,7 +526,7 @@ func TestContextQueryAndPostForm(t *testing.T) { Both string `form:"both"` Array []string `form:"array[]"` } - assert.NoError(t, c.Bind(&obj)) + require.NoError(t, c.Bind(&obj)) assert.Equal(t, "bar", obj.Foo, "bar") assert.Equal(t, "main", obj.ID, "main") assert.Equal(t, 11, obj.Page, 11) @@ -548,10 +543,10 @@ func TestContextQueryAndPostForm(t *testing.T) { assert.Equal(t, "second", values[1]) values = c.QueryArray("nokey") - assert.Equal(t, 0, len(values)) + assert.Empty(t, values) values = c.QueryArray("both") - assert.Equal(t, 1, len(values)) + assert.Len(t, values, 1) assert.Equal(t, "GET", values[0]) dicts, ok := c.GetQueryMap("ids") @@ -561,22 +556,22 @@ func TestContextQueryAndPostForm(t *testing.T) { dicts, ok = c.GetQueryMap("nokey") assert.False(t, ok) - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) dicts, ok = c.GetQueryMap("both") assert.False(t, ok) - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) dicts, ok = c.GetQueryMap("array") assert.False(t, ok) - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) dicts = c.QueryMap("ids") assert.Equal(t, "hi", dicts["a"]) assert.Equal(t, "3.14", dicts["b"]) dicts = c.QueryMap("nokey") - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) } func TestContextPostFormMultipart(t *testing.T) { @@ -594,7 +589,7 @@ func TestContextPostFormMultipart(t *testing.T) { TimeLocation time.Time `form:"time_location" time_format:"02/01/2006 15:04" time_location:"Asia/Tokyo"` BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"` } - assert.NoError(t, c.Bind(&obj)) + require.NoError(t, c.Bind(&obj)) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "10", obj.Bar) assert.Equal(t, 10, obj.BarAsInt) @@ -648,10 +643,10 @@ func TestContextPostFormMultipart(t *testing.T) { assert.Equal(t, "second", values[1]) values = c.PostFormArray("nokey") - assert.Equal(t, 0, len(values)) + assert.Empty(t, values) values = c.PostFormArray("foo") - assert.Equal(t, 1, len(values)) + assert.Len(t, values, 1) assert.Equal(t, "bar", values[0]) dicts, ok := c.GetPostFormMap("names") @@ -661,14 +656,14 @@ func TestContextPostFormMultipart(t *testing.T) { dicts, ok = c.GetPostFormMap("nokey") assert.False(t, ok) - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) dicts = c.PostFormMap("names") assert.Equal(t, "thinkerou", dicts["a"]) assert.Equal(t, "tianou", dicts["b"]) dicts = c.PostFormMap("nokey") - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) } func TestContextSetCookie(t *testing.T) { @@ -693,7 +688,7 @@ func TestContextGetCookie(t *testing.T) { assert.Equal(t, "gin", cookie) _, err := c.Cookie("nokey") - assert.Error(t, err) + require.Error(t, err) } func TestContextBodyAllowedForStatus(t *testing.T) { @@ -798,7 +793,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, w.Header().Get("Content-Type"), "application/vnd.api+json") + assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSON @@ -1160,7 +1155,7 @@ func TestContextRenderProtoBuf(t *testing.T) { c.ProtoBuf(http.StatusCreated, data) protoData, err := proto.Marshal(data) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, string(protoData), w.Body.String()) @@ -1321,7 +1316,7 @@ func TestContextNegotiationNotSupport(t *testing.T) { }) assert.Equal(t, http.StatusNotAcceptable, w.Code) - assert.Equal(t, c.index, abortIndex) + assert.Equal(t, abortIndex, c.index) assert.True(t, c.IsAborted()) } @@ -1349,23 +1344,23 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "*/*") - assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") - assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") - assert.Equal(t, c.NegotiateFormat("application/*"), "application/*") - assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON) - assert.Equal(t, c.NegotiateFormat(MIMEXML), MIMEXML) - assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) + assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) + assert.Equal(t, "text/*", c.NegotiateFormat("text/*")) + assert.Equal(t, "application/*", c.NegotiateFormat("application/*")) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) + assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML)) + assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML)) c, _ = CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "text/*") - assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") - assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") - assert.Equal(t, c.NegotiateFormat("application/*"), "") - assert.Equal(t, c.NegotiateFormat(MIMEJSON), "") - assert.Equal(t, c.NegotiateFormat(MIMEXML), "") - assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) + assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) + assert.Equal(t, "text/*", c.NegotiateFormat("text/*")) + assert.Equal(t, "", c.NegotiateFormat("application/*")) + assert.Equal(t, "", c.NegotiateFormat(MIMEJSON)) + assert.Equal(t, "", c.NegotiateFormat(MIMEXML)) + assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML)) } func TestContextNegotiationFormatCustom(t *testing.T) { @@ -1444,7 +1439,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) { buf := new(bytes.Buffer) _, err := buf.ReadFrom(w.Body) - assert.NoError(t, err) + require.NoError(t, err) jsonStringBody := buf.String() assert.Equal(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) } @@ -1669,7 +1664,7 @@ func TestContextAutoBindJSON(t *testing.T) { Foo string `json:"foo"` Bar string `json:"bar"` } - assert.NoError(t, c.Bind(&obj)) + require.NoError(t, c.Bind(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Empty(t, c.Errors) @@ -1686,7 +1681,7 @@ func TestContextBindWithJSON(t *testing.T) { Foo string `json:"foo"` Bar string `json:"bar"` } - assert.NoError(t, c.BindJSON(&obj)) + require.NoError(t, c.BindJSON(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1707,7 +1702,7 @@ func TestContextBindWithXML(t *testing.T) { Foo string `xml:"foo"` Bar string `xml:"bar"` } - assert.NoError(t, c.BindXML(&obj)) + require.NoError(t, c.BindXML(&obj)) assert.Equal(t, "FOO", obj.Foo) assert.Equal(t, "BAR", obj.Bar) assert.Equal(t, 0, w.Body.Len()) @@ -1722,7 +1717,7 @@ func TestContextBindPlain(t *testing.T) { var s string - assert.NoError(t, c.BindPlain(&s)) + require.NoError(t, c.BindPlain(&s)) assert.Equal(t, "test string", s) assert.Equal(t, 0, w.Body.Len()) @@ -1732,7 +1727,7 @@ func TestContextBindPlain(t *testing.T) { var bs []byte - assert.NoError(t, c.BindPlain(&bs)) + require.NoError(t, c.BindPlain(&bs)) assert.Equal(t, []byte("test []byte"), bs) assert.Equal(t, 0, w.Body.Len()) } @@ -1752,7 +1747,7 @@ func TestContextBindHeader(t *testing.T) { Limit int `header:"limit"` } - assert.NoError(t, c.BindHeader(&testHeader)) + require.NoError(t, c.BindHeader(&testHeader)) assert.Equal(t, 8000, testHeader.Rate) assert.Equal(t, "music", testHeader.Domain) assert.Equal(t, 1000, testHeader.Limit) @@ -1769,7 +1764,7 @@ func TestContextBindWithQuery(t *testing.T) { Foo string `form:"foo"` Bar string `form:"bar"` } - assert.NoError(t, c.BindQuery(&obj)) + require.NoError(t, c.BindQuery(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1786,7 +1781,7 @@ func TestContextBindWithYAML(t *testing.T) { Foo string `yaml:"foo"` Bar string `yaml:"bar"` } - assert.NoError(t, c.BindYAML(&obj)) + require.NoError(t, c.BindYAML(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1803,7 +1798,7 @@ func TestContextBindWithTOML(t *testing.T) { Foo string `toml:"foo"` Bar string `toml:"bar"` } - assert.NoError(t, c.BindTOML(&obj)) + require.NoError(t, c.BindTOML(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1821,7 +1816,7 @@ func TestContextBadAutoBind(t *testing.T) { } assert.False(t, c.IsAborted()) - assert.Error(t, c.Bind(&obj)) + require.Error(t, c.Bind(&obj)) c.Writer.WriteHeaderNow() assert.Empty(t, obj.Bar) @@ -1839,7 +1834,7 @@ func TestContextAutoShouldBindJSON(t *testing.T) { Foo string `json:"foo"` Bar string `json:"bar"` } - assert.NoError(t, c.ShouldBind(&obj)) + require.NoError(t, c.ShouldBind(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Empty(t, c.Errors) @@ -1856,7 +1851,7 @@ func TestContextShouldBindWithJSON(t *testing.T) { Foo string `json:"foo"` Bar string `json:"bar"` } - assert.NoError(t, c.ShouldBindJSON(&obj)) + require.NoError(t, c.ShouldBindJSON(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1877,7 +1872,7 @@ func TestContextShouldBindWithXML(t *testing.T) { Foo string `xml:"foo"` Bar string `xml:"bar"` } - assert.NoError(t, c.ShouldBindXML(&obj)) + require.NoError(t, c.ShouldBindXML(&obj)) assert.Equal(t, "FOO", obj.Foo) assert.Equal(t, "BAR", obj.Bar) assert.Equal(t, 0, w.Body.Len()) @@ -1892,7 +1887,7 @@ func TestContextShouldBindPlain(t *testing.T) { var s string - assert.NoError(t, c.ShouldBindPlain(&s)) + require.NoError(t, c.ShouldBindPlain(&s)) assert.Equal(t, "test string", s) assert.Equal(t, 0, w.Body.Len()) // []byte @@ -1902,7 +1897,7 @@ func TestContextShouldBindPlain(t *testing.T) { var bs []byte - assert.NoError(t, c.ShouldBindPlain(&bs)) + require.NoError(t, c.ShouldBindPlain(&bs)) assert.Equal(t, []byte("test []byte"), bs) assert.Equal(t, 0, w.Body.Len()) } @@ -1922,7 +1917,7 @@ func TestContextShouldBindHeader(t *testing.T) { Limit int `header:"limit"` } - assert.NoError(t, c.ShouldBindHeader(&testHeader)) + require.NoError(t, c.ShouldBindHeader(&testHeader)) assert.Equal(t, 8000, testHeader.Rate) assert.Equal(t, "music", testHeader.Domain) assert.Equal(t, 1000, testHeader.Limit) @@ -1941,7 +1936,7 @@ func TestContextShouldBindWithQuery(t *testing.T) { Foo1 string `form:"Foo"` Bar1 string `form:"Bar"` } - assert.NoError(t, c.ShouldBindQuery(&obj)) + require.NoError(t, c.ShouldBindQuery(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo1", obj.Bar1) @@ -1960,7 +1955,7 @@ func TestContextShouldBindWithYAML(t *testing.T) { Foo string `yaml:"foo"` Bar string `yaml:"bar"` } - assert.NoError(t, c.ShouldBindYAML(&obj)) + require.NoError(t, c.ShouldBindYAML(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1977,7 +1972,7 @@ func TestContextShouldBindWithTOML(t *testing.T) { Foo string `toml:"foo"` Bar string `toml:"bar"` } - assert.NoError(t, c.ShouldBindTOML(&obj)) + require.NoError(t, c.ShouldBindTOML(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1995,7 +1990,7 @@ func TestContextBadAutoShouldBind(t *testing.T) { } assert.False(t, c.IsAborted()) - assert.Error(t, c.ShouldBind(&obj)) + require.Error(t, c.ShouldBind(&obj)) assert.Empty(t, obj.Bar) assert.Empty(t, obj.Foo) @@ -2056,10 +2051,10 @@ func TestContextShouldBindBodyWith(t *testing.T) { // When it binds to typeA and typeB, it finds the body is // not typeB but typeA. objA := typeA{} - assert.NoError(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) + require.NoError(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) assert.Equal(t, typeA{"FOO"}, objA) objB := typeB{} - assert.Error(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) + require.Error(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) assert.NotEqual(t, typeB{"BAR"}, objB) } // bodyB to typeA and typeB @@ -2072,10 +2067,10 @@ func TestContextShouldBindBodyWith(t *testing.T) { "POST", "http://example.com", bytes.NewBufferString(tt.bodyB), ) objA := typeA{} - assert.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) + require.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) assert.NotEqual(t, typeA{"FOO"}, objA) objB := typeB{} - assert.NoError(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) + require.NoError(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) assert.Equal(t, typeB{"BAR"}, objB) } } @@ -2124,22 +2119,22 @@ func TestContextShouldBindBodyWithJSON(t *testing.T) { objJSON := typeJSON{} if tt.bindingBody == binding.JSON { - assert.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{"FOO"}, objJSON) } if tt.bindingBody == binding.XML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } if tt.bindingBody == binding.YAML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } if tt.bindingBody == binding.TOML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } } @@ -2188,22 +2183,22 @@ func TestContextShouldBindBodyWithXML(t *testing.T) { objXML := typeXML{} if tt.bindingBody == binding.JSON { - assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + require.Error(t, c.ShouldBindBodyWithXML(&objXML)) assert.Equal(t, typeXML{}, objXML) } if tt.bindingBody == binding.XML { - assert.NoError(t, c.ShouldBindBodyWithXML(&objXML)) + require.NoError(t, c.ShouldBindBodyWithXML(&objXML)) assert.Equal(t, typeXML{"FOO"}, objXML) } if tt.bindingBody == binding.YAML { - assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + require.Error(t, c.ShouldBindBodyWithXML(&objXML)) assert.Equal(t, typeXML{}, objXML) } if tt.bindingBody == binding.TOML { - assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + require.Error(t, c.ShouldBindBodyWithXML(&objXML)) assert.Equal(t, typeXML{}, objXML) } } @@ -2253,22 +2248,22 @@ func TestContextShouldBindBodyWithYAML(t *testing.T) { // YAML belongs to a super collection of JSON, so JSON can be parsed by YAML if tt.bindingBody == binding.JSON { - assert.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) + require.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) assert.Equal(t, typeYAML{"FOO"}, objYAML) } if tt.bindingBody == binding.XML { - assert.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) + require.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) assert.Equal(t, typeYAML{}, objYAML) } if tt.bindingBody == binding.YAML { - assert.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) + require.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) assert.Equal(t, typeYAML{"FOO"}, objYAML) } if tt.bindingBody == binding.TOML { - assert.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) + require.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) assert.Equal(t, typeYAML{}, objYAML) } } @@ -2317,22 +2312,22 @@ func TestContextShouldBindBodyWithTOML(t *testing.T) { objTOML := typeTOML{} if tt.bindingBody == binding.JSON { - assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + require.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) assert.Equal(t, typeTOML{}, objTOML) } if tt.bindingBody == binding.XML { - assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + require.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) assert.Equal(t, typeTOML{}, objTOML) } if tt.bindingBody == binding.YAML { - assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + require.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) assert.Equal(t, typeTOML{}, objTOML) } if tt.bindingBody == binding.TOML { - assert.NoError(t, c.ShouldBindBodyWithTOML(&objTOML)) + require.NoError(t, c.ShouldBindBodyWithTOML(&objTOML)) assert.Equal(t, typeTOML{"FOO"}, objTOML) } } @@ -2387,27 +2382,27 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { if tt.bindingBody == binding.Plain { body := "" - assert.NoError(t, c.ShouldBindBodyWithPlain(&body)) - assert.Equal(t, body, "foo=FOO") + require.NoError(t, c.ShouldBindBodyWithPlain(&body)) + assert.Equal(t, "foo=FOO", body) } if tt.bindingBody == binding.JSON { - assert.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{"FOO"}, objJSON) } if tt.bindingBody == binding.XML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } if tt.bindingBody == binding.YAML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } if tt.bindingBody == binding.TOML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } } @@ -2416,10 +2411,10 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) - assert.NoError(t, c.Err()) + require.NoError(t, c.Err()) assert.Nil(t, c.Done()) ti, ok := c.Deadline() - assert.Equal(t, ti, time.Time{}) + assert.Equal(t, time.Time{}, ti) assert.False(t, ok) assert.Equal(t, c.Value(ContextRequestKey), c.Request) assert.Equal(t, c.Value(ContextKey), c) @@ -2468,7 +2463,7 @@ func TestContextGetRawData(t *testing.T) { c.Request.Header.Add("Content-Type", MIMEPOSTForm) data, err := c.GetRawData() - assert.Nil(t, err) + require.NoError(t, err) assert.Equal(t, "Fetch binary post data", string(data)) } @@ -2539,7 +2534,7 @@ func TestContextStream(t *testing.T) { }() _, err := w.Write([]byte("test")) - assert.NoError(t, err) + require.NoError(t, err) return stopStream }) @@ -2557,7 +2552,7 @@ func TestContextStreamWithClientGone(t *testing.T) { }() _, err := writer.Write([]byte("test")) - assert.NoError(t, err) + require.NoError(t, err) return true }) @@ -2685,7 +2680,7 @@ func TestContextWithFallbackErrFromRequestContext(t *testing.T) { // enable ContextWithFallback feature flag c.engine.ContextWithFallback = true - assert.Nil(t, c.Err()) + require.NoError(t, c.Err()) c2, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag @@ -2834,7 +2829,7 @@ func TestContextAddParam(t *testing.T) { c.AddParam(id, value) v, ok := c.Params.Get(id) - assert.Equal(t, ok, true) + assert.True(t, ok) assert.Equal(t, value, v) } diff --git a/debug_test.go b/debug_test.go index 1e576681..edf4bb12 100644 --- a/debug_test.go +++ b/debug_test.go @@ -17,6 +17,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // TODO @@ -154,13 +155,13 @@ func TestGetMinVer(t *testing.T) { var m uint64 var e error _, e = getMinVer("go1") - assert.NotNil(t, e) + require.Error(t, e) m, e = getMinVer("go1.1") assert.Equal(t, uint64(1), m) - assert.Nil(t, e) + require.NoError(t, e) m, e = getMinVer("go1.1.1") - assert.Nil(t, e) + require.NoError(t, e) assert.Equal(t, uint64(1), m) _, e = getMinVer("go1.1.1.1") - assert.NotNil(t, e) + require.Error(t, e) } diff --git a/errors_test.go b/errors_test.go index f77a6342..72a36992 100644 --- a/errors_test.go +++ b/errors_test.go @@ -11,6 +11,7 @@ import ( "github.com/gin-gonic/gin/internal/json" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestError(t *testing.T) { @@ -122,7 +123,7 @@ func TestErrorUnwrap(t *testing.T) { }) // check that 'errors.Is()' and 'errors.As()' behave as expected : - assert.True(t, errors.Is(err, innerErr)) + require.ErrorIs(t, err, innerErr) var testErr TestErr - assert.True(t, errors.As(err, &testErr)) + require.ErrorAs(t, err, &testErr) } diff --git a/fs_test.go b/fs_test.go index a1690cd9..167ac1af 100644 --- a/fs_test.go +++ b/fs_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type mockFileSystem struct { @@ -28,7 +29,7 @@ func TestOnlyFilesFS_Open(t *testing.T) { file, err := fs.Open("foo") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, testFile, file.(neutralizedReaddirFile).File) } @@ -43,7 +44,7 @@ func TestOnlyFilesFS_Open_err(t *testing.T) { file, err := fs.Open("foo") - assert.ErrorIs(t, err, testError) + require.ErrorIs(t, err, testError) assert.Nil(t, file) } @@ -52,7 +53,7 @@ func Test_neuteredReaddirFile_Readdir(t *testing.T) { res, err := n.Readdir(0) - assert.NoError(t, err) + require.NoError(t, err) assert.Nil(t, res) } diff --git a/gin_integration_test.go b/gin_integration_test.go index 53982712..3082bc2c 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // params[0]=url example:http://127.0.0.1:8080/index (cannot be empty) @@ -40,11 +41,11 @@ func testRequest(t *testing.T, params ...string) { client := &http.Client{Transport: tr} resp, err := client.Get(params[0]) - assert.NoError(t, err) + require.NoError(t, err) defer resp.Body.Close() body, ioerr := io.ReadAll(resp.Body) - assert.NoError(t, ioerr) + require.NoError(t, ioerr) var responseStatus = "200 OK" if len(params) > 1 && params[1] != "" { @@ -73,13 +74,13 @@ func TestRunEmpty(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.Run(":8080")) + require.Error(t, router.Run(":8080")) testRequest(t, "http://localhost:8080/example") } func TestBadTrustedCIDRs(t *testing.T) { router := New() - assert.Error(t, router.SetTrustedProxies([]string{"hello/world"})) + require.Error(t, router.SetTrustedProxies([]string{"hello/world"})) } /* legacy tests @@ -87,7 +88,7 @@ func TestBadTrustedCIDRsForRun(t *testing.T) { os.Setenv("PORT", "") router := New() router.TrustedProxies = []string{"hello/world"} - assert.Error(t, router.Run(":8080")) + require.Error(t, router.Run(":8080")) } func TestBadTrustedCIDRsForRunUnix(t *testing.T) { @@ -100,7 +101,7 @@ func TestBadTrustedCIDRsForRunUnix(t *testing.T) { go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) - assert.Error(t, router.RunUnix(unixTestSocket)) + require.Error(t, router.RunUnix(unixTestSocket)) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete @@ -112,15 +113,15 @@ func TestBadTrustedCIDRsForRunFd(t *testing.T) { router.TrustedProxies = []string{"hello/world"} addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - assert.NoError(t, err) + require.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) - assert.NoError(t, err) + require.NoError(t, err) socketFile, err := listener.File() - assert.NoError(t, err) + require.NoError(t, err) go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) - assert.Error(t, router.RunFd(int(socketFile.Fd()))) + require.Error(t, router.RunFd(int(socketFile.Fd()))) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete @@ -132,12 +133,12 @@ func TestBadTrustedCIDRsForRunListener(t *testing.T) { router.TrustedProxies = []string{"hello/world"} addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - assert.NoError(t, err) + require.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) - assert.NoError(t, err) + require.NoError(t, err) go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) - assert.Error(t, router.RunListener(listener)) + require.Error(t, router.RunListener(listener)) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete @@ -148,7 +149,7 @@ func TestBadTrustedCIDRsForRunTLS(t *testing.T) { os.Setenv("PORT", "") router := New() router.TrustedProxies = []string{"hello/world"} - assert.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + require.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) } */ @@ -164,7 +165,7 @@ func TestRunTLS(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + require.Error(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) testRequest(t, "https://localhost:8443/example") } @@ -201,7 +202,7 @@ func TestPusher(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + require.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) testRequest(t, "https://localhost:8449/pusher") } @@ -216,14 +217,14 @@ func TestRunEmptyWithEnv(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.Run(":3123")) + require.Error(t, router.Run(":3123")) testRequest(t, "http://localhost:3123/example") } func TestRunTooMuchParams(t *testing.T) { router := New() assert.Panics(t, func() { - assert.NoError(t, router.Run("2", "2")) + require.NoError(t, router.Run("2", "2")) }) } @@ -237,7 +238,7 @@ func TestRunWithPort(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.Run(":5150")) + require.Error(t, router.Run(":5150")) testRequest(t, "http://localhost:5150/example") } @@ -257,7 +258,7 @@ func TestUnixSocket(t *testing.T) { time.Sleep(5 * time.Millisecond) c, err := net.Dial("unix", unixTestSocket) - assert.NoError(t, err) + require.NoError(t, err) fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) @@ -271,7 +272,7 @@ func TestUnixSocket(t *testing.T) { func TestBadUnixSocket(t *testing.T) { router := New() - assert.Error(t, router.RunUnix("#/tmp/unix_unit_test")) + require.Error(t, router.RunUnix("#/tmp/unix_unit_test")) } func TestRunQUIC(t *testing.T) { @@ -286,7 +287,7 @@ func TestRunQUIC(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + require.Error(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) testRequest(t, "https://localhost:8443/example") } @@ -294,15 +295,15 @@ func TestFileDescriptor(t *testing.T) { router := New() addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - assert.NoError(t, err) + require.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) - assert.NoError(t, err) + require.NoError(t, err) socketFile, err := listener.File() if isWindows() { // not supported by windows, it is unimplemented now - assert.Error(t, err) + require.Error(t, err) } else { - assert.NoError(t, err) + require.NoError(t, err) } if socketFile == nil { @@ -318,7 +319,7 @@ func TestFileDescriptor(t *testing.T) { time.Sleep(5 * time.Millisecond) c, err := net.Dial("tcp", listener.Addr().String()) - assert.NoError(t, err) + require.NoError(t, err) fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) @@ -332,15 +333,15 @@ func TestFileDescriptor(t *testing.T) { func TestBadFileDescriptor(t *testing.T) { router := New() - assert.Error(t, router.RunFd(0)) + require.Error(t, router.RunFd(0)) } func TestListener(t *testing.T) { router := New() addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - assert.NoError(t, err) + require.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) - assert.NoError(t, err) + require.NoError(t, err) go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.RunListener(listener)) @@ -350,7 +351,7 @@ func TestListener(t *testing.T) { time.Sleep(5 * time.Millisecond) c, err := net.Dial("tcp", listener.Addr().String()) - assert.NoError(t, err) + require.NoError(t, err) fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) @@ -365,11 +366,11 @@ func TestListener(t *testing.T) { func TestBadListener(t *testing.T) { router := New() addr, err := net.ResolveTCPAddr("tcp", "localhost:10086") - assert.NoError(t, err) + require.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) - assert.NoError(t, err) + require.NoError(t, err) listener.Close() - assert.Error(t, router.RunListener(listener)) + require.Error(t, router.RunListener(listener)) } func TestWithHttptestWithAutoSelectedPort(t *testing.T) { @@ -395,7 +396,14 @@ func TestConcurrentHandleContext(t *testing.T) { wg.Add(iterations) for i := 0; i < iterations; i++ { go func() { - testGetRequestHandler(t, router, "/") + req, err := http.NewRequest(http.MethodGet, "/", nil) + assert.NoError(t, err) + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, "it worked", w.Body.String(), "resp body should match") + assert.Equal(t, 200, w.Code, "should get a 200") wg.Done() }() } @@ -417,17 +425,6 @@ func TestConcurrentHandleContext(t *testing.T) { // testRequest(t, "http://localhost:8033/example") // } -func testGetRequestHandler(t *testing.T, h http.Handler, url string) { - req, err := http.NewRequest(http.MethodGet, url, nil) - assert.NoError(t, err) - - w := httptest.NewRecorder() - h.ServeHTTP(w, req) - - assert.Equal(t, "it worked", w.Body.String(), "resp body should match") - assert.Equal(t, 200, w.Code, "should get a 200") -} - func TestTreeRunDynamicRouting(t *testing.T) { router := New() router.GET("/aa/*xx", func(c *Context) { c.String(http.StatusOK, "/aa/*xx") }) diff --git a/gin_test.go b/gin_test.go index db70a8c5..719f63e4 100644 --- a/gin_test.go +++ b/gin_test.go @@ -20,6 +20,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/net/http2" ) @@ -547,10 +548,10 @@ func TestEngineHandleContextManyReEntries(t *testing.T) { r.GET("/:count", func(c *Context) { countStr := c.Param("count") count, err := strconv.Atoi(countStr) - assert.NoError(t, err) + require.NoError(t, err) n, err := c.Writer.Write([]byte(".")) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 1, n) switch { @@ -580,7 +581,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { expectedTrustedCIDRs := []*net.IPNet{parseCIDR("0.0.0.0/0")} err := r.SetTrustedProxies([]string{"0.0.0.0/0"}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } @@ -588,7 +589,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { { err := r.SetTrustedProxies([]string{"192.168.1.33/33"}) - assert.Error(t, err) + require.Error(t, err) } // valid ipv4 address @@ -597,7 +598,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { err := r.SetTrustedProxies([]string{"192.168.1.33"}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } @@ -605,7 +606,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { { err := r.SetTrustedProxies([]string{"192.168.1.256"}) - assert.Error(t, err) + require.Error(t, err) } // valid ipv6 address @@ -613,7 +614,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { expectedTrustedCIDRs := []*net.IPNet{parseCIDR("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")} err := r.SetTrustedProxies([]string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } @@ -621,7 +622,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { { err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"}) - assert.Error(t, err) + require.Error(t, err) } // valid ipv6 cidr @@ -629,7 +630,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { expectedTrustedCIDRs := []*net.IPNet{parseCIDR("::/0")} err := r.SetTrustedProxies([]string{"::/0"}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } @@ -637,7 +638,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { { err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"}) - assert.Error(t, err) + require.Error(t, err) } // valid combination @@ -653,7 +654,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { "172.16.0.1", }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } @@ -665,7 +666,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { "172.16.0.256", }) - assert.Error(t, err) + require.Error(t, err) } // nil value @@ -673,7 +674,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { err := r.SetTrustedProxies(nil) assert.Nil(t, r.trustedCIDRs) - assert.Nil(t, err) + require.NoError(t, err) } } diff --git a/githubapi_test.go b/githubapi_test.go index 9276bed5..6d348787 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -14,6 +14,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type route struct { @@ -295,9 +296,9 @@ func TestShouldBindUri(t *testing.T) { } router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person - assert.NoError(t, c.ShouldBindUri(&person)) - assert.True(t, person.Name != "") - assert.True(t, person.ID != "") + require.NoError(t, c.ShouldBindUri(&person)) + assert.NotEqual(t, "", person.Name) + assert.NotEqual(t, "", person.ID) c.String(http.StatusOK, "ShouldBindUri test OK") }) @@ -317,9 +318,9 @@ func TestBindUri(t *testing.T) { } router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person - assert.NoError(t, c.BindUri(&person)) - assert.True(t, person.Name != "") - assert.True(t, person.ID != "") + require.NoError(t, c.BindUri(&person)) + assert.NotEqual(t, "", person.Name) + assert.NotEqual(t, "", person.ID) c.String(http.StatusOK, "BindUri test OK") }) @@ -338,7 +339,7 @@ func TestBindUriError(t *testing.T) { } router.Handle(http.MethodGet, "/new/rest/:num", func(c *Context) { var m Member - assert.Error(t, c.BindUri(&m)) + require.Error(t, c.BindUri(&m)) }) path1, _ := exampleFromPath("/new/rest/:num") diff --git a/logger_test.go b/logger_test.go index 6c1814dc..b05df740 100644 --- a/logger_test.go +++ b/logger_test.go @@ -329,13 +329,13 @@ func TestIsOutputColor(t *testing.T) { } consoleColorMode = autoColor - assert.Equal(t, true, p.IsOutputColor()) + assert.True(t, p.IsOutputColor()) ForceConsoleColor() - assert.Equal(t, true, p.IsOutputColor()) + assert.True(t, p.IsOutputColor()) DisableConsoleColor() - assert.Equal(t, false, p.IsOutputColor()) + assert.False(t, p.IsOutputColor()) // test with isTerm flag false. p = LogFormatterParams{ @@ -343,13 +343,13 @@ func TestIsOutputColor(t *testing.T) { } consoleColorMode = autoColor - assert.Equal(t, false, p.IsOutputColor()) + assert.False(t, p.IsOutputColor()) ForceConsoleColor() - assert.Equal(t, true, p.IsOutputColor()) + assert.True(t, p.IsOutputColor()) DisableConsoleColor() - assert.Equal(t, false, p.IsOutputColor()) + assert.False(t, p.IsOutputColor()) // reset console color mode. consoleColorMode = autoColor diff --git a/path_test.go b/path_test.go index 864302f4..2269b78e 100644 --- a/path_test.go +++ b/path_test.go @@ -87,7 +87,7 @@ func TestPathCleanMallocs(t *testing.T) { for _, test := range cleanTests { allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) }) - assert.EqualValues(t, allocs, 0) + assert.InDelta(t, 0, allocs, 0.01) } } diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go index db4b71e5..579897cc 100644 --- a/render/render_msgpack_test.go +++ b/render/render_msgpack_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/ugorji/go/codec" ) @@ -29,7 +30,7 @@ func TestRenderMsgPack(t *testing.T) { err := (MsgPack{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) h := new(codec.MsgpackHandle) assert.NotNil(t, h) @@ -37,7 +38,7 @@ func TestRenderMsgPack(t *testing.T) { assert.NotNil(t, buf) err = codec.NewEncoder(buf, h).Encode(data) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, w.Body.String(), buf.String()) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/render/render_test.go b/render/render_test.go index 145f1316..27a5065b 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -18,6 +18,7 @@ import ( "github.com/gin-gonic/gin/internal/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) @@ -36,7 +37,7 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -46,7 +47,7 @@ func TestRenderJSONError(t *testing.T) { data := make(chan int) // json: unsupported type: chan int - assert.Error(t, (JSON{data}).Render(w)) + require.Error(t, (JSON{data}).Render(w)) } func TestRenderIndentedJSON(t *testing.T) { @@ -58,7 +59,7 @@ func TestRenderIndentedJSON(t *testing.T) { err := (IndentedJSON{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -69,7 +70,7 @@ func TestRenderIndentedJSONPanics(t *testing.T) { // json: unsupported type: chan int err := (IndentedJSON{data}).Render(w) - assert.Error(t, err) + require.Error(t, err) } func TestRenderSecureJSON(t *testing.T) { @@ -83,7 +84,7 @@ func TestRenderSecureJSON(t *testing.T) { err1 := (SecureJSON{"while(1);", data}).Render(w1) - assert.NoError(t, err1) + require.NoError(t, err1) assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) @@ -95,7 +96,7 @@ func TestRenderSecureJSON(t *testing.T) { }} err2 := (SecureJSON{"while(1);", datas}).Render(w2) - assert.NoError(t, err2) + require.NoError(t, err2) assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type")) } @@ -106,7 +107,7 @@ func TestRenderSecureJSONFail(t *testing.T) { // json: unsupported type: chan int err := (SecureJSON{"while(1);", data}).Render(w) - assert.Error(t, err) + require.Error(t, err) } func TestRenderJsonpJSON(t *testing.T) { @@ -120,7 +121,7 @@ func TestRenderJsonpJSON(t *testing.T) { err1 := (JsonpJSON{"x", data}).Render(w1) - assert.NoError(t, err1) + require.NoError(t, err1) assert.Equal(t, "x({\"foo\":\"bar\"});", w1.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) @@ -132,7 +133,7 @@ func TestRenderJsonpJSON(t *testing.T) { }} err2 := (JsonpJSON{"x", datas}).Render(w2) - assert.NoError(t, err2) + require.NoError(t, err2) assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}]);", w2.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) } @@ -191,7 +192,7 @@ func TestRenderJsonpJSONError2(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) e := (JsonpJSON{"", data}).Render(w) - assert.NoError(t, e) + require.NoError(t, e) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) @@ -203,7 +204,7 @@ func TestRenderJsonpJSONFail(t *testing.T) { // json: unsupported type: chan int err := (JsonpJSON{"x", data}).Render(w) - assert.Error(t, err) + require.Error(t, err) } func TestRenderAsciiJSON(t *testing.T) { @@ -215,7 +216,7 @@ func TestRenderAsciiJSON(t *testing.T) { err := (AsciiJSON{data1}).Render(w1) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) @@ -223,7 +224,7 @@ func TestRenderAsciiJSON(t *testing.T) { data2 := 3.1415926 err = (AsciiJSON{data2}).Render(w2) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "3.1415926", w2.Body.String()) } @@ -232,7 +233,7 @@ func TestRenderAsciiJSONFail(t *testing.T) { data := make(chan int) // json: unsupported type: chan int - assert.Error(t, (AsciiJSON{data}).Render(w)) + require.Error(t, (AsciiJSON{data}).Render(w)) } func TestRenderPureJSON(t *testing.T) { @@ -242,7 +243,7 @@ func TestRenderPureJSON(t *testing.T) { "html": "", } err := (PureJSON{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -283,7 +284,7 @@ b: assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) err := (YAML{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String()) assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) } @@ -298,7 +299,7 @@ func (ft *fail) MarshalYAML() (any, error) { func TestRenderYAMLFail(t *testing.T) { w := httptest.NewRecorder() err := (YAML{&fail{}}).Render(w) - assert.Error(t, err) + require.Error(t, err) } func TestRenderTOML(t *testing.T) { @@ -311,7 +312,7 @@ func TestRenderTOML(t *testing.T) { assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) err := (TOML{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "foo = 'bar'\nhtml = ''\n", w.Body.String()) assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) } @@ -319,7 +320,7 @@ func TestRenderTOML(t *testing.T) { func TestRenderTOMLFail(t *testing.T) { w := httptest.NewRecorder() err := (TOML{net.IPv4bcast}).Render(w) - assert.Error(t, err) + require.Error(t, err) } // test Protobuf rendering @@ -334,12 +335,12 @@ func TestRenderProtoBuf(t *testing.T) { (ProtoBuf{data}).WriteContentType(w) protoData, err := proto.Marshal(data) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) err = (ProtoBuf{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) } @@ -348,7 +349,7 @@ func TestRenderProtoBufFail(t *testing.T) { w := httptest.NewRecorder() data := &testdata.Test{} err := (ProtoBuf{data}).Render(w) - assert.Error(t, err) + require.Error(t, err) } func TestRenderXML(t *testing.T) { @@ -362,14 +363,14 @@ func TestRenderXML(t *testing.T) { err := (XML{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderRedirect(t *testing.T) { req, err := http.NewRequest("GET", "/test-redirect", nil) - assert.NoError(t, err) + require.NoError(t, err) data1 := Redirect{ Code: http.StatusMovedPermanently, @@ -379,7 +380,7 @@ func TestRenderRedirect(t *testing.T) { w := httptest.NewRecorder() err = data1.Render(w) - assert.NoError(t, err) + require.NoError(t, err) data2 := Redirect{ Code: http.StatusOK, @@ -390,7 +391,7 @@ func TestRenderRedirect(t *testing.T) { w = httptest.NewRecorder() assert.PanicsWithValue(t, "Cannot redirect with status code 200", func() { err := data2.Render(w) - assert.NoError(t, err) + require.NoError(t, err) }) data3 := Redirect{ @@ -401,7 +402,7 @@ func TestRenderRedirect(t *testing.T) { w = httptest.NewRecorder() err = data3.Render(w) - assert.NoError(t, err) + require.NoError(t, err) // only improve coverage data2.WriteContentType(w) @@ -416,7 +417,7 @@ func TestRenderData(t *testing.T) { Data: data, }).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "#!PNG some raw data", w.Body.String()) assert.Equal(t, "image/png", w.Header().Get("Content-Type")) } @@ -435,7 +436,7 @@ func TestRenderString(t *testing.T) { Data: []any{"manu", 2}, }).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "hola manu 2", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } @@ -448,7 +449,7 @@ func TestRenderStringLenZero(t *testing.T) { Data: []any{}, }).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "hola %s %d", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } @@ -464,7 +465,7 @@ func TestRenderHTMLTemplate(t *testing.T) { err := instance.Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } @@ -480,7 +481,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { err := instance.Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } @@ -499,7 +500,7 @@ func TestRenderHTMLDebugFiles(t *testing.T) { err := instance.Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "

Hello thinkerou

", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } @@ -518,7 +519,7 @@ func TestRenderHTMLDebugGlob(t *testing.T) { err := instance.Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "

Hello thinkerou

", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } @@ -548,7 +549,7 @@ func TestRenderReader(t *testing.T) { Headers: headers, }).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, body, w.Body.String()) assert.Equal(t, "image/png", w.Header().Get("Content-Type")) assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length")) @@ -571,7 +572,7 @@ func TestRenderReaderNoContentLength(t *testing.T) { Headers: headers, }).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, body, w.Body.String()) assert.Equal(t, "image/png", w.Header().Get("Content-Type")) assert.NotContains(t, "Content-Length", w.Header()) @@ -588,6 +589,6 @@ func TestRenderWriteError(t *testing.T) { ResponseRecorder: httptest.NewRecorder(), } err := r.Render(ew) - assert.NotNil(t, err) + require.Error(t, err) assert.Equal(t, `write "my-prefix:" error`, err.Error()) } diff --git a/response_writer_test.go b/response_writer_test.go index 964aa307..259b8fa8 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // TODO @@ -95,13 +96,13 @@ func TestResponseWriterWrite(t *testing.T) { assert.Equal(t, http.StatusOK, w.Status()) assert.Equal(t, http.StatusOK, testWriter.Code) assert.Equal(t, "hola", testWriter.Body.String()) - assert.NoError(t, err) + require.NoError(t, err) n, err = w.Write([]byte(" adios")) assert.Equal(t, 6, n) assert.Equal(t, 10, w.Size()) assert.Equal(t, "hola adios", testWriter.Body.String()) - assert.NoError(t, err) + require.NoError(t, err) } func TestResponseWriterHijack(t *testing.T) { @@ -112,7 +113,7 @@ func TestResponseWriterHijack(t *testing.T) { assert.Panics(t, func() { _, _, err := w.Hijack() - assert.NoError(t, err) + require.NoError(t, err) }) assert.True(t, w.Written()) @@ -135,7 +136,7 @@ func TestResponseWriterFlush(t *testing.T) { // should return 500 resp, err := http.Get(testServer.URL) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) } diff --git a/routes_test.go b/routes_test.go index 73f393e7..d6233b09 100644 --- a/routes_test.go +++ b/routes_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type header struct { @@ -386,7 +387,7 @@ func TestRouteStaticFile(t *testing.T) { } defer os.Remove(f.Name()) _, err = f.WriteString("Gin Web Framework") - assert.NoError(t, err) + require.NoError(t, err) f.Close() dir, filename := filepath.Split(f.Name()) @@ -421,7 +422,7 @@ func TestRouteStaticFileFS(t *testing.T) { } defer os.Remove(f.Name()) _, err = f.WriteString("Gin Web Framework") - assert.NoError(t, err) + require.NoError(t, err) f.Close() dir, filename := filepath.Split(f.Name()) @@ -484,7 +485,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' assert.NotEqual(t, "", w.Header().Get("Content-Type")) - assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") + assert.NotEqual(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Last-Modified")) assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires")) assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN")) } diff --git a/utils_test.go b/utils_test.go index 058ddb9d..af089963 100644 --- a/utils_test.go +++ b/utils_test.go @@ -145,6 +145,6 @@ func TestMarshalXMLforH(t *testing.T) { } func TestIsASCII(t *testing.T) { - assert.Equal(t, isASCII("test"), true) - assert.Equal(t, isASCII("๐Ÿงก๐Ÿ’›๐Ÿ’š๐Ÿ’™๐Ÿ’œ"), false) + assert.True(t, isASCII("test")) + assert.False(t, isASCII("๐Ÿงก๐Ÿ’›๐Ÿ’š๐Ÿ’™๐Ÿ’œ")) } From cc4e11438cd6c0bcc632fe3492d3817dfa21c337 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:34:34 +0800 Subject: [PATCH 168/332] chore(deps): bump golang.org/x/net from 0.25.0 to 0.27.0 (#4013) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.25.0 to 0.27.0. - [Commits](https://github.com/golang/net/compare/v0.25.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/net 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 | 30 ++++++++++++++++-------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 4937d2b7..035c2dea 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/quic-go/quic-go v0.43.1 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.25.0 + golang.org/x/net v0.27.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -39,10 +39,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.23.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect - golang.org/x/mod v0.11.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect ) diff --git a/go.sum b/go.sum index 44af4cc1..55a21627 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ 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/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -92,24 +92,26 @@ go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= 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/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 3cb30679b5e3021db16c776ed7e70d380586e9ce Mon Sep 17 00:00:00 2001 From: Jo YoHan <37216082+slowhigh@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:16:30 +0900 Subject: [PATCH 169/332] feat(form): add array collection format in form binding (#3986) * feat(form): add array collection format in form binding * feat(form): add array collection format in form binding * test(form): fix test code for array collection format in form binding --- binding/form_mapping.go | 40 ++++++++++++++++++++++++++ binding/form_mapping_test.go | 39 ++++++++++++++++++++++++++ docs/doc.md | 54 ++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 33389b28..4a35866d 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -182,6 +182,38 @@ func trySetCustom(val string, value reflect.Value) (isSet bool, err error) { return false, nil } +func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) { + cfTag := field.Tag.Get("collection_format") + if cfTag == "" || cfTag == "multi" { + return vs, nil + } + + var sep string + switch cfTag { + case "csv": + sep = "," + case "ssv": + sep = " " + case "tsv": + sep = "\t" + case "pipes": + sep = "|" + default: + return vs, fmt.Errorf("%s is not supported in the collection_format. (csv, ssv, pipes)", cfTag) + } + + totalLength := 0 + for _, v := range vs { + totalLength += strings.Count(v, sep) + 1 + } + newVs = make([]string, 0, totalLength) + for _, v := range vs { + newVs = append(newVs, strings.Split(v, sep)...) + } + + return newVs, nil +} + func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) { vs, ok := form[tagValue] if !ok && !opt.isDefaultExists { @@ -198,6 +230,10 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ return ok, err } + if vs, err = trySplit(vs, field); err != nil { + return false, err + } + return true, setSlice(vs, value, field) case reflect.Array: if !ok { @@ -208,6 +244,10 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ return ok, err } + if vs, err = trySplit(vs, field); err != nil { + return false, err + } + if len(vs) != value.Len() { return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) } diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 9ea0895c..c6db033e 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -264,6 +264,45 @@ func TestMappingArray(t *testing.T) { require.Error(t, err) } +func TestMappingCollectionFormat(t *testing.T) { + var s struct { + SliceMulti []int `form:"slice_multi" collection_format:"multi"` + SliceCsv []int `form:"slice_csv" collection_format:"csv"` + SliceSsv []int `form:"slice_ssv" collection_format:"ssv"` + SliceTsv []int `form:"slice_tsv" collection_format:"tsv"` + SlicePipes []int `form:"slice_pipes" collection_format:"pipes"` + ArrayMulti [2]int `form:"array_multi" collection_format:"multi"` + ArrayCsv [2]int `form:"array_csv" collection_format:"csv"` + ArraySsv [2]int `form:"array_ssv" collection_format:"ssv"` + ArrayTsv [2]int `form:"array_tsv" collection_format:"tsv"` + ArrayPipes [2]int `form:"array_pipes" collection_format:"pipes"` + } + err := mappingByPtr(&s, formSource{ + "slice_multi": {"1", "2"}, + "slice_csv": {"1,2"}, + "slice_ssv": {"1 2"}, + "slice_tsv": {"1 2"}, + "slice_pipes": {"1|2"}, + "array_multi": {"1", "2"}, + "array_csv": {"1,2"}, + "array_ssv": {"1 2"}, + "array_tsv": {"1 2"}, + "array_pipes": {"1|2"}, + }, "form") + require.NoError(t, err) + + assert.Equal(t, []int{1, 2}, s.SliceMulti) + assert.Equal(t, []int{1, 2}, s.SliceCsv) + assert.Equal(t, []int{1, 2}, s.SliceSsv) + assert.Equal(t, []int{1, 2}, s.SliceTsv) + assert.Equal(t, []int{1, 2}, s.SlicePipes) + assert.Equal(t, [2]int{1, 2}, s.ArrayMulti) + assert.Equal(t, [2]int{1, 2}, s.ArrayCsv) + assert.Equal(t, [2]int{1, 2}, s.ArraySsv) + assert.Equal(t, [2]int{1, 2}, s.ArrayTsv) + assert.Equal(t, [2]int{1, 2}, s.ArrayPipes) +} + func TestMappingStructField(t *testing.T) { var s struct { J struct { diff --git a/docs/doc.md b/docs/doc.md index 51366409..b76011f2 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -26,6 +26,7 @@ - [Custom Validators](#custom-validators) - [Only Bind Query String](#only-bind-query-string) - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Collection format for arrays](#collection-format-for-arrays) - [Bind Uri](#bind-uri) - [Bind custom unmarshaler](#bind-custom-unmarshaler) - [Bind Header](#bind-header) @@ -861,6 +862,59 @@ Test it with: curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" ``` +#### Collection format for arrays + +| Format | Description | Example | +| --------------- | --------------------------------------------------------- | ----------------------- | +| multi (default) | Multiple parameter instances rather than multiple values. | key=foo&key=bar&key=baz | +| csv | Comma-separated values. | foo,bar,baz | +| ssv | Space-separated values. | foo bar baz | +| tsv | Tab-separated values. | "foo\tbar\tbaz" | +| pipes | Pipe-separated values. | foo\|bar\|baz | + +```go +package main + +import ( + "log" + "time" + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Addresses []string `form:"addresses" collection_format:"csv"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` +} + +func main() { + route := gin.Default() + route.GET("/testing", startPage) + route.Run(":8085") +} +func startPage(c *gin.Context) { + var person Person + // If `GET`, only `Form` binding engine (`query`) used. + // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Addresses) + log.Println(person.Birthday) + log.Println(person.CreateTime) + log.Println(person.UnixTime) + } + c.String(200, "Success") +} +``` + +Test it with: +```sh +$ curl -X GET "localhost:8085/testing?name=appleboy&addresses=foo,bar&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" +``` + ### Bind Uri See the [detail information](https://github.com/gin-gonic/gin/issues/846). From 28e57f58b184b2305ace192e02496bb89f6fd8cb Mon Sep 17 00:00:00 2001 From: Ahmad Saeed Goda Date: Fri, 6 Sep 2024 08:21:19 +0300 Subject: [PATCH 170/332] fix(form): Set default value for form fields (#4047) - Use specified default value in struct tags when binding a request input to struct for validation, even if sent empty, not only when not sent at all. - Add string field to `TestMappingDefault` test case. - Add test case for not sent form field to default to the value specified via code. - Add test case for form field sent empty to default to the value specified via code. Fixes: How to apply default value if empty value provided by client during model binding? #4042, #13042df, #a41721a --- binding/form_mapping.go | 3 +++ binding/form_mapping_test.go | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 4a35866d..a84536f7 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -261,6 +261,9 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ if len(vs) > 0 { val = vs[0] + if val == "" { + val = opt.defaultValue + } } if ok, err := trySetCustom(val, value); ok { return ok, err diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index c6db033e..8cf74651 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -69,6 +69,7 @@ func TestMappingBaseTypes(t *testing.T) { func TestMappingDefault(t *testing.T) { var s struct { + Str string `form:",default=defaultVal"` Int int `form:",default=9"` Slice []int `form:",default=9"` Array [1]int `form:",default=9"` @@ -76,6 +77,7 @@ func TestMappingDefault(t *testing.T) { err := mappingByPtr(&s, formSource{}, "form") require.NoError(t, err) + assert.Equal(t, "defaultVal", s.Str) assert.Equal(t, 9, s.Int) assert.Equal(t, []int{9}, s.Slice) assert.Equal(t, [1]int{9}, s.Array) @@ -152,6 +154,24 @@ func TestMappingForm(t *testing.T) { assert.Equal(t, 6, s.F) } +func TestMappingFormFieldNotSent(t *testing.T) { + var s struct { + F string `form:"field,default=defVal"` + } + err := mapForm(&s, map[string][]string{}) + require.NoError(t, err) + assert.Equal(t, "defVal", s.F) +} + +func TestMappingFormWithEmptyToDefault(t *testing.T) { + var s struct { + F string `form:"field,default=DefVal"` + } + err := mapForm(&s, map[string][]string{"field": {""}}) + require.NoError(t, err) + assert.Equal(t, "DefVal", s.F) +} + func TestMapFormWithTag(t *testing.T) { var s struct { F int `externalTag:"field"` From f2c861a24f204f53dd6e6755b6d4efece7e373ea Mon Sep 17 00:00:00 2001 From: demouth <1133178+demouth@users.noreply.github.com> Date: Sun, 15 Sep 2024 09:54:23 +0900 Subject: [PATCH 171/332] docs: fix route group example code (#4020) --- docs/doc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/doc.md b/docs/doc.md index b76011f2..875dd619 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -339,16 +339,16 @@ func main() { router := gin.Default() // Simple group: v1 - v1 := router.Group("/v1") { + v1 := router.Group("/v1") v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint) } // Simple group: v2 - v2 := router.Group("/v2") { + v2 := router.Group("/v2") v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint) From 9d7c0e9e1a301f417df9dc89a8cadc3bf9063db2 Mon Sep 17 00:00:00 2001 From: CC11001100 Date: Sun, 15 Sep 2024 08:58:59 +0800 Subject: [PATCH 172/332] feat(context): GetXxx added support for more go native types (#3633) --- context.go | 144 ++++++++++++++++++++++++++++++++++++++++++++- context_test.go | 152 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index baa4b0f9..1f356515 100644 --- a/context.go +++ b/context.go @@ -315,7 +315,31 @@ func (c *Context) GetInt(key string) (i int) { return } -// GetInt64 returns the value associated with the key as an integer. +// GetInt8 returns the value associated with the key as an integer 8. +func (c *Context) GetInt8(key string) (i8 int8) { + if val, ok := c.Get(key); ok && val != nil { + i8, _ = val.(int8) + } + return +} + +// GetInt16 returns the value associated with the key as an integer 16. +func (c *Context) GetInt16(key string) (i16 int16) { + if val, ok := c.Get(key); ok && val != nil { + i16, _ = val.(int16) + } + return +} + +// GetInt32 returns the value associated with the key as an integer 32. +func (c *Context) GetInt32(key string) (i32 int32) { + if val, ok := c.Get(key); ok && val != nil { + i32, _ = val.(int32) + } + return +} + +// GetInt64 returns the value associated with the key as an integer 64. func (c *Context) GetInt64(key string) (i64 int64) { if val, ok := c.Get(key); ok && val != nil { i64, _ = val.(int64) @@ -331,7 +355,31 @@ func (c *Context) GetUint(key string) (ui uint) { return } -// GetUint64 returns the value associated with the key as an unsigned integer. +// GetUint8 returns the value associated with the key as an unsigned integer 8. +func (c *Context) GetUint8(key string) (ui8 uint8) { + if val, ok := c.Get(key); ok && val != nil { + ui8, _ = val.(uint8) + } + return +} + +// GetUint16 returns the value associated with the key as an unsigned integer 16. +func (c *Context) GetUint16(key string) (ui16 uint16) { + if val, ok := c.Get(key); ok && val != nil { + ui16, _ = val.(uint16) + } + return +} + +// GetUint32 returns the value associated with the key as an unsigned integer 32. +func (c *Context) GetUint32(key string) (ui32 uint32) { + if val, ok := c.Get(key); ok && val != nil { + ui32, _ = val.(uint32) + } + return +} + +// GetUint64 returns the value associated with the key as an unsigned integer 64. func (c *Context) GetUint64(key string) (ui64 uint64) { if val, ok := c.Get(key); ok && val != nil { ui64, _ = val.(uint64) @@ -339,6 +387,14 @@ func (c *Context) GetUint64(key string) (ui64 uint64) { return } +// GetFloat32 returns the value associated with the key as a float32. +func (c *Context) GetFloat32(key string) (f32 float32) { + if val, ok := c.Get(key); ok && val != nil { + f32, _ = val.(float32) + } + return +} + // GetFloat64 returns the value associated with the key as a float64. func (c *Context) GetFloat64(key string) (f64 float64) { if val, ok := c.Get(key); ok && val != nil { @@ -363,6 +419,90 @@ func (c *Context) GetDuration(key string) (d time.Duration) { return } +func (c *Context) GetIntSlice(key string) (is []int) { + if val, ok := c.Get(key); ok && val != nil { + is, _ = val.([]int) + } + return +} + +func (c *Context) GetInt8Slice(key string) (i8s []int8) { + if val, ok := c.Get(key); ok && val != nil { + i8s, _ = val.([]int8) + } + return +} + +func (c *Context) GetInt16Slice(key string) (i16s []int16) { + if val, ok := c.Get(key); ok && val != nil { + i16s, _ = val.([]int16) + } + return +} + +func (c *Context) GetInt32Slice(key string) (i32s []int32) { + if val, ok := c.Get(key); ok && val != nil { + i32s, _ = val.([]int32) + } + return +} + +func (c *Context) GetInt64Slice(key string) (i64s []int64) { + if val, ok := c.Get(key); ok && val != nil { + i64s, _ = val.([]int64) + } + return +} + +func (c *Context) GetUintSlice(key string) (uis []uint) { + if val, ok := c.Get(key); ok && val != nil { + uis, _ = val.([]uint) + } + return +} + +func (c *Context) GetUint8Slice(key string) (ui8s []uint8) { + if val, ok := c.Get(key); ok && val != nil { + ui8s, _ = val.([]uint8) + } + return +} + +func (c *Context) GetUint16Slice(key string) (ui16s []uint16) { + if val, ok := c.Get(key); ok && val != nil { + ui16s, _ = val.([]uint16) + } + return +} + +func (c *Context) GetUint32Slice(key string) (ui32s []uint32) { + if val, ok := c.Get(key); ok && val != nil { + ui32s, _ = val.([]uint32) + } + return +} + +func (c *Context) GetUint64Slice(key string) (ui64s []uint64) { + if val, ok := c.Get(key); ok && val != nil { + ui64s, _ = val.([]uint64) + } + return +} + +func (c *Context) GetFloat32Slice(key string) (f32s []float32) { + if val, ok := c.Get(key); ok && val != nil { + f32s, _ = val.([]float32) + } + return +} + +func (c *Context) GetFloat64Slice(key string) (f64s []float64) { + if val, ok := c.Get(key); ok && val != nil { + f64s, _ = val.([]float64) + } + return +} + // GetStringSlice returns the value associated with the key as a slice of strings. func (c *Context) GetStringSlice(key string) (ss []string) { if val, ok := c.Get(key); ok && val != nil { diff --git a/context_test.go b/context_test.go index 66190b30..211fbb1b 100644 --- a/context_test.go +++ b/context_test.go @@ -252,6 +252,30 @@ func TestContextGetInt(t *testing.T) { assert.Equal(t, 1, c.GetInt("int")) } +func TestContextGetInt8(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int8" + value := int8(0x7F) + c.Set(key, value) + assert.Equal(t, value, c.GetInt8(key)) +} + +func TestContextGetInt16(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int16" + value := int16(0x7FFF) + c.Set(key, value) + assert.Equal(t, value, c.GetInt16(key)) +} + +func TestContextGetInt32(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int32" + value := int32(0x7FFFFFFF) + c.Set(key, value) + assert.Equal(t, value, c.GetInt32(key)) +} + func TestContextGetInt64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("int64", int64(42424242424242)) @@ -264,12 +288,44 @@ func TestContextGetUint(t *testing.T) { assert.Equal(t, uint(1), c.GetUint("uint")) } +func TestContextGetUint8(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint8" + value := uint8(0xFF) + c.Set(key, value) + assert.Equal(t, value, c.GetUint8(key)) +} + +func TestContextGetUint16(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint16" + value := uint16(0xFFFF) + c.Set(key, value) + assert.Equal(t, value, c.GetUint16(key)) +} + +func TestContextGetUint32(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint32" + value := uint32(0xFFFFFFFF) + c.Set(key, value) + assert.Equal(t, value, c.GetUint32(key)) +} + func TestContextGetUint64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("uint64", uint64(18446744073709551615)) assert.Equal(t, uint64(18446744073709551615), c.GetUint64("uint64")) } +func TestContextGetFloat32(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "float32" + value := float32(3.14) + c.Set(key, value) + assert.Equal(t, value, c.GetFloat32(key)) +} + func TestContextGetFloat64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("float64", 4.2) @@ -289,6 +345,102 @@ func TestContextGetDuration(t *testing.T) { assert.Equal(t, time.Second, c.GetDuration("duration")) } +func TestContextGetIntSlice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int-slice" + value := []int{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetIntSlice(key)) +} + +func TestContextGetInt8Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int8-slice" + value := []int8{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetInt8Slice(key)) +} + +func TestContextGetInt16Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int16-slice" + value := []int16{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetInt16Slice(key)) +} + +func TestContextGetInt32Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int32-slice" + value := []int32{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetInt32Slice(key)) +} + +func TestContextGetInt64Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int64-slice" + value := []int64{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetInt64Slice(key)) +} + +func TestContextGetUintSlice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint-slice" + value := []uint{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetUintSlice(key)) +} + +func TestContextGetUint8Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint8-slice" + value := []uint8{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetUint8Slice(key)) +} + +func TestContextGetUint16Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint16-slice" + value := []uint16{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetUint16Slice(key)) +} + +func TestContextGetUint32Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint32-slice" + value := []uint32{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetUint32Slice(key)) +} + +func TestContextGetUint64Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint64-slice" + value := []uint64{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetUint64Slice(key)) +} + +func TestContextGetFloat32Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "float32-slice" + value := []float32{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetFloat32Slice(key)) +} + +func TestContextGetFloat64Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "float64-slice" + value := []float64{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetFloat64Slice(key)) +} + func TestContextGetStringSlice(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("slice", []string{"foo"}) From f05f966a0824b1d302ee556183e2579c91954266 Mon Sep 17 00:00:00 2001 From: takanuva15 <6986426+takanuva15@users.noreply.github.com> Date: Sat, 21 Sep 2024 11:24:18 -0400 Subject: [PATCH 173/332] feat(form): Support default values for collections in form binding (#4048) --- binding/form_mapping.go | 20 +++++++++++ binding/form_mapping_test.go | 66 ++++++++++++++++++++++++++++++++++++ docs/doc.md | 48 ++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index a84536f7..f5f6f3ae 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -159,6 +159,14 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter if k, v := head(opt, "="); k == "default" { setOpt.isDefaultExists = true setOpt.defaultValue = v + + // convert semicolon-separated default values to csv-separated values for processing in setByForm + if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array { + cfTag := field.Tag.Get("collection_format") + if cfTag == "" || cfTag == "multi" || cfTag == "csv" { + setOpt.defaultValue = strings.ReplaceAll(v, ";", ",") + } + } } } @@ -224,6 +232,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ case reflect.Slice: if !ok { vs = []string{opt.defaultValue} + + // pre-process the default value for multi if present + cfTag := field.Tag.Get("collection_format") + if cfTag == "" || cfTag == "multi" { + vs = strings.Split(opt.defaultValue, ",") + } } if ok, err = trySetCustom(vs[0], value); ok { @@ -238,6 +252,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ case reflect.Array: if !ok { vs = []string{opt.defaultValue} + + // pre-process the default value for multi if present + cfTag := field.Tag.Get("collection_format") + if cfTag == "" || cfTag == "multi" { + vs = strings.Split(opt.defaultValue, ",") + } } if ok, err = trySetCustom(vs[0], value); ok { diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 8cf74651..810315bb 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -323,6 +323,72 @@ func TestMappingCollectionFormat(t *testing.T) { assert.Equal(t, [2]int{1, 2}, s.ArrayPipes) } +func TestMappingCollectionFormatInvalid(t *testing.T) { + var s struct { + SliceCsv []int `form:"slice_csv" collection_format:"xxx"` + } + err := mappingByPtr(&s, formSource{ + "slice_csv": {"1,2"}, + }, "form") + require.Error(t, err) + + var s2 struct { + ArrayCsv [2]int `form:"array_csv" collection_format:"xxx"` + } + err = mappingByPtr(&s2, formSource{ + "array_csv": {"1,2"}, + }, "form") + require.Error(t, err) +} + +func TestMappingMultipleDefaultWithCollectionFormat(t *testing.T) { + var s struct { + SliceMulti []int `form:",default=1;2;3" collection_format:"multi"` + SliceCsv []int `form:",default=1;2;3" collection_format:"csv"` + SliceSsv []int `form:",default=1 2 3" collection_format:"ssv"` + SliceTsv []int `form:",default=1\t2\t3" collection_format:"tsv"` + SlicePipes []int `form:",default=1|2|3" collection_format:"pipes"` + ArrayMulti [2]int `form:",default=1;2" collection_format:"multi"` + ArrayCsv [2]int `form:",default=1;2" collection_format:"csv"` + ArraySsv [2]int `form:",default=1 2" collection_format:"ssv"` + ArrayTsv [2]int `form:",default=1\t2" collection_format:"tsv"` + ArrayPipes [2]int `form:",default=1|2" collection_format:"pipes"` + SliceStringMulti []string `form:",default=1;2;3" collection_format:"multi"` + SliceStringCsv []string `form:",default=1;2;3" collection_format:"csv"` + SliceStringSsv []string `form:",default=1 2 3" collection_format:"ssv"` + SliceStringTsv []string `form:",default=1\t2\t3" collection_format:"tsv"` + SliceStringPipes []string `form:",default=1|2|3" collection_format:"pipes"` + ArrayStringMulti [2]string `form:",default=1;2" collection_format:"multi"` + ArrayStringCsv [2]string `form:",default=1;2" collection_format:"csv"` + ArrayStringSsv [2]string `form:",default=1 2" collection_format:"ssv"` + ArrayStringTsv [2]string `form:",default=1\t2" collection_format:"tsv"` + ArrayStringPipes [2]string `form:",default=1|2" collection_format:"pipes"` + } + 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) + assert.Equal(t, []int{1, 2, 3}, s.SliceSsv) + assert.Equal(t, []int{1, 2, 3}, s.SliceTsv) + assert.Equal(t, []int{1, 2, 3}, s.SlicePipes) + assert.Equal(t, [2]int{1, 2}, s.ArrayMulti) + assert.Equal(t, [2]int{1, 2}, s.ArrayCsv) + assert.Equal(t, [2]int{1, 2}, s.ArraySsv) + assert.Equal(t, [2]int{1, 2}, s.ArrayTsv) + assert.Equal(t, [2]int{1, 2}, s.ArrayPipes) + assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringMulti) + assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringCsv) + assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringSsv) + assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringTsv) + assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringPipes) + assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringMulti) + assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringCsv) + assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringSsv) + assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringTsv) + assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringPipes) +} + func TestMappingStructField(t *testing.T) { var s struct { J struct { diff --git a/docs/doc.md b/docs/doc.md index 875dd619..8cb53cc2 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -26,6 +26,7 @@ - [Custom Validators](#custom-validators) - [Only Bind Query String](#only-bind-query-string) - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Bind default value if none provided](#bind-default-value-if-none-provided) - [Collection format for arrays](#collection-format-for-arrays) - [Bind Uri](#bind-uri) - [Bind custom unmarshaler](#bind-custom-unmarshaler) @@ -862,6 +863,53 @@ Test it with: curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" ``` + +### Bind default value if none provided + +If the server should bind a default value to a field when the client does not provide one, specify the default value using the `default` key within the `form` tag: + +``` +package main + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name,default=William"` + Age int `form:"age,default=10"` + Friends []string `form:"friends,default=Will;Bill"` + Addresses [2]string `form:"addresses,default=foo bar" collection_format:"ssv"` + LapTimes []int `form:"lap_times,default=1;2;3" collection_format:"csv"` +} + +func main() { + g := gin.Default() + g.POST("/person", func(c *gin.Context) { + var req Person + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, err) + return + } + c.JSON(http.StatusOK, req) + }) + _ = g.Run("localhost:8080") +} +``` + +``` +curl -X POST http://localhost:8080/person +{"Name":"William","Age":10,"Friends":["Will","Bill"],"Colors":["red","blue"],"LapTimes":[1,2,3]} +``` + +NOTE: For default [collection values](#collection-format-for-arrays), the following rules apply: +- Since commas are used to delimit tag options, they are not supported within a default value and will result in undefined behavior +- For the collection formats "multi" and "csv", a semicolon should be used in place of a comma to delimited default values +- Since semicolons are used to delimit default values for "multi" and "csv", they are not supported within a default value for "multi" and "csv" + + #### Collection format for arrays | Format | Description | Example | From ad740d508f3e98b53ecafda35b66e6a32f6758ac Mon Sep 17 00:00:00 2001 From: wangjingcun Date: Fri, 25 Oct 2024 09:07:03 +0800 Subject: [PATCH 174/332] docs(context): fix some function names in comment (#4079) --- context_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context_test.go b/context_test.go index 211fbb1b..dda59978 100644 --- a/context_test.go +++ b/context_test.go @@ -1153,7 +1153,7 @@ func TestContextRenderNoContentHTMLString(t *testing.T) { assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } -// TestContextData tests that the response can be written from `bytestring` +// TestContextRenderData tests that the response can be written from `bytestring` // with specified MIME type func TestContextRenderData(t *testing.T) { w := httptest.NewRecorder() @@ -1550,7 +1550,7 @@ func TestContextIsAborted(t *testing.T) { assert.True(t, c.IsAborted()) } -// TestContextData tests that the response can be written from `bytestring` +// TestContextAbortWithStatus tests that the response can be written from `bytestring` // with specified MIME type func TestContextAbortWithStatus(t *testing.T) { w := httptest.NewRecorder() From b080116a7f5c71f023e1059ebb9e99a799938909 Mon Sep 17 00:00:00 2001 From: Enzo Lanzellotti <102574758+YlanzinhoY@users.noreply.github.com> Date: Thu, 24 Oct 2024 22:08:11 -0300 Subject: [PATCH 175/332] docs(readme): add Portuguese documentation. (#4078) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index faeb4952..0464107c 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in - [ํ•œ๊ตญ์–ด](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/) ### Articles From 299c6f30e3df4c5a257c517a91f421ff3ea63a8e Mon Sep 17 00:00:00 2001 From: tsukasa-ino Date: Fri, 25 Oct 2024 10:16:40 +0900 Subject: [PATCH 176/332] docs: trimmed some white spaces (#4070) --- docs/doc.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/doc.md b/docs/doc.md index 8cb53cc2..a463e820 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -172,7 +172,7 @@ func main() { router := gin.Default() // Query string parameters are parsed using the existing underlying request object. - // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe + // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") @@ -516,19 +516,19 @@ Sample Output ```go func main() { router := gin.New() - + // skip logging for desired paths by setting SkipPaths in LoggerConfig loggerConfig := gin.LoggerConfig{SkipPaths: []string{"/metrics"}} - + // skip logging based on your logic by setting Skip func in LoggerConfig loggerConfig.Skip = func(c *gin.Context) bool { // as an example skip non server side errors return c.Writer.Status() < http.StatusInternalServerError } - + router.Use(gin.LoggerWithConfig(loggerConfig)) router.Use(gin.Recovery()) - + // skipped router.GET("/metrics", func(c *gin.Context) { c.Status(http.StatusNotImplemented) @@ -543,7 +543,7 @@ func main() { router.GET("/data", func(c *gin.Context) { c.Status(http.StatusNotImplemented) }) - + router.Run(":8080") } @@ -615,7 +615,7 @@ You can also specify that specific fields are required. If a field is decorated ```go // Binding from JSON type Login struct { - User string `form:"user" json:"user" xml:"user" binding:"required"` + User string `form:"user" json:"user" xml:"user" binding:"required"` Password string `form:"password" json:"password" xml:"password" binding:"required"` } @@ -1252,7 +1252,7 @@ func main() { #### JSONP -Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. +Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. ```go func main() { @@ -1301,7 +1301,7 @@ func main() { #### PureJSON -Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. +Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. This feature is unavailable in Go 1.6 and lower. ```go @@ -1336,7 +1336,7 @@ func main() { router.StaticFS("/more_static", http.Dir("my_file_system")) router.StaticFile("/favicon.ico", "./resources/favicon.ico") router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system")) - + // Listen and serve on 0.0.0.0:8080 router.Run(":8080") } @@ -2320,7 +2320,7 @@ or network CIDRs from where clients which their request headers related to clien IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs. -**Attention:** Gin trust all proxies by default if you don't specify a trusted +**Attention:** Gin trust all proxies by default if you don't specify a trusted proxy using the function above, **this is NOT safe**. At the same time, if you don't use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`, then `Context.ClientIP()` will return the remote address directly to avoid some @@ -2349,7 +2349,7 @@ func main() { ``` **Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` -to skip TrustedProxies check, it has a higher priority than TrustedProxies. +to skip TrustedProxies check, it has a higher priority than TrustedProxies. Look at the example below: ```go From 647311aba203dd7262b24f973503e7689e00389d Mon Sep 17 00:00:00 2001 From: Xinyu Kuo Date: Fri, 25 Oct 2024 09:33:31 +0800 Subject: [PATCH 177/332] refactor(context): refactor context handling and improve test robustness (#4066) Use assert.InDelta for float comparison with tolerance in TestContextGetFloat32 Remove unnecessary blank line in TestContextInitQueryCache Replace anonymous struct with named contextKey type in TestContextWithFallbackValueFromRequestContext Update context key handling in TestContextWithFallbackValueFromRequestContext to use contextKey type --- context.go | 2 +- context_test.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 1f356515..77232f90 100644 --- a/context.go +++ b/context.go @@ -951,7 +951,7 @@ func (c *Context) ShouldBindBodyWithTOML(obj any) error { return c.ShouldBindBodyWith(obj, binding.TOML) } -// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). +// ShouldBindBodyWithPlain is a shortcut for c.ShouldBindBodyWith(obj, binding.Plain). func (c *Context) ShouldBindBodyWithPlain(obj any) error { return c.ShouldBindBodyWith(obj, binding.Plain) } diff --git a/context_test.go b/context_test.go index dda59978..62a1e14f 100644 --- a/context_test.go +++ b/context_test.go @@ -323,7 +323,7 @@ func TestContextGetFloat32(t *testing.T) { key := "float32" value := float32(3.14) c.Set(key, value) - assert.Equal(t, value, c.GetFloat32(key)) + assert.InDelta(t, value, c.GetFloat32(key), 0.01) } func TestContextGetFloat64(t *testing.T) { @@ -2857,7 +2857,8 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { { name: "c with struct context key", getContextAndKey: func() (*Context, any) { - var key struct{} + type KeyStruct struct{} // https://staticcheck.dev/docs/checks/#SA1029 + var key KeyStruct c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag c.engine.ContextWithFallback = true From 9d11234efec1e5517b2887a6e7dfbc9c017bc52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Karpi=C5=84ski?= Date: Sat, 26 Oct 2024 02:26:25 +0200 Subject: [PATCH 178/332] docs(gin): Replace broken link to documentation with valid (#4064) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 48cc15c9..e17596aa 100644 --- a/gin.go +++ b/gin.go @@ -598,7 +598,7 @@ func (engine *Engine) RunQUIC(addr, certFile, keyFile string) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } err = http3.ListenAndServeQUIC(addr, certFile, keyFile, engine.Handler()) From ea53388e6ee4a6a0a1647b390c56eeed780e7e56 Mon Sep 17 00:00:00 2001 From: Xinyu Kuo Date: Sat, 26 Oct 2024 08:28:59 +0800 Subject: [PATCH 179/332] fix(tree): Keep panic infos consistent when wildcard type build faild (#4077) --- .github/workflows/gin.yml | 2 +- tree.go | 2 +- tree_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 947abf9c..74983c50 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@v6 with: - version: v1.58.1 + version: v1.61.0 args: --verbose test: needs: lint diff --git a/tree.go b/tree.go index b0a5f982..0d3e5a8c 100644 --- a/tree.go +++ b/tree.go @@ -369,7 +369,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) // currently fixed width 1 for '/' i-- - if path[i] != '/' { + if i < 0 || path[i] != '/' { panic("no / before catch-all in path '" + fullPath + "'") } diff --git a/tree_test.go b/tree_test.go index 3aa3a594..74eb6104 100644 --- a/tree_test.go +++ b/tree_test.go @@ -993,3 +993,28 @@ func TestTreeInvalidEscape(t *testing.T) { } } } + +func TestWildcardInvalidSlash(t *testing.T) { + const panicMsgPrefix = "no / before catch-all in path" + + routes := map[string]bool{ + "/foo/bar": true, + "/foo/x*zy": false, + "/foo/b*r": false, + } + + for route, valid := range routes { + tree := &node{} + recv := catchPanic(func() { + tree.addRoute(route, nil) + }) + + if recv == nil != valid { + t.Fatalf("%s should be %t but got %v", route, valid, recv) + } + + if rs, ok := recv.(string); recv != nil && (!ok || !strings.HasPrefix(rs, panicMsgPrefix)) { + t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsgPrefix, route, recv) + } + } +} From c8a3adc65703d8958265c07689662e54f037038c Mon Sep 17 00:00:00 2001 From: Konovalov Maxim <43151027+KaymeKaydex@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:24:53 +0300 Subject: [PATCH 180/332] refactor(context): simplify "GetType()" functions (#4080) This PR introduces a generic function, getTyped[T any], to simplify value retrieval in the Context struct. It replaces repetitive type assertions in the GetString GetBool etc. methods. Co-authored-by: Maksim Konovalov --- context.go | 177 +++++++++++++++-------------------------------------- 1 file changed, 50 insertions(+), 127 deletions(-) diff --git a/context.go b/context.go index 77232f90..1e8c8e27 100644 --- a/context.go +++ b/context.go @@ -291,248 +291,171 @@ func (c *Context) MustGet(key string) any { panic("Key \"" + key + "\" does not exist") } -// GetString returns the value associated with the key as a string. -func (c *Context) GetString(key string) (s string) { +func getTyped[T any](c *Context, key string) (res T) { if val, ok := c.Get(key); ok && val != nil { - s, _ = val.(string) + res, _ = val.(T) } return } +// GetString returns the value associated with the key as a string. +func (c *Context) GetString(key string) (s string) { + return getTyped[string](c, key) +} + // GetBool returns the value associated with the key as a boolean. func (c *Context) GetBool(key string) (b bool) { - if val, ok := c.Get(key); ok && val != nil { - b, _ = val.(bool) - } - return + return getTyped[bool](c, key) } // GetInt returns the value associated with the key as an integer. func (c *Context) GetInt(key string) (i int) { - if val, ok := c.Get(key); ok && val != nil { - i, _ = val.(int) - } - return + return getTyped[int](c, key) } // GetInt8 returns the value associated with the key as an integer 8. func (c *Context) GetInt8(key string) (i8 int8) { - if val, ok := c.Get(key); ok && val != nil { - i8, _ = val.(int8) - } - return + return getTyped[int8](c, key) } // GetInt16 returns the value associated with the key as an integer 16. func (c *Context) GetInt16(key string) (i16 int16) { - if val, ok := c.Get(key); ok && val != nil { - i16, _ = val.(int16) - } - return + return getTyped[int16](c, key) } // GetInt32 returns the value associated with the key as an integer 32. func (c *Context) GetInt32(key string) (i32 int32) { - if val, ok := c.Get(key); ok && val != nil { - i32, _ = val.(int32) - } - return + return getTyped[int32](c, key) } // GetInt64 returns the value associated with the key as an integer 64. func (c *Context) GetInt64(key string) (i64 int64) { - if val, ok := c.Get(key); ok && val != nil { - i64, _ = val.(int64) - } - return + return getTyped[int64](c, key) } // GetUint returns the value associated with the key as an unsigned integer. func (c *Context) GetUint(key string) (ui uint) { - if val, ok := c.Get(key); ok && val != nil { - ui, _ = val.(uint) - } - return + return getTyped[uint](c, key) } // GetUint8 returns the value associated with the key as an unsigned integer 8. func (c *Context) GetUint8(key string) (ui8 uint8) { - if val, ok := c.Get(key); ok && val != nil { - ui8, _ = val.(uint8) - } - return + return getTyped[uint8](c, key) } // GetUint16 returns the value associated with the key as an unsigned integer 16. func (c *Context) GetUint16(key string) (ui16 uint16) { - if val, ok := c.Get(key); ok && val != nil { - ui16, _ = val.(uint16) - } - return + return getTyped[uint16](c, key) } // GetUint32 returns the value associated with the key as an unsigned integer 32. func (c *Context) GetUint32(key string) (ui32 uint32) { - if val, ok := c.Get(key); ok && val != nil { - ui32, _ = val.(uint32) - } - return + return getTyped[uint32](c, key) } // GetUint64 returns the value associated with the key as an unsigned integer 64. func (c *Context) GetUint64(key string) (ui64 uint64) { - if val, ok := c.Get(key); ok && val != nil { - ui64, _ = val.(uint64) - } - return + return getTyped[uint64](c, key) } // GetFloat32 returns the value associated with the key as a float32. func (c *Context) GetFloat32(key string) (f32 float32) { - if val, ok := c.Get(key); ok && val != nil { - f32, _ = val.(float32) - } - return + return getTyped[float32](c, key) } // GetFloat64 returns the value associated with the key as a float64. func (c *Context) GetFloat64(key string) (f64 float64) { - if val, ok := c.Get(key); ok && val != nil { - f64, _ = val.(float64) - } - return + return getTyped[float64](c, key) } // GetTime returns the value associated with the key as time. func (c *Context) GetTime(key string) (t time.Time) { - if val, ok := c.Get(key); ok && val != nil { - t, _ = val.(time.Time) - } - return + return getTyped[time.Time](c, key) } // GetDuration returns the value associated with the key as a duration. func (c *Context) GetDuration(key string) (d time.Duration) { - if val, ok := c.Get(key); ok && val != nil { - d, _ = val.(time.Duration) - } - return + return getTyped[time.Duration](c, key) } +// GetIntSlice returns the value associated with the key as a slice of integers. func (c *Context) GetIntSlice(key string) (is []int) { - if val, ok := c.Get(key); ok && val != nil { - is, _ = val.([]int) - } - return + return getTyped[[]int](c, key) } +// GetInt8Slice returns the value associated with the key as a slice of int8 integers. func (c *Context) GetInt8Slice(key string) (i8s []int8) { - if val, ok := c.Get(key); ok && val != nil { - i8s, _ = val.([]int8) - } - return + return getTyped[[]int8](c, key) } +// GetInt16Slice returns the value associated with the key as a slice of int16 integers. func (c *Context) GetInt16Slice(key string) (i16s []int16) { - if val, ok := c.Get(key); ok && val != nil { - i16s, _ = val.([]int16) - } - return + return getTyped[[]int16](c, key) } +// GetInt32Slice returns the value associated with the key as a slice of int32 integers. func (c *Context) GetInt32Slice(key string) (i32s []int32) { - if val, ok := c.Get(key); ok && val != nil { - i32s, _ = val.([]int32) - } - return + return getTyped[[]int32](c, key) } +// GetInt64Slice returns the value associated with the key as a slice of int64 integers. func (c *Context) GetInt64Slice(key string) (i64s []int64) { - if val, ok := c.Get(key); ok && val != nil { - i64s, _ = val.([]int64) - } - return + return getTyped[[]int64](c, key) } +// GetUintSlice returns the value associated with the key as a slice of unsigned integers. func (c *Context) GetUintSlice(key string) (uis []uint) { - if val, ok := c.Get(key); ok && val != nil { - uis, _ = val.([]uint) - } - return + return getTyped[[]uint](c, key) } +// GetUint8Slice returns the value associated with the key as a slice of uint8 integers. func (c *Context) GetUint8Slice(key string) (ui8s []uint8) { - if val, ok := c.Get(key); ok && val != nil { - ui8s, _ = val.([]uint8) - } - return + return getTyped[[]uint8](c, key) } +// GetUint16Slice returns the value associated with the key as a slice of uint16 integers. func (c *Context) GetUint16Slice(key string) (ui16s []uint16) { - if val, ok := c.Get(key); ok && val != nil { - ui16s, _ = val.([]uint16) - } - return + return getTyped[[]uint16](c, key) } +// GetUint32Slice returns the value associated with the key as a slice of uint32 integers. func (c *Context) GetUint32Slice(key string) (ui32s []uint32) { - if val, ok := c.Get(key); ok && val != nil { - ui32s, _ = val.([]uint32) - } - return + return getTyped[[]uint32](c, key) } +// GetUint64Slice returns the value associated with the key as a slice of uint64 integers. func (c *Context) GetUint64Slice(key string) (ui64s []uint64) { - if val, ok := c.Get(key); ok && val != nil { - ui64s, _ = val.([]uint64) - } - return + return getTyped[[]uint64](c, key) } +// GetFloat32Slice returns the value associated with the key as a slice of float32 numbers. func (c *Context) GetFloat32Slice(key string) (f32s []float32) { - if val, ok := c.Get(key); ok && val != nil { - f32s, _ = val.([]float32) - } - return + return getTyped[[]float32](c, key) } +// GetFloat64Slice returns the value associated with the key as a slice of float64 numbers. func (c *Context) GetFloat64Slice(key string) (f64s []float64) { - if val, ok := c.Get(key); ok && val != nil { - f64s, _ = val.([]float64) - } - return + return getTyped[[]float64](c, key) } // GetStringSlice returns the value associated with the key as a slice of strings. func (c *Context) GetStringSlice(key string) (ss []string) { - if val, ok := c.Get(key); ok && val != nil { - ss, _ = val.([]string) - } - return + return getTyped[[]string](c, key) } // GetStringMap returns the value associated with the key as a map of interfaces. func (c *Context) GetStringMap(key string) (sm map[string]any) { - if val, ok := c.Get(key); ok && val != nil { - sm, _ = val.(map[string]any) - } - return + 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 string) (sms map[string]string) { - if val, ok := c.Get(key); ok && val != nil { - sms, _ = val.(map[string]string) - } - return + 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 string) (smss map[string][]string) { - if val, ok := c.Get(key); ok && val != nil { - smss, _ = val.(map[string][]string) - } - return + return getTyped[map[string][]string](c, key) } /************************************/ From f875d8728306c2c2c6f504900ab08cd1d8c47f12 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 15 Nov 2024 23:49:08 +0800 Subject: [PATCH 181/332] chore(context): test context initialization and handler logic (#4087) * enhance code imported by #3413 if it needs to check if the handler is nil, tie c.index shall always ++ * test: refactor test context initialization and handler logic - Remove an empty line in `TestContextInitQueryCache` - Add `TestContextNext` function with tests for `Next` method behavior with no handlers, one handler, and multiple handlers Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu Co-authored-by: zjj --- context.go | 5 ++--- context_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/context.go b/context.go index 1e8c8e27..cab14529 100644 --- a/context.go +++ b/context.go @@ -186,10 +186,9 @@ func (c *Context) FullPath() string { func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { - if c.handlers[c.index] == nil { - continue + if c.handlers[c.index] != nil { + c.handlers[c.index](c) } - c.handlers[c.index](c) c.index++ } } diff --git a/context_test.go b/context_test.go index 62a1e14f..6921deab 100644 --- a/context_test.go +++ b/context_test.go @@ -610,7 +610,6 @@ func TestContextInitQueryCache(t *testing.T) { assert.Equal(t, test.expectedQueryCache, test.testContext.queryCache) }) } - } func TestContextDefaultQueryOnEmptyRequest(t *testing.T) { @@ -3038,3 +3037,47 @@ func TestInterceptedHeader(t *testing.T) { assert.Equal(t, "", w.Result().Header.Get("X-Test")) assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) } + +func TestContextNext(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + + // Test with no handlers + c.Next() + assert.Equal(t, int8(0), c.index) + + // Test with one handler + c.index = -1 + c.handlers = HandlersChain{func(c *Context) { + c.Set("key", "value") + }} + c.Next() + assert.Equal(t, int8(1), c.index) + value, exists := c.Get("key") + assert.True(t, exists) + assert.Equal(t, "value", value) + + // Test with multiple handlers + c.handlers = HandlersChain{ + func(c *Context) { + c.Set("key1", "value1") + c.Next() + c.Set("key2", "value2") + }, + nil, + func(c *Context) { + c.Set("key3", "value3") + }, + } + c.index = -1 + c.Next() + assert.Equal(t, int8(4), c.index) + value, exists = c.Get("key1") + assert.True(t, exists) + assert.Equal(t, "value1", value) + value, exists = c.Get("key2") + assert.True(t, exists) + assert.Equal(t, "value2", value) + value, exists = c.Get("key3") + assert.True(t, exists) + assert.Equal(t, "value3", value) +} From 02c1144f312eaf18767475a578bc421ddbcc4b82 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 15 Nov 2024 16:51:12 +0100 Subject: [PATCH 182/332] ci(lint): enable perfsprint linter (#4090) Signed-off-by: Matthieu MOREL --- .golangci.yml | 8 ++++++++ binding/form_mapping_test.go | 8 ++++---- context_test.go | 5 +++-- gin_test.go | 20 ++++++++++---------- githubapi_test.go | 5 +++-- recovery_test.go | 3 +-- routes_test.go | 4 ++-- 7 files changed, 31 insertions(+), 22 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 8d58c989..c3ae7275 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,6 +16,7 @@ linters: - nakedret - nilerr - nolintlint + - perfsprint - revive - testifylint - wastedassign @@ -34,6 +35,13 @@ linters-settings: - G112 - G201 - G203 + perfsprint: + err-error: true + errorf: true + fiximports: true + int-conversion: true + sprintf1: true + strconcat: true testifylint: enable-all: true diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 810315bb..1277fd5f 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -6,7 +6,7 @@ package binding import ( "encoding/hex" - "fmt" + "errors" "mime/multipart" "reflect" "strconv" @@ -494,7 +494,7 @@ type customUnmarshalParamType struct { func (f *customUnmarshalParamType) UnmarshalParam(param string) error { parts := strings.Split(param, ":") if len(parts) != 3 { - return fmt.Errorf("invalid format") + return errors.New("invalid format") } f.Protocol = parts[0] f.Path = parts[1] @@ -556,7 +556,7 @@ func (p *customPath) UnmarshalParam(param string) error { elems := strings.Split(param, "/") n := len(elems) if n < 2 { - return fmt.Errorf("invalid format") + return errors.New("invalid format") } *p = elems @@ -600,7 +600,7 @@ func (o *objectID) UnmarshalParam(param string) error { func convertTo(s string) (objectID, error) { var nilObjectID objectID if len(s) != 24 { - return nilObjectID, fmt.Errorf("invalid format") + return nilObjectID, errors.New("invalid format") } var oid [12]byte diff --git a/context_test.go b/context_test.go index 6921deab..7c414843 100644 --- a/context_test.go +++ b/context_test.go @@ -18,6 +18,7 @@ import ( "net/url" "os" "reflect" + "strconv" "strings" "sync" "testing" @@ -2633,7 +2634,7 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, body, w.Body.String()) assert.Equal(t, contentType, w.Header().Get("Content-Type")) - assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length")) + assert.Equal(t, strconv.FormatInt(contentLength, 10), w.Header().Get("Content-Length")) assert.Equal(t, extraHeaders["Content-Disposition"], w.Header().Get("Content-Disposition")) } @@ -2651,7 +2652,7 @@ func TestContextRenderDataFromReaderNoHeaders(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, body, w.Body.String()) assert.Equal(t, contentType, w.Header().Get("Content-Type")) - assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length")) + assert.Equal(t, strconv.FormatInt(contentLength, 10), w.Header().Get("Content-Length")) } type TestResponseRecorder struct { diff --git a/gin_test.go b/gin_test.go index 719f63e4..5d0c47d3 100644 --- a/gin_test.go +++ b/gin_test.go @@ -73,7 +73,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -131,7 +131,7 @@ func TestLoadHTMLGlobTestMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -151,7 +151,7 @@ func TestLoadHTMLGlobReleaseMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -178,7 +178,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) { }, } client := &http.Client{Transport: tr} - res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := client.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -198,7 +198,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) + res, err := http.Get(ts.URL + "/raw") if err != nil { t.Error(err) } @@ -229,7 +229,7 @@ func TestLoadHTMLFilesTestMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -249,7 +249,7 @@ func TestLoadHTMLFilesDebugMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -269,7 +269,7 @@ func TestLoadHTMLFilesReleaseMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -296,7 +296,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) { }, } client := &http.Client{Transport: tr} - res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := client.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -316,7 +316,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) + res, err := http.Get(ts.URL + "/raw") if err != nil { t.Error(err) } diff --git a/githubapi_test.go b/githubapi_test.go index 6d348787..0c86af2e 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httptest" "os" + "strconv" "strings" "testing" @@ -411,7 +412,7 @@ func exampleFromPath(path string) (string, Params) { } if start >= 0 { if c == '/' { - value := fmt.Sprint(rand.Intn(100000)) + value := strconv.Itoa(rand.Intn(100000)) params = append(params, Param{ Key: path[start:i], Value: value, @@ -425,7 +426,7 @@ func exampleFromPath(path string) (string, Params) { } } if start >= 0 { - value := fmt.Sprint(rand.Intn(100000)) + value := strconv.Itoa(rand.Intn(100000)) params = append(params, Param{ Key: path[start:], Value: value, diff --git a/recovery_test.go b/recovery_test.go index fa8ab894..ee063cd1 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -5,7 +5,6 @@ package gin import ( - "fmt" "net" "net/http" "os" @@ -33,7 +32,7 @@ func TestPanicClean(t *testing.T) { }, header{ Key: "Authorization", - Value: fmt.Sprintf("Bearer %s", password), + Value: "Bearer " + password, }, header{ Key: "Content-Type", diff --git a/routes_test.go b/routes_test.go index d6233b09..49f355a7 100644 --- a/routes_test.go +++ b/routes_test.go @@ -560,7 +560,7 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) { w := PerformRequest(router, "GET", tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { - assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) + assert.Equal(t, tr.location, w.Header().Get("Location")) } } } @@ -590,7 +590,7 @@ func TestRouterNotFound(t *testing.T) { w := PerformRequest(router, http.MethodGet, tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { - assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) + assert.Equal(t, tr.location, w.Header().Get("Location")) } } From e8d34d053f7008858886b8e4f76b3e8564105870 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 15 Nov 2024 16:52:16 +0100 Subject: [PATCH 183/332] ci(lint): enable usestdlibvars linter (#4091) Signed-off-by: Matthieu MOREL --- .golangci.yml | 1 + auth_test.go | 10 +- benchmarks_test.go | 24 +-- binding/binding_msgpack_test.go | 9 +- binding/binding_test.go | 232 ++++++++++++------------- binding/multipart_form_mapping_test.go | 2 +- context_test.go | 154 ++++++++-------- debug_test.go | 5 +- deprecated_test.go | 2 +- gin_test.go | 48 ++--- logger_test.go | 84 ++++----- middleware_test.go | 16 +- recovery_test.go | 22 +-- render/render_test.go | 2 +- routes_test.go | 8 +- utils_test.go | 12 +- 16 files changed, 317 insertions(+), 314 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index c3ae7275..ccb26684 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,6 +19,7 @@ linters: - perfsprint - revive - testifylint + - usestdlibvars - wastedassign linters-settings: diff --git a/auth_test.go b/auth_test.go index f7175929..9166e3b0 100644 --- a/auth_test.go +++ b/auth_test.go @@ -90,7 +90,7 @@ func TestBasicAuthSucceed(t *testing.T) { }) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/login", nil) + req, _ := http.NewRequest(http.MethodGet, "/login", nil) req.Header.Set("Authorization", authorizationHeader("admin", "password")) router.ServeHTTP(w, req) @@ -109,7 +109,7 @@ func TestBasicAuth401(t *testing.T) { }) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/login", nil) + req, _ := http.NewRequest(http.MethodGet, "/login", nil) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) router.ServeHTTP(w, req) @@ -129,7 +129,7 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { }) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/login", nil) + req, _ := http.NewRequest(http.MethodGet, "/login", nil) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) router.ServeHTTP(w, req) @@ -147,7 +147,7 @@ func TestBasicAuthForProxySucceed(t *testing.T) { }) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("Proxy-Authorization", authorizationHeader("admin", "password")) router.ServeHTTP(w, req) @@ -166,7 +166,7 @@ func TestBasicAuthForProxy407(t *testing.T) { }) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) router.ServeHTTP(w, req) diff --git a/benchmarks_test.go b/benchmarks_test.go index 5b7929b8..3a8d53f3 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -14,21 +14,21 @@ import ( func BenchmarkOneRoute(B *testing.B) { router := New() router.GET("/ping", func(c *Context) {}) - runRequest(B, router, "GET", "/ping") + runRequest(B, router, http.MethodGet, "/ping") } func BenchmarkRecoveryMiddleware(B *testing.B) { router := New() router.Use(Recovery()) router.GET("/", func(c *Context) {}) - runRequest(B, router, "GET", "/") + runRequest(B, router, http.MethodGet, "/") } func BenchmarkLoggerMiddleware(B *testing.B) { router := New() router.Use(LoggerWithWriter(newMockWriter())) router.GET("/", func(c *Context) {}) - runRequest(B, router, "GET", "/") + runRequest(B, router, http.MethodGet, "/") } func BenchmarkManyHandlers(B *testing.B) { @@ -37,7 +37,7 @@ func BenchmarkManyHandlers(B *testing.B) { router.Use(func(c *Context) {}) router.Use(func(c *Context) {}) router.GET("/ping", func(c *Context) {}) - runRequest(B, router, "GET", "/ping") + runRequest(B, router, http.MethodGet, "/ping") } func Benchmark5Params(B *testing.B) { @@ -45,7 +45,7 @@ func Benchmark5Params(B *testing.B) { router := New() router.Use(func(c *Context) {}) router.GET("/param/:param1/:params2/:param3/:param4/:param5", func(c *Context) {}) - runRequest(B, router, "GET", "/param/path/to/parameter/john/12345") + runRequest(B, router, http.MethodGet, "/param/path/to/parameter/john/12345") } func BenchmarkOneRouteJSON(B *testing.B) { @@ -56,7 +56,7 @@ func BenchmarkOneRouteJSON(B *testing.B) { router.GET("/json", func(c *Context) { c.JSON(http.StatusOK, data) }) - runRequest(B, router, "GET", "/json") + runRequest(B, router, http.MethodGet, "/json") } func BenchmarkOneRouteHTML(B *testing.B) { @@ -68,7 +68,7 @@ func BenchmarkOneRouteHTML(B *testing.B) { router.GET("/html", func(c *Context) { c.HTML(http.StatusOK, "index", "hola") }) - runRequest(B, router, "GET", "/html") + runRequest(B, router, http.MethodGet, "/html") } func BenchmarkOneRouteSet(B *testing.B) { @@ -76,7 +76,7 @@ func BenchmarkOneRouteSet(B *testing.B) { router.GET("/ping", func(c *Context) { c.Set("key", "value") }) - runRequest(B, router, "GET", "/ping") + runRequest(B, router, http.MethodGet, "/ping") } func BenchmarkOneRouteString(B *testing.B) { @@ -84,13 +84,13 @@ func BenchmarkOneRouteString(B *testing.B) { router.GET("/text", func(c *Context) { c.String(http.StatusOK, "this is a plain text") }) - runRequest(B, router, "GET", "/text") + runRequest(B, router, http.MethodGet, "/text") } func BenchmarkManyRoutesFist(B *testing.B) { router := New() router.Any("/ping", func(c *Context) {}) - runRequest(B, router, "GET", "/ping") + runRequest(B, router, http.MethodGet, "/ping") } func BenchmarkManyRoutesLast(B *testing.B) { @@ -103,7 +103,7 @@ func Benchmark404(B *testing.B) { router := New() router.Any("/something", func(c *Context) {}) router.NoRoute(func(c *Context) {}) - runRequest(B, router, "GET", "/ping") + runRequest(B, router, http.MethodGet, "/ping") } func Benchmark404Many(B *testing.B) { @@ -118,7 +118,7 @@ func Benchmark404Many(B *testing.B) { router.GET("/user/:id/:mode", func(c *Context) {}) router.NoRoute(func(c *Context) {}) - runRequest(B, router, "GET", "/viewfake") + runRequest(B, router, http.MethodGet, "/viewfake") } type mockWriter struct { diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go index a8116391..7a5db34b 100644 --- a/binding/binding_msgpack_test.go +++ b/binding/binding_msgpack_test.go @@ -8,6 +8,7 @@ package binding import ( "bytes" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -39,20 +40,20 @@ func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, assert.Equal(t, name, b.Name()) obj := FooStruct{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) req.Header.Add("Content-Type", MIMEMSGPACK) err := b.Bind(req, &obj) require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) req.Header.Add("Content-Type", MIMEMSGPACK) err = MsgPack.Bind(req, &obj) require.Error(t, err) } func TestBindingDefaultMsgPack(t *testing.T) { - assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) - assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) + assert.Equal(t, MsgPack, Default(http.MethodPost, MIMEMSGPACK)) + assert.Equal(t, MsgPack, Default(http.MethodPut, MIMEMSGPACK2)) } diff --git a/binding/binding_test.go b/binding/binding_test.go index 2036b59b..901e9740 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -145,31 +145,31 @@ type FooStructForMapPtrType struct { } func TestBindingDefault(t *testing.T) { - assert.Equal(t, Form, Default("GET", "")) - assert.Equal(t, Form, Default("GET", MIMEJSON)) + assert.Equal(t, Form, Default(http.MethodGet, "")) + assert.Equal(t, Form, Default(http.MethodGet, MIMEJSON)) - assert.Equal(t, JSON, Default("POST", MIMEJSON)) - assert.Equal(t, JSON, Default("PUT", MIMEJSON)) + assert.Equal(t, JSON, Default(http.MethodPost, MIMEJSON)) + assert.Equal(t, JSON, Default(http.MethodPut, MIMEJSON)) - assert.Equal(t, XML, Default("POST", MIMEXML)) - assert.Equal(t, XML, Default("PUT", MIMEXML2)) + assert.Equal(t, XML, Default(http.MethodPost, MIMEXML)) + assert.Equal(t, XML, Default(http.MethodPut, MIMEXML2)) - assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) - assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) + assert.Equal(t, Form, Default(http.MethodPost, MIMEPOSTForm)) + assert.Equal(t, Form, Default(http.MethodPut, MIMEPOSTForm)) - assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm)) - assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default(http.MethodPost, MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default(http.MethodPut, MIMEMultipartPOSTForm)) - assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) - assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) + assert.Equal(t, ProtoBuf, Default(http.MethodPost, MIMEPROTOBUF)) + assert.Equal(t, ProtoBuf, Default(http.MethodPut, MIMEPROTOBUF)) - assert.Equal(t, YAML, Default("POST", MIMEYAML)) - assert.Equal(t, YAML, Default("PUT", MIMEYAML)) - assert.Equal(t, YAML, Default("POST", MIMEYAML2)) - assert.Equal(t, YAML, Default("PUT", MIMEYAML2)) + assert.Equal(t, YAML, Default(http.MethodPost, MIMEYAML)) + assert.Equal(t, YAML, Default(http.MethodPut, MIMEYAML)) + assert.Equal(t, YAML, Default(http.MethodPost, MIMEYAML2)) + assert.Equal(t, YAML, Default(http.MethodPut, MIMEYAML2)) - assert.Equal(t, TOML, Default("POST", MIMETOML)) - assert.Equal(t, TOML, Default("PUT", MIMETOML)) + assert.Equal(t, TOML, Default(http.MethodPost, MIMETOML)) + assert.Equal(t, TOML, Default(http.MethodPut, MIMETOML)) } func TestBindingJSONNilBody(t *testing.T) { @@ -227,137 +227,137 @@ func TestBindingJSONStringMap(t *testing.T) { } func TestBindingForm(t *testing.T) { - testFormBinding(t, "POST", + testFormBinding(t, http.MethodPost, "/", "/", "foo=bar&bar=foo", "bar2=foo") } func TestBindingForm2(t *testing.T) { - testFormBinding(t, "GET", + testFormBinding(t, http.MethodGet, "/?foo=bar&bar=foo", "/?bar2=foo", "", "") } func TestBindingFormEmbeddedStruct(t *testing.T) { - testFormBindingEmbeddedStruct(t, "POST", + testFormBindingEmbeddedStruct(t, http.MethodPost, "/", "/", "page=1&size=2&appkey=test-appkey", "bar2=foo") } func TestBindingFormEmbeddedStruct2(t *testing.T) { - testFormBindingEmbeddedStruct(t, "GET", + testFormBindingEmbeddedStruct(t, http.MethodGet, "/?page=1&size=2&appkey=test-appkey", "/?bar2=foo", "", "") } func TestBindingFormDefaultValue(t *testing.T) { - testFormBindingDefaultValue(t, "POST", + testFormBindingDefaultValue(t, http.MethodPost, "/", "/", "foo=bar", "bar2=foo") } func TestBindingFormDefaultValue2(t *testing.T) { - testFormBindingDefaultValue(t, "GET", + testFormBindingDefaultValue(t, http.MethodGet, "/?foo=bar", "/?bar2=foo", "", "") } func TestBindingFormForTime(t *testing.T) { - testFormBindingForTime(t, "POST", + testFormBindingForTime(t, http.MethodPost, "/", "/", "time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "bar2=foo") - testFormBindingForTimeNotUnixFormat(t, "POST", + testFormBindingForTimeNotUnixFormat(t, http.MethodPost, "/", "/", "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") - testFormBindingForTimeNotFormat(t, "POST", + testFormBindingForTimeNotFormat(t, http.MethodPost, "/", "/", "time_foo=2017-11-15", "bar2=foo") - testFormBindingForTimeFailFormat(t, "POST", + testFormBindingForTimeFailFormat(t, http.MethodPost, "/", "/", "time_foo=2017-11-15", "bar2=foo") - testFormBindingForTimeFailLocation(t, "POST", + testFormBindingForTimeFailLocation(t, http.MethodPost, "/", "/", "time_foo=2017-11-15", "bar2=foo") } func TestBindingFormForTime2(t *testing.T) { - testFormBindingForTime(t, "GET", + testFormBindingForTime(t, http.MethodGet, "/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "/?bar2=foo", "", "") - testFormBindingForTimeNotUnixFormat(t, "POST", + testFormBindingForTimeNotUnixFormat(t, http.MethodPost, "/", "/", "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") - testFormBindingForTimeNotFormat(t, "GET", + testFormBindingForTimeNotFormat(t, http.MethodGet, "/?time_foo=2017-11-15", "/?bar2=foo", "", "") - testFormBindingForTimeFailFormat(t, "GET", + testFormBindingForTimeFailFormat(t, http.MethodGet, "/?time_foo=2017-11-15", "/?bar2=foo", "", "") - testFormBindingForTimeFailLocation(t, "GET", + testFormBindingForTimeFailLocation(t, http.MethodGet, "/?time_foo=2017-11-15", "/?bar2=foo", "", "") } func TestFormBindingIgnoreField(t *testing.T) { - testFormBindingIgnoreField(t, "POST", + testFormBindingIgnoreField(t, http.MethodPost, "/", "/", "-=bar", "") } func TestBindingFormInvalidName(t *testing.T) { - testFormBindingInvalidName(t, "POST", + testFormBindingInvalidName(t, http.MethodPost, "/", "/", "test_name=bar", "bar2=foo") } func TestBindingFormInvalidName2(t *testing.T) { - testFormBindingInvalidName2(t, "POST", + testFormBindingInvalidName2(t, http.MethodPost, "/", "/", "map_foo=bar", "bar2=foo") } func TestBindingFormForType(t *testing.T) { - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "map_foo={\"bar\":123}", "map_foo=1", "Map") - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "slice_foo=1&slice_foo=2", "bar2=1&bar2=2", "Slice") - testFormBindingForType(t, "GET", + testFormBindingForType(t, http.MethodGet, "/?slice_foo=1&slice_foo=2", "/?bar2=1&bar2=2", "", "", "Slice") - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "slice_map_foo=1&slice_map_foo=2", "bar2=1&bar2=2", "SliceMap") - testFormBindingForType(t, "GET", + testFormBindingForType(t, http.MethodGet, "/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2", "", "", "SliceMap") - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "ptr_bar=test", "bar2=test", "Ptr") - testFormBindingForType(t, "GET", + testFormBindingForType(t, http.MethodGet, "/?ptr_bar=test", "/?bar2=test", "", "", "Ptr") - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "idx=123", "id1=1", "Struct") - testFormBindingForType(t, "GET", + testFormBindingForType(t, http.MethodGet, "/?idx=123", "/?id1=1", "", "", "Struct") - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "name=thinkerou", "name1=ou", "StructPointer") - testFormBindingForType(t, "GET", + testFormBindingForType(t, http.MethodGet, "/?name=thinkerou", "/?name1=ou", "", "", "StructPointer") } @@ -374,7 +374,7 @@ func TestBindingFormStringMap(t *testing.T) { func TestBindingFormStringSliceMap(t *testing.T) { obj := make(map[string][]string) - req := requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") + req := requestWithBody(http.MethodPost, "/", "foo=something&foo=bar&hello=world") req.Header.Add("Content-Type", MIMEPOSTForm) err := Form.Bind(req, &obj) require.NoError(t, err) @@ -387,38 +387,38 @@ func TestBindingFormStringSliceMap(t *testing.T) { assert.True(t, reflect.DeepEqual(obj, target)) objInvalid := make(map[string][]int) - req = requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") + req = requestWithBody(http.MethodPost, "/", "foo=something&foo=bar&hello=world") req.Header.Add("Content-Type", MIMEPOSTForm) err = Form.Bind(req, &objInvalid) require.Error(t, err) } func TestBindingQuery(t *testing.T) { - testQueryBinding(t, "POST", + testQueryBinding(t, http.MethodPost, "/?foo=bar&bar=foo", "/", "foo=unused", "bar2=foo") } func TestBindingQuery2(t *testing.T) { - testQueryBinding(t, "GET", + testQueryBinding(t, http.MethodGet, "/?foo=bar&bar=foo", "/?bar2=foo", "foo=unused", "") } func TestBindingQueryFail(t *testing.T) { - testQueryBindingFail(t, "POST", + testQueryBindingFail(t, http.MethodPost, "/?map_foo=", "/", "map_foo=unused", "bar2=foo") } func TestBindingQueryFail2(t *testing.T) { - testQueryBindingFail(t, "GET", + testQueryBindingFail(t, http.MethodGet, "/?map_foo=", "/?bar2=foo", "map_foo=unused", "") } func TestBindingQueryBoolFail(t *testing.T) { - testQueryBindingBoolFail(t, "GET", + testQueryBindingBoolFail(t, http.MethodGet, "/?bool_foo=fasl", "/?bar2=foo", "bool_foo=unused", "") } @@ -427,7 +427,7 @@ func TestBindingQueryStringMap(t *testing.T) { b := Query obj := make(map[string]string) - req := requestWithBody("GET", "/?foo=bar&hello=world", "") + req := requestWithBody(http.MethodGet, "/?foo=bar&hello=world", "") err := b.Bind(req, &obj) require.NoError(t, err) assert.NotNil(t, obj) @@ -436,7 +436,7 @@ func TestBindingQueryStringMap(t *testing.T) { assert.Equal(t, "world", obj["hello"]) obj = make(map[string]string) - req = requestWithBody("GET", "/?foo=bar&foo=2&hello=world", "") // should pick last + req = requestWithBody(http.MethodGet, "/?foo=bar&foo=2&hello=world", "") // should pick last err = b.Bind(req, &obj) require.NoError(t, err) assert.NotNil(t, obj) @@ -495,28 +495,28 @@ func TestBindingYAMLFail(t *testing.T) { } func createFormPostRequest(t *testing.T) *http.Request { - req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) + req, err := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createDefaultFormPostRequest(t *testing.T) *http.Request { - req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) + req, err := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestForMap(t *testing.T) *http.Request { - req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) + req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestForMapFail(t *testing.T) *http.Request { - req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) + req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req @@ -540,7 +540,7 @@ func createFormFilesMultipartRequest(t *testing.T) *http.Request { _, err = io.Copy(fw, f) require.NoError(t, err) - req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + req, err2 := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", body) require.NoError(t, err2) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) @@ -565,7 +565,7 @@ func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { _, err = io.Copy(fw, f) require.NoError(t, err) - req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + req, err2 := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", body) require.NoError(t, err2) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) @@ -581,7 +581,7 @@ func createFormMultipartRequest(t *testing.T) *http.Request { require.NoError(t, mw.SetBoundary(boundary)) require.NoError(t, mw.WriteField("foo", "bar")) require.NoError(t, mw.WriteField("bar", "foo")) - req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + req, err := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", body) require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -595,7 +595,7 @@ func createFormMultipartRequestForMap(t *testing.T) *http.Request { require.NoError(t, mw.SetBoundary(boundary)) require.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")) - req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) + req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", body) require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -609,7 +609,7 @@ func createFormMultipartRequestForMapFail(t *testing.T) *http.Request { require.NoError(t, mw.SetBoundary(boundary)) require.NoError(t, mw.WriteField("map_foo", "3.14")) - req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) + req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", body) require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -731,7 +731,7 @@ func TestBindingProtoBufFail(t *testing.T) { func TestValidationFails(t *testing.T) { var obj FooStruct - req := requestWithBody("POST", "/", `{"bar": "foo"}`) + req := requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`) err := JSON.Bind(req, &obj) require.Error(t, err) } @@ -742,7 +742,7 @@ func TestValidationDisabled(t *testing.T) { defer func() { Validator = backup }() var obj FooStruct - req := requestWithBody("POST", "/", `{"bar": "foo"}`) + req := requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`) err := JSON.Bind(req, &obj) require.NoError(t, err) } @@ -753,7 +753,7 @@ func TestRequiredSucceeds(t *testing.T) { } var obj HogeStruct - req := requestWithBody("POST", "/", `{"hoge": 0}`) + req := requestWithBody(http.MethodPost, "/", `{"hoge": 0}`) err := JSON.Bind(req, &obj) require.NoError(t, err) } @@ -764,7 +764,7 @@ func TestRequiredFails(t *testing.T) { } var obj HogeStruct - req := requestWithBody("POST", "/", `{"boen": 0}`) + req := requestWithBody(http.MethodPost, "/", `{"boen": 0}`) err := JSON.Bind(req, &obj) require.Error(t, err) } @@ -778,12 +778,12 @@ func TestHeaderBinding(t *testing.T) { } var theader tHeader - req := requestWithBody("GET", "/", "") + req := requestWithBody(http.MethodGet, "/", "") req.Header.Add("limit", "1000") require.NoError(t, h.Bind(req, &theader)) assert.Equal(t, 1000, theader.Limit) - req = requestWithBody("GET", "/", "") + req = requestWithBody(http.MethodGet, "/", "") req.Header.Add("fail", `{fail:fail}`) type failStruct struct { @@ -843,7 +843,7 @@ func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, ba obj := QueryTest{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -859,7 +859,7 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) obj := FooBarStruct{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -879,7 +879,7 @@ func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badB obj := FooDefaultBarStruct{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -898,14 +898,14 @@ func TestFormBindingFail(t *testing.T) { assert.Equal(t, "form", b.Name()) obj := FooBarStruct{} - req, _ := http.NewRequest("POST", "/", nil) + req, _ := http.NewRequest(http.MethodPost, "/", nil) err := b.Bind(req, &obj) require.Error(t, err) } func TestFormBindingMultipartFail(t *testing.T) { obj := FooBarStruct{} - req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar")) + req, err := http.NewRequest(http.MethodPost, "/", strings.NewReader("foo=bar")) require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary") _, err = req.MultipartReader() @@ -919,7 +919,7 @@ func TestFormPostBindingFail(t *testing.T) { assert.Equal(t, "form-urlencoded", b.Name()) obj := FooBarStruct{} - req, _ := http.NewRequest("POST", "/", nil) + req, _ := http.NewRequest(http.MethodPost, "/", nil) err := b.Bind(req, &obj) require.Error(t, err) } @@ -929,7 +929,7 @@ func TestFormMultipartBindingFail(t *testing.T) { assert.Equal(t, "multipart/form-data", b.Name()) obj := FooBarStruct{} - req, _ := http.NewRequest("POST", "/", nil) + req, _ := http.NewRequest(http.MethodPost, "/", nil) err := b.Bind(req, &obj) require.Error(t, err) } @@ -940,7 +940,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForTimeType{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -965,7 +965,7 @@ func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, bo obj := FooStructForTimeTypeNotUnixFormat{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -983,7 +983,7 @@ func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, obj := FooStructForTimeTypeNotFormat{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1001,7 +1001,7 @@ func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, obj := FooStructForTimeTypeFailFormat{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1019,7 +1019,7 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod obj := FooStructForTimeTypeFailLocation{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1037,7 +1037,7 @@ func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBo obj := FooStructForIgnoreFormTag{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1052,7 +1052,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo obj := InvalidNameType{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1071,7 +1071,7 @@ func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badB obj := InvalidNameMapType{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1088,7 +1088,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s assert.Equal(t, "form", b.Name()) req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } switch typ { @@ -1159,7 +1159,7 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) obj := FooBarStruct{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1174,7 +1174,7 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str obj := FooStructForMapType{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1187,7 +1187,7 @@ func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody obj := FooStructForBoolType{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1198,13 +1198,13 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody assert.Equal(t, name, b.Name()) obj := FooStruct{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) err := b.Bind(req, &obj) require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj) require.Error(t, err) } @@ -1213,19 +1213,19 @@ func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, ba assert.Equal(t, name, b.Name()) var obj1 []FooStruct - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) err := b.Bind(req, &obj1) require.NoError(t, err) var obj2 []FooStruct - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj2) require.Error(t, err) } func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) { obj := make(map[string]string) - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) if b.Name() == "form" { req.Header.Add("Content-Type", MIMEPOSTForm) } @@ -1238,13 +1238,13 @@ func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badB if badPath != "" && badBody != "" { obj = make(map[string]string) - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = b.Bind(req, &obj) require.Error(t, err) } objInt := make(map[string]int) - req = requestWithBody("POST", path, body) + req = requestWithBody(http.MethodPost, path, body) err = b.Bind(req, &objInt) require.Error(t, err) } @@ -1253,7 +1253,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) EnableDecoderUseNumber = true err := b.Bind(req, &obj) require.NoError(t, err) @@ -1263,7 +1263,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body assert.Equal(t, int64(123), v) obj = FooStructUseNumber{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj) require.Error(t, err) } @@ -1272,7 +1272,7 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) EnableDecoderUseNumber = false err := b.Bind(req, &obj) require.NoError(t, err) @@ -1281,7 +1281,7 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod assert.InDelta(t, float64(123), obj.Foo, 0.01) obj = FooStructUseNumber{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj) require.Error(t, err) } @@ -1293,13 +1293,13 @@ func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath }() obj := FooStructDisallowUnknownFields{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) err := b.Bind(req, &obj) require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStructDisallowUnknownFields{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj) require.Error(t, err) assert.Contains(t, err.Error(), "what") @@ -1309,13 +1309,13 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad assert.Equal(t, name, b.Name()) obj := FooStruct{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) err := b.Bind(req, &obj) require.Error(t, err) assert.Equal(t, "", obj.Foo) obj = FooStruct{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj) require.Error(t, err) } @@ -1324,14 +1324,14 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba assert.Equal(t, name, b.Name()) obj := protoexample.Test{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) require.NoError(t, err) assert.Equal(t, "yes", *obj.Label) obj = protoexample.Test{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) require.Error(t, err) @@ -1358,28 +1358,28 @@ func TestPlainBinding(t *testing.T) { assert.Equal(t, "plain", p.Name()) var s string - req := requestWithBody("POST", "/", "test string") + req := requestWithBody(http.MethodPost, "/", "test string") require.NoError(t, p.Bind(req, &s)) assert.Equal(t, "test string", s) var bs []byte - req = requestWithBody("POST", "/", "test []byte") + req = requestWithBody(http.MethodPost, "/", "test []byte") require.NoError(t, p.Bind(req, &bs)) assert.Equal(t, bs, []byte("test []byte")) var i int - req = requestWithBody("POST", "/", "test fail") + req = requestWithBody(http.MethodPost, "/", "test fail") require.Error(t, p.Bind(req, &i)) - req = requestWithBody("POST", "/", "") + req = requestWithBody(http.MethodPost, "/", "") req.Body = &failRead{} require.Error(t, p.Bind(req, &s)) - req = requestWithBody("POST", "/", "") + req = requestWithBody(http.MethodPost, "/", "") require.NoError(t, p.Bind(req, nil)) var ptr *string - req = requestWithBody("POST", "/", "") + req = requestWithBody(http.MethodPost, "/", "") require.NoError(t, p.Bind(req, ptr)) } @@ -1387,7 +1387,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body assert.Equal(t, name, b.Name()) obj := protoexample.Test{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) req.Body = io.NopCloser(&hook{}) req.Header.Add("Content-Type", MIMEPROTOBUF) @@ -1402,7 +1402,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body assert.Equal(t, "obj is not ProtoMessage", err.Error()) obj = protoexample.Test{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) require.Error(t, err) diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go index 9782b81d..c93f2141 100644 --- a/binding/multipart_form_mapping_test.go +++ b/binding/multipart_form_mapping_test.go @@ -116,7 +116,7 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request err := mw.Close() require.NoError(t, err) - req, err := http.NewRequest("POST", "/", &body) + req, err := http.NewRequest(http.MethodPost, "/", &body) require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary()) diff --git a/context_test.go b/context_test.go index 7c414843..91d5e898 100644 --- a/context_test.go +++ b/context_test.go @@ -60,7 +60,7 @@ func createMultipartRequest() *http.Request { must(mw.WriteField("time_location", "31/12/2016 14:55")) must(mw.WriteField("names[a]", "thinkerou")) must(mw.WriteField("names[b]", "tianou")) - req, err := http.NewRequest("POST", "/", body) + req, err := http.NewRequest(http.MethodPost, "/", body) must(err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -81,7 +81,7 @@ func TestContextFormFile(t *testing.T) { require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") require.NoError(t, err) @@ -95,7 +95,7 @@ func TestContextFormFileFailed(t *testing.T) { mw := multipart.NewWriter(buf) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) c.engine.MaxMultipartMemory = 8 << 20 f, err := c.FormFile("file") @@ -113,7 +113,7 @@ func TestContextMultipartForm(t *testing.T) { require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.MultipartForm() require.NoError(t, err) @@ -128,7 +128,7 @@ func TestSaveUploadedOpenFailed(t *testing.T) { mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f := &multipart.FileHeader{ @@ -146,7 +146,7 @@ func TestSaveUploadedCreateFailed(t *testing.T) { require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") require.NoError(t, err) @@ -481,7 +481,7 @@ func TestContextGetStringMapStringSlice(t *testing.T) { func TestContextCopy(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.index = 2 - c.Request, _ = http.NewRequest("POST", "/hola", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/hola", nil) c.handlers = HandlersChain{func(c *Context) {}} c.Params = Params{Param{Key: "foo", Value: "bar"}} c.Set("foo", "bar") @@ -538,7 +538,7 @@ func TestContextHandler(t *testing.T) { func TestContextQuery(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10&id=", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "http://example.com/?foo=bar&page=10&id=", nil) value, ok := c.GetQuery("foo") assert.True(t, ok) @@ -631,7 +631,7 @@ func TestContextDefaultQueryOnEmptyRequest(t *testing.T) { func TestContextQueryAndPostForm(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") - c.Request, _ = http.NewRequest("POST", + c.Request, _ = http.NewRequest(http.MethodPost, "/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) @@ -651,7 +651,7 @@ func TestContextQueryAndPostForm(t *testing.T) { assert.Empty(t, value) assert.Empty(t, c.PostForm("both")) assert.Empty(t, c.DefaultPostForm("both", "nothing")) - assert.Equal(t, "GET", c.Query("both"), "GET") + assert.Equal(t, http.MethodGet, c.Query("both"), http.MethodGet) value, ok = c.GetQuery("id") assert.True(t, ok) @@ -699,7 +699,7 @@ func TestContextQueryAndPostForm(t *testing.T) { values = c.QueryArray("both") assert.Len(t, values, 1) - assert.Equal(t, "GET", values[0]) + assert.Equal(t, http.MethodGet, values[0]) dicts, ok := c.GetQueryMap("ids") assert.True(t, ok) @@ -834,7 +834,7 @@ func TestContextSetCookiePathEmpty(t *testing.T) { func TestContextGetCookie(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("GET", "/get", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/get", nil) c.Request.Header.Set("Cookie", "user=gin") cookie, _ := c.Cookie("user") assert.Equal(t, "gin", cookie) @@ -886,7 +886,7 @@ func TestContextRenderJSON(t *testing.T) { func TestContextRenderJSONP(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "http://example.com/?callback=x", nil) c.JSONP(http.StatusCreated, H{"foo": "bar"}) @@ -900,7 +900,7 @@ func TestContextRenderJSONP(t *testing.T) { func TestContextRenderJSONPWithoutCallback(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("GET", "http://example.com", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "http://example.com", nil) c.JSONP(http.StatusCreated, H{"foo": "bar"}) @@ -1043,7 +1043,7 @@ func TestContextRenderHTML2(t *testing.T) { c, router := CreateTestContext(w) // print debug warning log when Engine.trees > 0 - router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodGet, "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 1) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) @@ -1199,7 +1199,7 @@ func TestContextRenderFile(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("GET", "/", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c.File("./gin.go") assert.Equal(t, http.StatusOK, w.Code) @@ -1213,7 +1213,7 @@ func TestContextRenderFileFromFS(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("GET", "/some/path", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/some/path", nil) c.FileFromFS("./gin.go", Dir(".", false)) assert.Equal(t, http.StatusOK, w.Code) @@ -1229,7 +1229,7 @@ func TestContextRenderAttachment(t *testing.T) { c, _ := CreateTestContext(w) newFilename := "new_filename.go" - c.Request, _ = http.NewRequest("GET", "/", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c.FileAttachment("./gin.go", newFilename) assert.Equal(t, 200, w.Code) @@ -1243,7 +1243,7 @@ func TestContextRenderAndEscapeAttachment(t *testing.T) { maliciousFilename := "tampering_field.sh\"; \\\"; dummy=.go" actualEscapedResponseFilename := "tampering_field.sh\\\"; \\\\\\\"; dummy=.go" - c.Request, _ = http.NewRequest("GET", "/", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c.FileAttachment("./gin.go", maliciousFilename) assert.Equal(t, 200, w.Code) @@ -1256,7 +1256,7 @@ func TestContextRenderUTF8Attachment(t *testing.T) { c, _ := CreateTestContext(w) newFilename := "new๐Ÿงก_filename.go" - c.Request, _ = http.NewRequest("GET", "/", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c.FileAttachment("./gin.go", newFilename) assert.Equal(t, 200, w.Code) @@ -1335,7 +1335,7 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "http://example.com", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) assert.Panics(t, func() { c.Redirect(299, "/new_path") }) assert.Panics(t, func() { c.Redirect(309, "/new_path") }) @@ -1349,7 +1349,7 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "http://example.com", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) c.Redirect(http.StatusFound, "http://google.com") c.Writer.WriteHeaderNow() @@ -1361,7 +1361,7 @@ func TestContextRenderRedirectWith201(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "http://example.com", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) c.Redirect(http.StatusCreated, "/resource") c.Writer.WriteHeaderNow() @@ -1371,7 +1371,7 @@ func TestContextRenderRedirectWith201(t *testing.T) { func TestContextRenderRedirectAll(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "http://example.com", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") }) assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") }) assert.Panics(t, func() { c.Redirect(299, "/resource") }) @@ -1383,7 +1383,7 @@ func TestContextRenderRedirectAll(t *testing.T) { func TestContextNegotiationWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEJSON, MIMEXML, MIMEYAML, MIMEYAML2}, @@ -1398,7 +1398,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { func TestContextNegotiationWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2}, @@ -1413,7 +1413,7 @@ func TestContextNegotiationWithXML(t *testing.T) { func TestContextNegotiationWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML, MIMEYAML2}, @@ -1428,7 +1428,7 @@ func TestContextNegotiationWithYAML(t *testing.T) { func TestContextNegotiationWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2}, @@ -1443,7 +1443,7 @@ func TestContextNegotiationWithTOML(t *testing.T) { func TestContextNegotiationWithHTML(t *testing.T) { w := httptest.NewRecorder() c, router := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) @@ -1461,7 +1461,7 @@ func TestContextNegotiationWithHTML(t *testing.T) { func TestContextNegotiationNotSupport(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEPOSTForm}, @@ -1474,7 +1474,7 @@ func TestContextNegotiationNotSupport(t *testing.T) { func TestContextNegotiationFormat(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) assert.Panics(t, func() { c.NegotiateFormat() }) assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) @@ -1483,7 +1483,7 @@ func TestContextNegotiationFormat(t *testing.T) { func TestContextNegotiationFormatWithAccept(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML)) @@ -1493,7 +1493,7 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) { func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "*/*") assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) @@ -1504,7 +1504,7 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML)) c, _ = CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "text/*") assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) @@ -1517,7 +1517,7 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { func TestContextNegotiationFormatCustom(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") c.Accepted = nil @@ -1530,7 +1530,7 @@ func TestContextNegotiationFormatCustom(t *testing.T) { func TestContextNegotiationFormat2(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "image/tiff-fx") assert.Equal(t, "", c.NegotiateFormat("image/tiff")) @@ -1658,7 +1658,7 @@ func TestContextAbortWithError(t *testing.T) { func TestContextClientIP(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs() resetContextForClientIPTests(c) @@ -1801,7 +1801,7 @@ func resetContextForClientIPTests(c *Context) { func TestContextContentType(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Set("Content-Type", "application/json; charset=utf-8") assert.Equal(t, "application/json", c.ContentType()) @@ -1809,7 +1809,7 @@ func TestContextContentType(t *testing.T) { func TestContextAutoBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { @@ -1826,7 +1826,7 @@ func TestContextBindWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -1843,7 +1843,7 @@ func TestContextBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(` FOO BAR @@ -1864,7 +1864,7 @@ func TestContextBindPlain(t *testing.T) { // string w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(`test string`)) c.Request.Header.Add("Content-Type", MIMEPlain) var s string @@ -1874,7 +1874,7 @@ func TestContextBindPlain(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) // []byte - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(`test []byte`)) c.Request.Header.Add("Content-Type", MIMEPlain) var bs []byte @@ -1888,7 +1888,7 @@ func TestContextBindHeader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("rate", "8000") c.Request.Header.Add("domain", "music") c.Request.Header.Add("limit", "1000") @@ -1910,7 +1910,7 @@ func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) var obj struct { Foo string `form:"foo"` @@ -1926,7 +1926,7 @@ func TestContextBindWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo: bar\nbar: foo")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -1943,7 +1943,7 @@ func TestContextBindWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo = 'bar'\nbar = 'foo'")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo = 'bar'\nbar = 'foo'")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -1960,7 +1960,7 @@ func TestContextBadAutoBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` @@ -1979,7 +1979,7 @@ func TestContextBadAutoBind(t *testing.T) { func TestContextAutoShouldBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { @@ -1996,7 +1996,7 @@ func TestContextShouldBindWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -2013,7 +2013,7 @@ func TestContextShouldBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(` FOO BAR @@ -2034,7 +2034,7 @@ func TestContextShouldBindPlain(t *testing.T) { // string w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(`test string`)) c.Request.Header.Add("Content-Type", MIMEPlain) var s string @@ -2044,7 +2044,7 @@ func TestContextShouldBindPlain(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) // []byte - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(`test []byte`)) c.Request.Header.Add("Content-Type", MIMEPlain) var bs []byte @@ -2058,7 +2058,7 @@ func TestContextShouldBindHeader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("rate", "8000") c.Request.Header.Add("domain", "music") c.Request.Header.Add("limit", "1000") @@ -2080,7 +2080,7 @@ func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused")) var obj struct { Foo string `form:"foo"` @@ -2100,7 +2100,7 @@ func TestContextShouldBindWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo: bar\nbar: foo")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -2117,7 +2117,7 @@ func TestContextShouldBindWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'")) c.Request.Header.Add("Content-Type", MIMETOML) // set fake content-type var obj struct { @@ -2134,7 +2134,7 @@ func TestContextBadAutoShouldBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` @@ -2198,7 +2198,7 @@ func TestContextShouldBindBodyWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest( - "POST", "http://example.com", bytes.NewBufferString(tt.bodyA), + http.MethodPost, "http://example.com", bytes.NewBufferString(tt.bodyA), ) // When it binds to typeA and typeB, it finds the body is // not typeB but typeA. @@ -2216,7 +2216,7 @@ func TestContextShouldBindBodyWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest( - "POST", "http://example.com", bytes.NewBufferString(tt.bodyB), + http.MethodPost, "http://example.com", bytes.NewBufferString(tt.bodyB), ) objA := typeA{} require.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) @@ -2263,7 +2263,7 @@ func TestContextShouldBindBodyWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body)) type typeJSON struct { Foo string `json:"foo" binding:"required"` @@ -2327,7 +2327,7 @@ func TestContextShouldBindBodyWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body)) type typeXML struct { Foo string `xml:"foo" binding:"required"` @@ -2391,7 +2391,7 @@ func TestContextShouldBindBodyWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body)) type typeYAML struct { Foo string `yaml:"foo" binding:"required"` @@ -2456,7 +2456,7 @@ func TestContextShouldBindBodyWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body)) type typeTOML struct { Foo string `toml:"foo" binding:"required"` @@ -2525,7 +2525,7 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body)) type typeJSON struct { Foo string `json:"foo" binding:"required"` @@ -2562,7 +2562,7 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) require.NoError(t, c.Err()) assert.Nil(t, c.Done()) ti, ok := c.Deadline() @@ -2580,7 +2580,7 @@ func TestContextGolangContext(t *testing.T) { func TestWebsocketsRequired(t *testing.T) { // Example request from spec: https://tools.ietf.org/html/rfc6455#section-1.2 c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("GET", "/chat", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/chat", nil) c.Request.Header.Set("Host", "server.example.com") c.Request.Header.Set("Upgrade", "websocket") c.Request.Header.Set("Connection", "Upgrade") @@ -2593,7 +2593,7 @@ func TestWebsocketsRequired(t *testing.T) { // Normal request, no websocket required. c, _ = CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("GET", "/chat", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/chat", nil) c.Request.Header.Set("Host", "server.example.com") assert.False(t, c.IsWebsocket()) @@ -2601,7 +2601,7 @@ func TestWebsocketsRequired(t *testing.T) { func TestGetRequestHeaderValue(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("GET", "/chat", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/chat", nil) c.Request.Header.Set("Gin-Version", "1.0.0") assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version")) @@ -2611,7 +2611,7 @@ func TestGetRequestHeaderValue(t *testing.T) { func TestContextGetRawData(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("Fetch binary post data") - c.Request, _ = http.NewRequest("POST", "/", body) + c.Request, _ = http.NewRequest(http.MethodPost, "/", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) data, err := c.GetRawData() @@ -2740,8 +2740,8 @@ func TestRaceParamsContextCopy(t *testing.T) { }(c.Copy(), c.Param("name")) }) } - PerformRequest(router, "GET", "/name1/api") - PerformRequest(router, "GET", "/name2/api") + PerformRequest(router, http.MethodGet, "/name1/api") + PerformRequest(router, http.MethodGet, "/name2/api") wg.Wait() } @@ -2760,7 +2760,7 @@ func TestContextWithKeysMutex(t *testing.T) { func TestRemoteIPFail(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.RemoteAddr = "[:::]:80" ip := net.ParseIP(c.RemoteIP()) trust := c.engine.isTrustedProxy(ip) @@ -2862,7 +2862,7 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag c.engine.ContextWithFallback = true - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "value")) return c, key }, @@ -2874,7 +2874,7 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag c.engine.ContextWithFallback = true - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value")) return c, contextKey("key") }, @@ -2897,7 +2897,7 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag c.engine.ContextWithFallback = true - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) return c, "key" }, value: nil, @@ -3029,7 +3029,7 @@ func TestInterceptedHeader(t *testing.T) { c.Header("X-Test-2", "present") c.String(http.StatusOK, "hello world") }) - c.Request = httptest.NewRequest("GET", "/", nil) + c.Request = httptest.NewRequest(http.MethodGet, "/", nil) r.HandleContext(c) // Result() has headers frozen when WriteHeaderNow() has been called // Compared to this time, this is when the response headers will be flushed diff --git a/debug_test.go b/debug_test.go index edf4bb12..0efbfd78 100644 --- a/debug_test.go +++ b/debug_test.go @@ -10,6 +10,7 @@ import ( "html/template" "io" "log" + "net/http" "os" "runtime" "strings" @@ -60,7 +61,7 @@ func TestDebugPrintError(t *testing.T) { func TestDebugPrintRoutes(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) - debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) + debugPrintRoute(http.MethodGet, "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) SetMode(TestMode) }) assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re) @@ -72,7 +73,7 @@ func TestDebugPrintRouteFunc(t *testing.T) { } re := captureOutput(t, func() { SetMode(DebugMode) - debugPrintRoute("GET", "/path/to/route/:param1/:param2", HandlersChain{func(c *Context) {}, handlerNameTest}) + debugPrintRoute(http.MethodGet, "/path/to/route/:param1/:param2", HandlersChain{func(c *Context) {}, handlerNameTest}) SetMode(TestMode) }) assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param1/:param2 --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re) diff --git a/deprecated_test.go b/deprecated_test.go index 0240b2ec..6c8f2a7f 100644 --- a/deprecated_test.go +++ b/deprecated_test.go @@ -18,7 +18,7 @@ func TestBindWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) var obj struct { Foo string `form:"foo"` diff --git a/gin_test.go b/gin_test.go index 5d0c47d3..732da18b 100644 --- a/gin_test.go +++ b/gin_test.go @@ -327,31 +327,31 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { func TestAddRoute(t *testing.T) { router := New() - router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodGet, "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 1) - assert.NotNil(t, router.trees.get("GET")) - assert.Nil(t, router.trees.get("POST")) + assert.NotNil(t, router.trees.get(http.MethodGet)) + assert.Nil(t, router.trees.get(http.MethodPost)) - router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 2) - assert.NotNil(t, router.trees.get("GET")) - assert.NotNil(t, router.trees.get("POST")) + assert.NotNil(t, router.trees.get(http.MethodGet)) + assert.NotNil(t, router.trees.get(http.MethodPost)) - router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 2) } func TestAddRouteFails(t *testing.T) { router := New() assert.Panics(t, func() { router.addRoute("", "/", HandlersChain{func(_ *Context) {}}) }) - assert.Panics(t, func() { router.addRoute("GET", "a", HandlersChain{func(_ *Context) {}}) }) - assert.Panics(t, func() { router.addRoute("GET", "/", HandlersChain{}) }) + assert.Panics(t, func() { router.addRoute(http.MethodGet, "a", HandlersChain{func(_ *Context) {}}) }) + assert.Panics(t, func() { router.addRoute(http.MethodGet, "/", HandlersChain{}) }) - router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}}) assert.Panics(t, func() { - router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}}) }) } @@ -493,27 +493,27 @@ func TestListOfRoutes(t *testing.T) { assert.Len(t, list, 7) assertRoutePresent(t, list, RouteInfo{ - Method: "GET", + Method: http.MethodGet, Path: "/favicon.ico", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$", }) assertRoutePresent(t, list, RouteInfo{ - Method: "GET", + Method: http.MethodGet, Path: "/", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$", }) assertRoutePresent(t, list, RouteInfo{ - Method: "GET", + Method: http.MethodGet, Path: "/users/", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$", }) assertRoutePresent(t, list, RouteInfo{ - Method: "GET", + Method: http.MethodGet, Path: "/users/:id", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$", }) assertRoutePresent(t, list, RouteInfo{ - Method: "POST", + Method: http.MethodPost, Path: "/users/:id", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$", }) @@ -531,7 +531,7 @@ func TestEngineHandleContext(t *testing.T) { } assert.NotPanics(t, func() { - w := PerformRequest(r, "GET", "/") + w := PerformRequest(r, http.MethodGet, "/") assert.Equal(t, 301, w.Code) }) } @@ -564,7 +564,7 @@ func TestEngineHandleContextManyReEntries(t *testing.T) { }) assert.NotPanics(t, func() { - w := PerformRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value + w := PerformRequest(r, http.MethodGet, "/"+strconv.Itoa(expectValue-1)) // include 0 value assert.Equal(t, 200, w.Code) assert.Equal(t, expectValue, w.Body.Len()) }) @@ -712,8 +712,8 @@ func TestNewOptionFunc(t *testing.T) { r := New(fc) routes := r.Routes() - assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"}) - assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest1"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest2"}) } func TestWithOptionFunc(t *testing.T) { @@ -729,8 +729,8 @@ func TestWithOptionFunc(t *testing.T) { }) routes := r.Routes() - assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"}) - assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest1"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest2"}) } type Birthday string @@ -749,7 +749,7 @@ func TestCustomUnmarshalStruct(t *testing.T) { _ = ctx.BindQuery(&request) ctx.JSON(200, request.Birthday) }) - req := httptest.NewRequest("GET", "/test?birthday=2000-01-01", nil) + req := httptest.NewRequest(http.MethodGet, "/test?birthday=2000-01-01", nil) w := httptest.NewRecorder() route.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) @@ -761,7 +761,7 @@ func TestMethodNotAllowedNoRoute(t *testing.T) { g := New() g.HandleMethodNotAllowed = true - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(http.MethodGet, "/", nil) resp := httptest.NewRecorder() assert.NotPanics(t, func() { g.ServeHTTP(resp, req) }) assert.Equal(t, http.StatusNotFound, resp.Code) diff --git a/logger_test.go b/logger_test.go index b05df740..de00c499 100644 --- a/logger_test.go +++ b/logger_test.go @@ -31,9 +31,9 @@ func TestLogger(t *testing.T) { router.HEAD("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {}) - PerformRequest(router, "GET", "/example?a=100") + PerformRequest(router, http.MethodGet, "/example?a=100") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") @@ -41,21 +41,21 @@ func TestLogger(t *testing.T) { // like integration tests because they test the whole logging process rather // than individual functions. Im not sure where these should go. buffer.Reset() - PerformRequest(router, "POST", "/example") + PerformRequest(router, http.MethodPost, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "POST") + assert.Contains(t, buffer.String(), http.MethodPost) assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "PUT", "/example") + PerformRequest(router, http.MethodPut, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "PUT") + assert.Contains(t, buffer.String(), http.MethodPut) assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "DELETE", "/example") + PerformRequest(router, http.MethodDelete, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "DELETE") + assert.Contains(t, buffer.String(), http.MethodDelete) assert.Contains(t, buffer.String(), "/example") buffer.Reset() @@ -77,9 +77,9 @@ func TestLogger(t *testing.T) { assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "GET", "/notfound") + PerformRequest(router, http.MethodGet, "/notfound") assert.Contains(t, buffer.String(), "404") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/notfound") } @@ -95,9 +95,9 @@ func TestLoggerWithConfig(t *testing.T) { router.HEAD("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {}) - PerformRequest(router, "GET", "/example?a=100") + PerformRequest(router, http.MethodGet, "/example?a=100") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") @@ -105,21 +105,21 @@ func TestLoggerWithConfig(t *testing.T) { // like integration tests because they test the whole logging process rather // than individual functions. Im not sure where these should go. buffer.Reset() - PerformRequest(router, "POST", "/example") + PerformRequest(router, http.MethodPost, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "POST") + assert.Contains(t, buffer.String(), http.MethodPost) assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "PUT", "/example") + PerformRequest(router, http.MethodPut, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "PUT") + assert.Contains(t, buffer.String(), http.MethodPut) assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "DELETE", "/example") + PerformRequest(router, http.MethodDelete, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "DELETE") + assert.Contains(t, buffer.String(), http.MethodDelete) assert.Contains(t, buffer.String(), "/example") buffer.Reset() @@ -141,9 +141,9 @@ func TestLoggerWithConfig(t *testing.T) { assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "GET", "/notfound") + PerformRequest(router, http.MethodGet, "/notfound") assert.Contains(t, buffer.String(), "404") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/notfound") } @@ -169,12 +169,12 @@ func TestLoggerWithFormatter(t *testing.T) { ) })) router.GET("/example", func(c *Context) {}) - PerformRequest(router, "GET", "/example?a=100") + PerformRequest(router, http.MethodGet, "/example?a=100") // output test assert.Contains(t, buffer.String(), "[FORMATTER TEST]") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") } @@ -210,12 +210,12 @@ func TestLoggerWithConfigFormatting(t *testing.T) { gotKeys = c.Keys time.Sleep(time.Millisecond) }) - PerformRequest(router, "GET", "/example?a=100") + PerformRequest(router, http.MethodGet, "/example?a=100") // output test assert.Contains(t, buffer.String(), "[FORMATTER TEST]") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") @@ -225,7 +225,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) { assert.Equal(t, 200, gotParam.StatusCode) assert.NotEmpty(t, gotParam.Latency) assert.Equal(t, "20.20.20.20", gotParam.ClientIP) - assert.Equal(t, "GET", gotParam.Method) + assert.Equal(t, http.MethodGet, gotParam.Method) assert.Equal(t, "/example?a=100", gotParam.Path) assert.Empty(t, gotParam.ErrorMessage) assert.Equal(t, gotKeys, gotParam.Keys) @@ -239,7 +239,7 @@ func TestDefaultLogFormatter(t *testing.T) { StatusCode: 200, Latency: time.Second * 5, ClientIP: "20.20.20.20", - Method: "GET", + Method: http.MethodGet, Path: "/", ErrorMessage: "", isTerm: false, @@ -250,7 +250,7 @@ func TestDefaultLogFormatter(t *testing.T) { StatusCode: 200, Latency: time.Second * 5, ClientIP: "20.20.20.20", - Method: "GET", + Method: http.MethodGet, Path: "/", ErrorMessage: "", isTerm: true, @@ -260,7 +260,7 @@ func TestDefaultLogFormatter(t *testing.T) { StatusCode: 200, Latency: time.Millisecond * 9876543210, ClientIP: "20.20.20.20", - Method: "GET", + Method: http.MethodGet, Path: "/", ErrorMessage: "", isTerm: true, @@ -271,7 +271,7 @@ func TestDefaultLogFormatter(t *testing.T) { StatusCode: 200, Latency: time.Millisecond * 9876543210, ClientIP: "20.20.20.20", - Method: "GET", + Method: http.MethodGet, Path: "/", ErrorMessage: "", isTerm: false, @@ -292,10 +292,10 @@ func TestColorForMethod(t *testing.T) { return p.MethodColor() } - assert.Equal(t, blue, colorForMethod("GET"), "get should be blue") - assert.Equal(t, cyan, colorForMethod("POST"), "post should be cyan") - assert.Equal(t, yellow, colorForMethod("PUT"), "put should be yellow") - assert.Equal(t, red, colorForMethod("DELETE"), "delete should be red") + assert.Equal(t, blue, colorForMethod(http.MethodGet), "get should be blue") + assert.Equal(t, cyan, colorForMethod(http.MethodPost), "post should be cyan") + assert.Equal(t, yellow, colorForMethod(http.MethodPut), "put should be yellow") + assert.Equal(t, red, colorForMethod(http.MethodDelete), "delete should be red") assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green") assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta") assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white") @@ -369,15 +369,15 @@ func TestErrorLogger(t *testing.T) { c.String(http.StatusInternalServerError, "hola!") }) - w := PerformRequest(router, "GET", "/error") + w := PerformRequest(router, http.MethodGet, "/error") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) - w = PerformRequest(router, "GET", "/abort") + w = PerformRequest(router, http.MethodGet, "/abort") assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) - w = PerformRequest(router, "GET", "/print") + w = PerformRequest(router, http.MethodGet, "/print") assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } @@ -389,11 +389,11 @@ func TestLoggerWithWriterSkippingPaths(t *testing.T) { router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) - PerformRequest(router, "GET", "/logged") + PerformRequest(router, http.MethodGet, "/logged") assert.Contains(t, buffer.String(), "200") buffer.Reset() - PerformRequest(router, "GET", "/skipped") + PerformRequest(router, http.MethodGet, "/skipped") assert.Contains(t, buffer.String(), "") } @@ -407,11 +407,11 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) - PerformRequest(router, "GET", "/logged") + PerformRequest(router, http.MethodGet, "/logged") assert.Contains(t, buffer.String(), "200") buffer.Reset() - PerformRequest(router, "GET", "/skipped") + PerformRequest(router, http.MethodGet, "/skipped") assert.Contains(t, buffer.String(), "") } @@ -427,11 +427,11 @@ func TestLoggerWithConfigSkipper(t *testing.T) { router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) }) router.GET("/skipped", func(c *Context) { c.Status(http.StatusNoContent) }) - PerformRequest(router, "GET", "/logged") + PerformRequest(router, http.MethodGet, "/logged") assert.Contains(t, buffer.String(), "200") buffer.Reset() - PerformRequest(router, "GET", "/skipped") + PerformRequest(router, http.MethodGet, "/skipped") assert.Contains(t, buffer.String(), "") } diff --git a/middleware_test.go b/middleware_test.go index acdf89c4..eafc60ad 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -35,7 +35,7 @@ func TestMiddlewareGeneralCase(t *testing.T) { signature += " XX " }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusOK, w.Code) @@ -71,7 +71,7 @@ func TestMiddlewareNoRoute(t *testing.T) { signature += " X " }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusNotFound, w.Code) @@ -108,7 +108,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) { signature += " XX " }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusMethodNotAllowed, w.Code) @@ -149,7 +149,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) { }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusNotFound, w.Code) @@ -175,7 +175,7 @@ func TestMiddlewareAbort(t *testing.T) { }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusUnauthorized, w.Code) @@ -196,7 +196,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { c.Next() }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusGone, w.Code) @@ -219,7 +219,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { signature += "C" }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) @@ -246,7 +246,7 @@ func TestMiddlewareWrite(t *testing.T) { }) }) - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) diff --git a/recovery_test.go b/recovery_test.go index ee063cd1..08eec1e4 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -25,7 +25,7 @@ func TestPanicClean(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery", + w := PerformRequest(router, http.MethodGet, "/recovery", header{ Key: "Host", Value: "www.google.com", @@ -55,7 +55,7 @@ func TestPanicInHandler(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -66,7 +66,7 @@ func TestPanicInHandler(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = PerformRequest(router, "GET", "/recovery") + w = PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") @@ -83,7 +83,7 @@ func TestPanicWithAbort(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) } @@ -134,7 +134,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { panic(e) }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, expectCode, w.Code) assert.Contains(t, strings.ToLower(buf.String()), expectMsg) @@ -155,7 +155,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -166,7 +166,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = PerformRequest(router, "GET", "/recovery") + w = PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") @@ -190,7 +190,7 @@ func TestCustomRecovery(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -201,7 +201,7 @@ func TestCustomRecovery(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = PerformRequest(router, "GET", "/recovery") + w = PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") @@ -225,7 +225,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -236,7 +236,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = PerformRequest(router, "GET", "/recovery") + w = PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") diff --git a/render/render_test.go b/render/render_test.go index 27a5065b..ad633b00 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -369,7 +369,7 @@ func TestRenderXML(t *testing.T) { } func TestRenderRedirect(t *testing.T) { - req, err := http.NewRequest("GET", "/test-redirect", nil) + req, err := http.NewRequest(http.MethodGet, "/test-redirect", nil) require.NoError(t, err) data1 := Redirect{ diff --git a/routes_test.go b/routes_test.go index 49f355a7..995ff51c 100644 --- a/routes_test.go +++ b/routes_test.go @@ -523,8 +523,8 @@ func TestRouteNotAllowedEnabled3(t *testing.T) { w := PerformRequest(router, http.MethodPut, "/path") assert.Equal(t, http.StatusMethodNotAllowed, w.Code) allowed := w.Header().Get("Allow") - assert.Contains(t, allowed, "GET") - assert.Contains(t, allowed, "POST") + assert.Contains(t, allowed, http.MethodGet) + assert.Contains(t, allowed, http.MethodPost) } func TestRouteNotAllowedDisabled(t *testing.T) { @@ -557,7 +557,7 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) { {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { - w := PerformRequest(router, "GET", tr.route) + w := PerformRequest(router, http.MethodGet, tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { assert.Equal(t, tr.location, w.Header().Get("Location")) @@ -786,6 +786,6 @@ func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) { v1.GET("/orgs/:id", handlerTest1) v1.DELETE("/orgs/:id", handlerTest1) - w := PerformRequest(r, "GET", "/base/v1/user/groups") + w := PerformRequest(r, http.MethodGet, "/base/v1/user/groups") assert.Equal(t, http.StatusNotFound, w.Code) } diff --git a/utils_test.go b/utils_test.go index af089963..8098c681 100644 --- a/utils_test.go +++ b/utils_test.go @@ -29,7 +29,7 @@ type testStruct struct { } func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) { - assert.Equal(t.T, "POST", req.Method) + assert.Equal(t.T, http.MethodPost, req.Method) assert.Equal(t.T, "/path", req.URL.Path) w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, "hello") @@ -39,17 +39,17 @@ func TestWrap(t *testing.T) { router := New() router.POST("/path", WrapH(&testStruct{t})) router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) { - assert.Equal(t, "GET", req.Method) + assert.Equal(t, http.MethodGet, req.Method) assert.Equal(t, "/path2", req.URL.Path) w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, "hola!") })) - w := PerformRequest(router, "POST", "/path") + w := PerformRequest(router, http.MethodPost, "/path") assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hello", w.Body.String()) - w = PerformRequest(router, "GET", "/path2") + w = PerformRequest(router, http.MethodGet, "/path2") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "hola!", w.Body.String()) } @@ -119,13 +119,13 @@ func TestBindMiddleware(t *testing.T) { called = true value = c.MustGet(BindKey).(*bindTestStruct) }) - PerformRequest(router, "GET", "/?foo=hola&bar=10") + PerformRequest(router, http.MethodGet, "/?foo=hola&bar=10") assert.True(t, called) assert.Equal(t, "hola", value.Foo) assert.Equal(t, 10, value.Bar) called = false - PerformRequest(router, "GET", "/?foo=hola&bar=1") + PerformRequest(router, http.MethodGet, "/?foo=hola&bar=1") assert.False(t, called) assert.Panics(t, func() { From e46bd521859fdfc83c508f1d42c92cb7f91e9fcb Mon Sep 17 00:00:00 2001 From: haesuo566 <102643523+haesuo566@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:54:06 +0900 Subject: [PATCH 184/332] refactor(context): add an optional permission parameter to the SaveUploadedFile method (#4068) (#4088) Co-authored-by: hso --- context.go | 13 +++++++++++-- context_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index cab14529..c724daf3 100644 --- a/context.go +++ b/context.go @@ -7,6 +7,7 @@ package gin import ( "errors" "io" + "io/fs" "log" "math" "mime/multipart" @@ -676,14 +677,22 @@ func (c *Context) MultipartForm() (*multipart.Form, error) { } // SaveUploadedFile uploads the form file to specific dst. -func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error { +func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm ...fs.FileMode) error { src, err := file.Open() if err != nil { return err } defer src.Close() - if err = os.MkdirAll(filepath.Dir(dst), 0o750); err != nil { + if len(perm) <= 0 { + perm = append(perm, 0o750) + } + + if err = os.MkdirAll(filepath.Dir(dst), perm[0]); err != nil { + return err + } + + if err = os.Chmod(filepath.Dir(dst), perm[0]); err != nil { return err } diff --git a/context_test.go b/context_test.go index 91d5e898..5b63a647 100644 --- a/context_test.go +++ b/context_test.go @@ -11,12 +11,14 @@ import ( "fmt" "html/template" "io" + "io/fs" "mime/multipart" "net" "net/http" "net/http/httptest" "net/url" "os" + "path/filepath" "reflect" "strconv" "strings" @@ -155,6 +157,45 @@ func TestSaveUploadedCreateFailed(t *testing.T) { require.Error(t, c.SaveUploadedFile(f, "/")) } +func TestSaveUploadedFileWithPermission(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + w, err := mw.CreateFormFile("file", "permission_test") + require.NoError(t, err) + _, err = w.Write([]byte("permission_test")) + require.NoError(t, err) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + f, err := c.FormFile("file") + require.NoError(t, err) + assert.Equal(t, "permission_test", f.Filename) + var mode fs.FileMode = 0o755 + require.NoError(t, c.SaveUploadedFile(f, "permission_test", mode)) + info, err := os.Stat(filepath.Dir("permission_test")) + require.NoError(t, err) + assert.Equal(t, info.Mode().Perm(), mode) +} + +func TestSaveUploadedFileWithPermissionFailed(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + w, err := mw.CreateFormFile("file", "permission_test") + require.NoError(t, err) + _, err = w.Write([]byte("permission_test")) + require.NoError(t, err) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + f, err := c.FormFile("file") + require.NoError(t, err) + assert.Equal(t, "permission_test", f.Filename) + var mode fs.FileMode = 0o644 + require.Error(t, c.SaveUploadedFile(f, "test/permission_test", mode)) +} + func TestContextReset(t *testing.T) { router := New() c := router.allocateContext(0) From e2e80f33472bd02094f242da3c3efde2cec0a037 Mon Sep 17 00:00:00 2001 From: Xianglin Gao Date: Sat, 28 Dec 2024 17:18:03 +0800 Subject: [PATCH 185/332] chore(security): update vendor to fix CVE (#4121) Signed-off-by: Xianglin Gao --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 035c2dea..5de7d065 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/quic-go/quic-go v0.43.1 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.27.0 + golang.org/x/net v0.33.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -39,10 +39,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect ) diff --git a/go.sum b/go.sum index 55a21627..d0389bb7 100644 --- a/go.sum +++ b/go.sum @@ -92,22 +92,22 @@ go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= 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/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= From 23d6961aeb9d2670a7b36c77cb180f479e220580 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 30 Dec 2024 11:39:24 +0800 Subject: [PATCH 186/332] ci(lint): update workflows and improve test request consistency (#4126) - Update GoReleaser action to version 6 in GitHub workflow - Use `http.MethodPost` constant in test requests instead of hardcoded string Signed-off-by: appleboy --- .github/workflows/goreleaser.yml | 2 +- context_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 8ae11823..22edf453 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -21,7 +21,7 @@ jobs: with: go-version: "^1" - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v6 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser diff --git a/context_test.go b/context_test.go index 5b63a647..ef0cfccd 100644 --- a/context_test.go +++ b/context_test.go @@ -166,7 +166,7 @@ func TestSaveUploadedFileWithPermission(t *testing.T) { require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") require.NoError(t, err) @@ -187,7 +187,7 @@ func TestSaveUploadedFileWithPermissionFailed(t *testing.T) { require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") require.NoError(t, err) From 3f818c3fa69e03feb46d2b49d2a8084c425cbed6 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 30 Dec 2024 11:40:37 +0800 Subject: [PATCH 187/332] chore(security): upgrade quic-go version to 0.48.2 (#4127) - Update Go versions in GitHub Actions workflow to `1.22` and `1.23` - Update README to require Go version `1.22` or above - Adjust table formatting in README for better alignment - Update warning message in `debug.go` to reflect Go version `1.22` - Update test in `debug_test.go` to reflect Go version `1.22` - Update `go.mod` to require Go version `1.22` - Update dependencies in `go.mod` to newer versions Signed-off-by: appleboy --- .github/workflows/gin.yml | 2 +- README.md | 4 ++-- debug.go | 2 +- debug_test.go | 2 +- go.mod | 9 ++++----- go.sum | 23 +++++++---------------- 6 files changed, 16 insertions(+), 26 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 74983c50..1d193efb 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.21", "1.22"] + go: ["1.22", "1.23"] test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: diff --git a/README.md b/README.md index 0464107c..ae155048 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ If you need performance and good productivity, you will love Gin. ### Prerequisites -Gin requires [Go](https://go.dev/) version [1.21](https://go.dev/doc/devel/release#go1.21.0) or above. +Gin requires [Go](https://go.dev/) version [1.22](https://go.dev/doc/devel/release#go1.22.0) or above. ### Getting Gin @@ -113,7 +113,7 @@ The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks](/BENCHMARKS.md). | Benchmark name | (1) | (2) | (3) | (4) | -| ------------------------------ | ---------:| ---------------:| ------------:| ---------------:| +| ------------------------------ | --------: | --------------: | -----------: | --------------: | | BenchmarkGin_GithubAll | **43550** | **27364 ns/op** | **0 B/op** | **0 allocs/op** | | BenchmarkAce_GithubAll | 40543 | 29670 ns/op | 0 B/op | 0 allocs/op | | BenchmarkAero_GithubAll | 57632 | 20648 ns/op | 0 B/op | 0 allocs/op | diff --git a/debug.go b/debug.go index 62085c5d..43dcd722 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.21+. + debugPrint(`[WARNING] Now Gin requires Go 1.22+. `) } diff --git a/debug_test.go b/debug_test.go index 0efbfd78..4b440e3a 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.21+.\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.22+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/go.mod b/go.mod index 5de7d065..398787ea 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gin-gonic/gin -go 1.21.0 +go 1.22 require ( github.com/bytedance/sonic v1.11.6 @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.2 - github.com/quic-go/quic-go v0.43.1 + github.com/quic-go/quic-go v0.48.2 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.33.0 @@ -29,18 +29,17 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.4.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.4.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/go.sum b/go.sum index d0389bb7..ddd0f87d 100644 --- a/go.sum +++ b/go.sum @@ -9,7 +9,6 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -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= @@ -44,10 +43,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm 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/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= @@ -62,15 +57,12 @@ github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ= -github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +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.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= 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= @@ -94,8 +86,8 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VA golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= @@ -114,9 +106,8 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From c3c8620a7fb4e09c7845feca4e8e8a8678a2685d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 08:50:51 +0800 Subject: [PATCH 188/332] chore(deps): bump github.com/go-playground/validator/v10 from 10.20.0 to 10.22.1 (#4052) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.20.0 to 10.22.1. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.20.0...v10.22.1) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 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 398787ea..85ad56ff 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22 require ( github.com/bytedance/sonic v1.11.6 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.20.0 + github.com/go-playground/validator/v10 v10.22.1 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 diff --git a/go.sum b/go.sum index ddd0f87d..f4ab17cb 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,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.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= From 3b28645dc95d58e0df36b8aff7a6c64f7c0ca5e9 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 12 Feb 2025 10:22:02 +0800 Subject: [PATCH 189/332] ci: add go version 1.24 to GitHub Actions (#4154) - Add Go version `1.24` to the GitHub Actions workflow Signed-off-by: Bo-Yi Wu --- .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 1d193efb..095dea6d 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.22", "1.23"] + go: ["1.22", "1.23", "1.24"] test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: From 1eb827240e520804ea83b1aafcbaf7ba728b81dd Mon Sep 17 00:00:00 2001 From: NezhaFan Date: Tue, 18 Mar 2025 22:12:36 +0800 Subject: [PATCH 190/332] docs: fix case error of X-Real-IP (#4185) Co-authored-by: voyager1 --- CHANGELOG.md | 2 +- context.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de47c750..5648902d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -488,7 +488,7 @@ - [FIX] Refactor render - [FIX] Reworked tests - [FIX] logger now supports cygwin -- [FIX] Use X-Forwarded-For before X-Real-Ip +- [FIX] Use X-Forwarded-For before X-Real-IP - [FIX] time.Time binding (#904) ## Gin 1.1.4 diff --git a/context.go b/context.go index c724daf3..408f1861 100644 --- a/context.go +++ b/context.go @@ -889,7 +889,7 @@ func (c *Context) ShouldBindBodyWithPlain(obj any) error { // ClientIP implements one best effort algorithm to return the real client IP. // It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. -// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). +// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-IP]). // If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy, // the remote IP (coming from Request.RemoteAddr) is returned. func (c *Context) ClientIP() string { From a4baac6e5e030ca707e519a3bf209d25699e3902 Mon Sep 17 00:00:00 2001 From: NezhaFan Date: Tue, 18 Mar 2025 22:14:38 +0800 Subject: [PATCH 191/332] refactor(context):Avoid using filepath.Dir twice in SaveUploadedFile (#4181) Co-authored-by: voyager1 --- context.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 408f1861..1c76c0f6 100644 --- a/context.go +++ b/context.go @@ -684,15 +684,15 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm } defer src.Close() - if len(perm) <= 0 { - perm = append(perm, 0o750) + var mode os.FileMode = 0o750 + if len(perm) > 0 { + mode = perm[0] } - - if err = os.MkdirAll(filepath.Dir(dst), perm[0]); err != nil { + dir := filepath.Dir(dst) + if err = os.MkdirAll(dir, mode); err != nil { return err } - - if err = os.Chmod(filepath.Dir(dst), perm[0]); err != nil { + if err = os.Chmod(dir, mode); err != nil { return err } From 733ee094fc4aaf016fb05820f553eeb0b81d0f1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 22:15:13 +0800 Subject: [PATCH 192/332] chore(deps): bump golang.org/x/net from 0.33.0 to 0.37.0 (#4178) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.37.0. - [Commits](https://github.com/golang/net/compare/v0.33.0...v0.37.0) --- updated-dependencies: - dependency-name: golang.org/x/net 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 | 9 +++++---- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 85ad56ff..a8032b70 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/gin-gonic/gin go 1.22 +toolchain go1.23.7 require ( github.com/bytedance/sonic v1.11.6 @@ -13,7 +14,7 @@ require ( github.com/quic-go/quic-go v0.48.2 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.33.0 + golang.org/x/net v0.37.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -38,10 +39,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.31.0 // indirect + golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect ) diff --git a/go.sum b/go.sum index f4ab17cb..d28753ab 100644 --- a/go.sum +++ b/go.sum @@ -84,22 +84,22 @@ go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= 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/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= From ebe5e2a6bfdca50fd44074b470ad486392e2933f Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Tue, 18 Mar 2025 23:13:03 +0800 Subject: [PATCH 193/332] fix(golangci.yml): move fiximports to goimports section and replace exportloopref with copyloopvar (#4167) Co-authored-by: huangzw --- .golangci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ccb26684..b50a911b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,7 +7,7 @@ linters: - durationcheck - errcheck - errorlint - - exportloopref + - copyloopvar - gci - gofmt - goimports @@ -39,10 +39,11 @@ linters-settings: perfsprint: err-error: true errorf: true - fiximports: true int-conversion: true sprintf1: true strconcat: true + goimports: + fiximports: true testifylint: enable-all: true From 90cf4602698dcbce18df3165b2d24e2940670a41 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 20 Mar 2025 10:13:47 +0800 Subject: [PATCH 194/332] chore: update Go versions and dependencies for improved compatibility (#4187) * chore: update Go versions and dependencies for improved compatibility - Update Go versions in workflow file to `1.23` and `1.24` - Enhance test tags in workflow with specific linker flags - Remove the conditional formatting step for Go `1.22.x` in workflow - Remove `goimports` settings from `.golangci.yml` - Update `go.mod` to use Go `1.23.0` - Upgrade `github.com/bytedance/sonic` from `v1.11.6` to `v1.13.1` - Update indirect dependencies `sonic/loader` to `v0.2.4` and `base64x` to `v0.1.5` in `go.mod` Signed-off-by: appleboy * chore: update project for Go 1.23 compatibility and documentation fixes - Update Go version requirement from 1.22 to 1.23 in README.md - Remove superfluous `$` from example command in README.md - Update warning message to reflect new Go version requirement in debug.go - Update test assertion to reflect new Go version requirement in debug_test.go Signed-off-by: appleboy --------- Signed-off-by: appleboy --- .github/workflows/gin.yml | 14 ++++++++------ .golangci.yml | 2 -- README.md | 4 ++-- debug.go | 2 +- debug_test.go | 2 +- go.mod | 10 ++++------ go.sum | 13 ++++++------- 7 files changed, 22 insertions(+), 25 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 095dea6d..1062252a 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -33,9 +33,15 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.22", "1.23", "1.24"] + go: ["1.23", "1.24"] test-tags: - ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] + [ + "", + "-tags nomsgpack", + '--ldflags="-checklinkname=0" -tags "sonic avx"', + "-tags go_json", + "-race", + ] include: - os: ubuntu-latest go-build: ~/.cache/go-build @@ -75,7 +81,3 @@ jobs: uses: codecov/codecov-action@v4 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - - - name: Format - if: matrix.go-version == '1.22.x' - run: diff -u <(echo -n) <(gofmt -d .) diff --git a/.golangci.yml b/.golangci.yml index b50a911b..925e1306 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -42,8 +42,6 @@ linters-settings: int-conversion: true sprintf1: true strconcat: true - goimports: - fiximports: true testifylint: enable-all: true diff --git a/README.md b/README.md index ae155048..fe5722b2 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ If you need performance and good productivity, you will love Gin. ### Prerequisites -Gin requires [Go](https://go.dev/) version [1.22](https://go.dev/doc/devel/release#go1.22.0) or above. +Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above. ### Getting Gin @@ -73,7 +73,7 @@ func main() { To run the code, use the `go run` command, like: ```sh -$ go run example.go +go run example.go ``` Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response! diff --git a/debug.go b/debug.go index 43dcd722..f2016168 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.22+. + debugPrint(`[WARNING] Now Gin requires Go 1.23+. `) } diff --git a/debug_test.go b/debug_test.go index 4b440e3a..59b61beb 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.22+.\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.23+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/go.mod b/go.mod index a8032b70..1223267c 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,9 @@ module github.com/gin-gonic/gin -go 1.22 -toolchain go1.23.7 +go 1.23.0 require ( - github.com/bytedance/sonic v1.11.6 + github.com/bytedance/sonic v1.13.1 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.22.1 github.com/goccy/go-json v0.10.2 @@ -20,9 +19,8 @@ require ( ) require ( - github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect + github.com/bytedance/sonic/loader v0.2.4 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect diff --git a/go.sum b/go.sum index d28753ab..2e1c30ad 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= +github.com/bytedance/sonic v1.13.1/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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +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/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= @@ -112,4 +112,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C 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= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From 4ccfa7c275c449990818e46759d5974a953cc1c1 Mon Sep 17 00:00:00 2001 From: takanuva15 <6986426+takanuva15@users.noreply.github.com> Date: Thu, 20 Mar 2025 11:33:10 -0400 Subject: [PATCH 195/332] feat(binding): add support for unixMilli and unixMicro (#4190) --- binding/binding_test.go | 26 ++++++++++++++++---------- binding/form_mapping.go | 16 +++++++++++----- docs/doc.md | 6 +++++- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 901e9740..bdab3694 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -69,15 +69,19 @@ type FooStructDisallowUnknownFields struct { } type FooBarStructForTimeType struct { - TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` - TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` - CreateTime time.Time `form:"createTime" time_format:"unixNano"` - UnixTime time.Time `form:"unixTime" time_format:"unix"` + TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` + TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` + UnixMilliTime time.Time `form:"unixMilliTime" time_format:"unixmilli"` + UnixMicroTime time.Time `form:"unixMicroTime" time_format:"uNiXmiCrO"` } type FooStructForTimeTypeNotUnixFormat struct { - CreateTime time.Time `form:"createTime" time_format:"unixNano"` - UnixTime time.Time `form:"unixTime" time_format:"unix"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` + UnixMilliTime time.Time `form:"unixMilliTime" time_format:"unixMilli"` + UnixMicroTime time.Time `form:"unixMicroTime" time_format:"unixMicro"` } type FooStructForTimeTypeNotFormat struct { @@ -265,10 +269,10 @@ func TestBindingFormDefaultValue2(t *testing.T) { func TestBindingFormForTime(t *testing.T) { testFormBindingForTime(t, http.MethodPost, "/", "/", - "time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "bar2=foo") + "time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012", "bar2=foo") testFormBindingForTimeNotUnixFormat(t, http.MethodPost, "/", "/", - "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") + "time_foo=2017-11-15&createTime=bad&unixTime=bad&unixMilliTime=bad&unixMicroTime=bad", "bar2=foo") testFormBindingForTimeNotFormat(t, http.MethodPost, "/", "/", "time_foo=2017-11-15", "bar2=foo") @@ -282,11 +286,11 @@ func TestBindingFormForTime(t *testing.T) { func TestBindingFormForTime2(t *testing.T) { testFormBindingForTime(t, http.MethodGet, - "/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "/?bar2=foo", + "/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012", "/?bar2=foo", "", "") testFormBindingForTimeNotUnixFormat(t, http.MethodPost, "/", "/", - "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") + "time_foo=2017-11-15&createTime=bad&unixTime=bad&unixMilliTime=bad&unixMicroTime=bad", "bar2=foo") testFormBindingForTimeNotFormat(t, http.MethodGet, "/?time_foo=2017-11-15", "/?bar2=foo", "", "") @@ -952,6 +956,8 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s assert.Equal(t, "UTC", obj.TimeBar.Location().String()) assert.Equal(t, int64(1562400033000000123), obj.CreateTime.UnixNano()) assert.Equal(t, int64(1562400033), obj.UnixTime.Unix()) + assert.Equal(t, int64(1562400033001), obj.UnixMilliTime.UnixMilli()) + assert.Equal(t, int64(1562400033000012), obj.UnixMicroTime.UnixMicro()) obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index f5f6f3ae..235692d2 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -398,18 +398,24 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val } switch tf := strings.ToLower(timeFormat); tf { - case "unix", "unixnano": + case "unix", "unixmilli", "unixmicro", "unixnano": tv, err := strconv.ParseInt(val, 10, 64) if err != nil { return err } - d := time.Duration(1) - if tf == "unixnano" { - d = time.Second + var t time.Time + switch tf { + case "unix": + t = time.Unix(tv, 0) + case "unixmilli": + t = time.UnixMilli(tv) + case "unixmicro": + t = time.UnixMicro(tv) + default: + t = time.Unix(0, tv) } - t := time.Unix(tv/int64(d), tv%int64(d)) value.Set(reflect.ValueOf(t)) return nil } diff --git a/docs/doc.md b/docs/doc.md index a463e820..bea417b2 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -832,6 +832,8 @@ type Person struct { Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` CreateTime time.Time `form:"createTime" time_format:"unixNano"` UnixTime time.Time `form:"unixTime" time_format:"unix"` + UnixMilliTime time.Time `form:"unixMilliTime" time_format:"unixmilli"` + UnixMicroTime time.Time `form:"unixMicroTime" time_format:"uNiXmIcRo"` // case does not matter for "unix*" time formats } func main() { @@ -851,6 +853,8 @@ func startPage(c *gin.Context) { log.Println(person.Birthday) log.Println(person.CreateTime) log.Println(person.UnixTime) + log.Println(person.UnixMilliTime) + log.Println(person.UnixMicroTime) } c.String(http.StatusOK, "Success") @@ -860,7 +864,7 @@ func startPage(c *gin.Context) { Test it with: ```sh -curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" +curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012" ``` From e737e3e267beb4dc3bab16cc8be59e3902d98a94 Mon Sep 17 00:00:00 2001 From: revevide <158151416+revevide@users.noreply.github.com> Date: Thu, 20 Mar 2025 23:35:49 +0800 Subject: [PATCH 196/332] fix(binding): prevent duplicate decoding and add validation in decodeToml (#4193) --- binding/toml.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/toml.go b/binding/toml.go index a66b93aa..2681231d 100644 --- a/binding/toml.go +++ b/binding/toml.go @@ -31,5 +31,5 @@ func decodeToml(r io.Reader, obj any) error { if err := decoder.Decode(obj); err != nil { return err } - return decoder.Decode(obj) + return validate(obj) } From 8763f33c65f7df8be5b9fe7504ab7fcf20abb41d Mon Sep 17 00:00:00 2001 From: bound2 <9380102+bound2@users.noreply.github.com> Date: Thu, 20 Mar 2025 17:40:41 +0200 Subject: [PATCH 197/332] fix: prevent middleware re-entry issue in HandleContext (#3987) --- gin.go | 2 ++ gin_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/gin.go b/gin.go index e17596aa..0761c14d 100644 --- a/gin.go +++ b/gin.go @@ -637,10 +637,12 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Disclaimer: You can loop yourself to deal with this, use wisely. func (engine *Engine) HandleContext(c *Context) { oldIndexValue := c.index + oldHandlers := c.handlers c.reset() engine.handleHTTPRequest(c) c.index = oldIndexValue + c.handlers = oldHandlers } func (engine *Engine) handleHTTPRequest(c *Context) { diff --git a/gin_test.go b/gin_test.go index 732da18b..850ae09b 100644 --- a/gin_test.go +++ b/gin_test.go @@ -573,6 +573,44 @@ func TestEngineHandleContextManyReEntries(t *testing.T) { assert.Equal(t, int64(expectValue), middlewareCounter) } +func TestEngineHandleContextPreventsMiddlewareReEntry(t *testing.T) { + // given + var handlerCounterV1, handlerCounterV2, middlewareCounterV1 int64 + + r := New() + v1 := r.Group("/v1") + { + v1.Use(func(c *Context) { + atomic.AddInt64(&middlewareCounterV1, 1) + }) + v1.GET("/test", func(c *Context) { + atomic.AddInt64(&handlerCounterV1, 1) + c.Status(http.StatusOK) + }) + } + + v2 := r.Group("/v2") + { + v2.GET("/test", func(c *Context) { + c.Request.URL.Path = "/v1/test" + r.HandleContext(c) + }, func(c *Context) { + atomic.AddInt64(&handlerCounterV2, 1) + }) + } + + // when + responseV1 := PerformRequest(r, "GET", "/v1/test") + responseV2 := PerformRequest(r, "GET", "/v2/test") + + // then + assert.Equal(t, 200, responseV1.Code) + assert.Equal(t, 200, responseV2.Code) + assert.Equal(t, int64(2), handlerCounterV1) + assert.Equal(t, int64(2), middlewareCounterV1) + assert.Equal(t, int64(1), handlerCounterV2) +} + func TestPrepareTrustedCIRDsWith(t *testing.T) { r := New() From 3afff295a2e638075fcfbd2c38ee89a4263637d3 Mon Sep 17 00:00:00 2001 From: Andrey Bolonin Date: Fri, 11 Apr 2025 18:58:02 +0300 Subject: [PATCH 198/332] docs: add Upd language list (#4211) * Upd language list * Update url --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fe5722b2..aa8ada4e 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in - [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/) ### Articles From 1b53a477904c783c82cd6cc402aece4b9558a66e Mon Sep 17 00:00:00 2001 From: Adlai Bridson-Boyczuk <71486949+boyczuk@users.noreply.github.com> Date: Fri, 11 Apr 2025 11:59:03 -0400 Subject: [PATCH 199/332] docs: Fixing English grammar mistakes and awkward sentence structure in doc/doc.md (#4207) * docs: Fixing grammar mistakes and awkward sentences, such as modeling binding section * Update doc.md Missed grammar mistake --- docs/doc.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/doc.md b/docs/doc.md index bea417b2..cd651390 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -70,7 +70,7 @@ ### Build with json replacement -Gin uses `encoding/json` as default json package but you can change it by build from other tags. +Gin uses `encoding/json` as the default JSON package but you can change it by building from other tags. [jsoniter](https://github.com/json-iterator/go) @@ -84,7 +84,7 @@ go build -tags=jsoniter . go build -tags=go_json . ``` -[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.) +[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu supports avx instruction.) ```sh $ go build -tags="sonic avx" . @@ -120,7 +120,7 @@ func main() { router.HEAD("/someHead", head) router.OPTIONS("/someOptions", options) - // By default it serves on :8080 unless a + // By default, it serves on :8080 unless a // PORT environment variable was defined. router.Run() // router.Run(":3000") for a hard coded port @@ -172,7 +172,7 @@ func main() { router := gin.Default() // Query string parameters are parsed using the existing underlying request object. - // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe + // The request responds to a URL matching: /welcome?firstname=Jane&lastname=Doe router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") @@ -300,7 +300,7 @@ curl -X POST http://localhost:8080/upload \ #### Multiple files -See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). +See the detailed [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). ```go func main() { @@ -704,7 +704,7 @@ $ curl -v -X POST \ {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} ``` -Skip validate: when running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. +Skip-validation: Running the example above using the `curl` command returns an error. This is because the example uses `binding:"required"` for `Password`. If instead, you use `binding:"-"` for `Password`, then it will not return an error when you run the example again. ### Custom Validators @@ -1187,7 +1187,7 @@ func main() { }) r.GET("/moreJSON", func(c *gin.Context) { - // You also can use a struct + // You can also use a struct var msg struct { Name string `json:"user"` Message string @@ -1490,7 +1490,7 @@ You may use custom delims #### Custom Template Funcs -See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). +See the detailed [example code](https://github.com/gin-gonic/examples/tree/master/template). main.go @@ -1542,7 +1542,7 @@ Date: 2017/07/01 ### Multitemplate -Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. +Gin allows only one html.Template by default. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. ### Redirects @@ -2091,7 +2091,7 @@ type formB struct { func SomeHandler(c *gin.Context) { objA := formA{} objB := formB{} - // This c.ShouldBind consumes c.Request.Body and it cannot be reused. + // Calling c.ShouldBind consumes c.Request.Body and it cannot be reused. if errA := c.ShouldBind(&objA); errA == nil { c.String(http.StatusOK, `the body should be formA`) // Always an error is occurred by this because c.Request.Body is EOF now. @@ -2324,7 +2324,7 @@ or network CIDRs from where clients which their request headers related to clien IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs. -**Attention:** Gin trust all proxies by default if you don't specify a trusted +**Attention:** Gin trusts all proxies by default if you don't specify a trusted proxy using the function above, **this is NOT safe**. At the same time, if you don't use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`, then `Context.ClientIP()` will return the remote address directly to avoid some From 49e9137c68e6dfaa529a2d0c9fe64d9e69a8554e Mon Sep 17 00:00:00 2001 From: NezhaFan Date: Sat, 12 Apr 2025 00:00:59 +0800 Subject: [PATCH 200/332] docs: fix comment (#4205) Co-authored-by: voyager1 --- binding/protobuf.go | 2 +- ginS/gins.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/binding/protobuf.go b/binding/protobuf.go index 57721fc9..259ae8e7 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -34,7 +34,7 @@ func (protobufBinding) BindBody(body []byte, obj any) error { if err := proto.Unmarshal(body, msg); err != nil { return err } - // Here it's same to return validate(obj), but util now we can't add + // Here it's same to return validate(obj), but until now we can't add // `binding:""` to the struct which automatically generate by gen-proto return nil // return validate(obj) diff --git a/ginS/gins.go b/ginS/gins.go index ea38c613..1dcb1919 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -154,7 +154,7 @@ func RunUnix(file string) (err error) { // RunFd attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified file descriptor. -// Note: the method will block the calling goroutine indefinitely unless on error happens. +// Note: the method will block the calling goroutine indefinitely unless an error happens. func RunFd(fd int) (err error) { return engine().RunFd(fd) } From 3319038418656a268c889393cb2dd4224c4469ec Mon Sep 17 00:00:00 2001 From: eduardo-ax Date: Sun, 20 Apr 2025 13:01:03 -0300 Subject: [PATCH 201/332] fix(readme): fix broken link to English documentation (#4222) Co-authored-by: Eduardo Alexandre --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa8ada4e..9f548cc0 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ See the [API documentation on go.dev](https://pkg.go.dev/github.com/gin-gonic/gi The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages: -- [English](https://gin-gonic.com/docs/) +- [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/) From 56fccc39ec5cbe30e39fa34e67371219354f14cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:01:35 +0800 Subject: [PATCH 202/332] chore(deps): bump golang.org/x/net from 0.37.0 to 0.38.0 (#4221) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.37.0 to 0.38.0. - [Commits](https://github.com/golang/net/compare/v0.37.0...v0.38.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.38.0 dependency-type: direct:production ... 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 1223267c..1756a917 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/quic-go/quic-go v0.48.2 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.37.0 + golang.org/x/net v0.38.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 2e1c30ad..71fc142a 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,8 @@ golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJ golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From afa0c31d97e1b112ccfe7652957f7d8514580c72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:02:02 +0800 Subject: [PATCH 203/332] chore(deps): bump github.com/gin-contrib/sse from 0.1.0 to 1.1.0 (#4216) Bumps [github.com/gin-contrib/sse](https://github.com/gin-contrib/sse) from 0.1.0 to 1.1.0. - [Release notes](https://github.com/gin-contrib/sse/releases) - [Changelog](https://github.com/gin-contrib/sse/blob/master/.goreleaser.yaml) - [Commits](https://github.com/gin-contrib/sse/compare/v0.1.0...v1.1.0) --- updated-dependencies: - dependency-name: github.com/gin-contrib/sse dependency-version: 1.1.0 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> --- go.mod | 4 ++-- go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 1756a917..b993ca53 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.23.0 require ( github.com/bytedance/sonic v1.13.1 - github.com/gin-contrib/sse v0.1.0 + github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.22.1 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.2 github.com/quic-go/quic-go v0.48.2 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.38.0 google.golang.org/protobuf v1.34.1 diff --git a/go.sum b/go.sum index 71fc142a..c107dc03 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ 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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -74,8 +74,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= From 0eb99493c28b09cee339061b0d8a11c9a4f31399 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Mon, 21 Apr 2025 00:05:34 +0800 Subject: [PATCH 204/332] perf: optimize AsciiJSON.Render method by using fmt.Appendf and reusing temp buffer (#4175) per: use bytesconv.BytesToString(ret) instead of string(str) Co-authored-by: 1911860538 --- render/json.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/render/json.go b/render/json.go index fc8dea45..a6b54dc3 100644 --- a/render/json.go +++ b/render/json.go @@ -151,7 +151,7 @@ func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { } // Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType. -func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { +func (r AsciiJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) ret, err := json.Marshal(r.Data) if err != nil { @@ -159,12 +159,15 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { } var buffer bytes.Buffer + escapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences + for _, r := range bytesconv.BytesToString(ret) { - cvt := string(r) if r >= 128 { - cvt = fmt.Sprintf("\\u%04x", int64(r)) + escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) // Reuse escapeBuf + buffer.Write(escapeBuf) + } else { + buffer.WriteByte(byte(r)) } - buffer.WriteString(cvt) } _, err = w.Write(buffer.Bytes()) From 71496abe6836462e2ed70436b7d72ea2a3585417 Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Mon, 21 Apr 2025 00:11:16 +0800 Subject: [PATCH 205/332] feat(fs): Implement loading HTML from http.FileSystem (#4053) * Implement loading HTML from http.FileSystem * Add OnlyHTMLFS test * Move OnlyHTMLFS to internal and add test --- docs/doc.md | 8 ++- gin.go | 14 ++++++ ginS/gins.go | 5 ++ gin_test.go | 109 +++++++++++++++++++++++++++++++++++++++++ internal/fs/fs.go | 22 +++++++++ internal/fs/fs_test.go | 49 ++++++++++++++++++ render/html.go | 18 +++++-- render/render_test.go | 51 ++++++++++++++----- 8 files changed, 258 insertions(+), 18 deletions(-) create mode 100644 internal/fs/fs.go create mode 100644 internal/fs/fs_test.go diff --git a/docs/doc.md b/docs/doc.md index cd651390..ce466652 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -1393,13 +1393,19 @@ func main() { ### HTML rendering -Using LoadHTMLGlob() or LoadHTMLFiles() +Using LoadHTMLGlob() or LoadHTMLFiles() or LoadHTMLFS() ```go +//go:embed templates/* +var templates embed.FS + func main() { router := gin.Default() router.LoadHTMLGlob("templates/*") //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") + //router.LoadHTMLFS(http.Dir("templates"), "template1.html", "template2.html") + //or + //router.LoadHTMLFS(http.FS(templates), "templates/template1.html", "templates/template2.html") router.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl", gin.H{ "title": "Main website", diff --git a/gin.go b/gin.go index 0761c14d..f9813e1d 100644 --- a/gin.go +++ b/gin.go @@ -16,6 +16,7 @@ import ( "sync" "github.com/gin-gonic/gin/internal/bytesconv" + filesystem "github.com/gin-gonic/gin/internal/fs" "github.com/gin-gonic/gin/render" "github.com/quic-go/quic-go/http3" @@ -285,6 +286,19 @@ func (engine *Engine) LoadHTMLFiles(files ...string) { engine.SetHTMLTemplate(templ) } +// LoadHTMLFS loads an http.FileSystem and a slice of patterns +// and associates the result with HTML renderer. +func (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) { + if IsDebugging() { + engine.HTMLRender = render.HTMLDebug{FileSystem: fs, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims} + return + } + + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS( + filesystem.FileSystem{FileSystem: fs}, patterns...)) + engine.SetHTMLTemplate(templ) +} + // SetHTMLTemplate associate a template with HTML renderer. func (engine *Engine) SetHTMLTemplate(templ *template.Template) { if len(engine.trees) > 0 { diff --git a/ginS/gins.go b/ginS/gins.go index 1dcb1919..3e6a92eb 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -32,6 +32,11 @@ func LoadHTMLFiles(files ...string) { engine().LoadHTMLFiles(files...) } +// LoadHTMLFS is a wrapper for Engine.LoadHTMLFS. +func LoadHTMLFS(fs http.FileSystem, patterns ...string) { + engine().LoadHTMLFS(fs, patterns...) +} + // SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate. func SetHTMLTemplate(templ *template.Template) { engine().SetHTMLTemplate(templ) diff --git a/gin_test.go b/gin_test.go index 850ae09b..a80b690e 100644 --- a/gin_test.go +++ b/gin_test.go @@ -325,6 +325,115 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { assert.Equal(t, "Date: 2017/07/01", string(resp)) } +var tmplFS = http.Dir("testdata/template") + +func TestLoadHTMLFSTestMode(t *testing.T) { + ts := setupHTMLFiles( + t, + TestMode, + false, + func(router *Engine) { + router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + if err != nil { + t.Error(err) + } + + resp, _ := io.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp)) +} + +func TestLoadHTMLFSDebugMode(t *testing.T) { + ts := setupHTMLFiles( + t, + DebugMode, + false, + func(router *Engine) { + router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + if err != nil { + t.Error(err) + } + + resp, _ := io.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp)) +} + +func TestLoadHTMLFSReleaseMode(t *testing.T) { + ts := setupHTMLFiles( + t, + ReleaseMode, + false, + func(router *Engine) { + router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + if err != nil { + t.Error(err) + } + + resp, _ := io.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp)) +} + +func TestLoadHTMLFSUsingTLS(t *testing.T) { + ts := setupHTMLFiles( + t, + TestMode, + true, + func(router *Engine) { + router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl") + }, + ) + defer ts.Close() + + // Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{Transport: tr} + res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) + if err != nil { + t.Error(err) + } + + resp, _ := io.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp)) +} + +func TestLoadHTMLFSFuncMap(t *testing.T) { + ts := setupHTMLFiles( + t, + TestMode, + false, + func(router *Engine) { + router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) + if err != nil { + t.Error(err) + } + + resp, _ := io.ReadAll(res.Body) + assert.Equal(t, "Date: 2017/07/01", string(resp)) +} + func TestAddRoute(t *testing.T) { router := New() router.addRoute(http.MethodGet, "/", HandlersChain{func(_ *Context) {}}) diff --git a/internal/fs/fs.go b/internal/fs/fs.go new file mode 100644 index 00000000..524ac08b --- /dev/null +++ b/internal/fs/fs.go @@ -0,0 +1,22 @@ +package fs + +import ( + "io/fs" + "net/http" +) + +// FileSystem implements an [fs.FS]. +type FileSystem struct { + http.FileSystem +} + +// Open passes `Open` to the upstream implementation and return an [fs.File]. +func (o FileSystem) Open(name string) (fs.File, error) { + f, err := o.FileSystem.Open(name) + + if err != nil { + return nil, err + } + + return fs.File(f), nil +} diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go new file mode 100644 index 00000000..113e92b6 --- /dev/null +++ b/internal/fs/fs_test.go @@ -0,0 +1,49 @@ +package fs + +import ( + "errors" + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type mockFileSystem struct { + open func(name string) (http.File, error) +} + +func (m *mockFileSystem) Open(name string) (http.File, error) { + return m.open(name) +} + +func TesFileSystem_Open(t *testing.T) { + var testFile *os.File + mockFS := &mockFileSystem{ + open: func(name string) (http.File, error) { + return testFile, nil + }, + } + fs := &FileSystem{mockFS} + + file, err := fs.Open("foo") + + require.NoError(t, err) + assert.Equal(t, testFile, file) +} + +func TestFileSystem_Open_err(t *testing.T) { + testError := errors.New("mock") + mockFS := &mockFileSystem{ + open: func(_ string) (http.File, error) { + return nil, testError + }, + } + fs := &FileSystem{mockFS} + + file, err := fs.Open("foo") + + require.ErrorIs(t, err, testError) + assert.Nil(t, file) +} diff --git a/render/html.go b/render/html.go index c308408d..f5e7455a 100644 --- a/render/html.go +++ b/render/html.go @@ -7,6 +7,8 @@ package render import ( "html/template" "net/http" + + "github.com/gin-gonic/gin/internal/fs" ) // Delims represents a set of Left and Right delimiters for HTML template rendering. @@ -31,10 +33,12 @@ type HTMLProduction struct { // HTMLDebug contains template delims and pattern and function with file list. type HTMLDebug struct { - Files []string - Glob string - Delims Delims - FuncMap template.FuncMap + Files []string + Glob string + FileSystem http.FileSystem + Patterns []string + Delims Delims + FuncMap template.FuncMap } // HTML contains template reference and its name with given interface object. @@ -73,7 +77,11 @@ func (r HTMLDebug) loadTemplate() *template.Template { if r.Glob != "" { return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob)) } - panic("the HTML debug render was created without files or glob pattern") + if r.FileSystem != nil && len(r.Patterns) > 0 { + return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS( + fs.FileSystem{FileSystem: r.FileSystem}, r.Patterns...)) + } + panic("the HTML debug render was created without files or glob pattern or file system with patterns") } // Render (HTML) executes template and writes its result with custom ContentType for response. diff --git a/render/render_test.go b/render/render_test.go index ad633b00..4dd2a3af 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -489,10 +489,12 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { func TestRenderHTMLDebugFiles(t *testing.T) { w := httptest.NewRecorder() htmlRender := HTMLDebug{ - Files: []string{"../testdata/template/hello.tmpl"}, - Glob: "", - Delims: Delims{Left: "{[{", Right: "}]}"}, - FuncMap: nil, + Files: []string{"../testdata/template/hello.tmpl"}, + Glob: "", + FileSystem: nil, + Patterns: nil, + Delims: Delims{Left: "{[{", Right: "}]}"}, + FuncMap: nil, } instance := htmlRender.Instance("hello.tmpl", map[string]any{ "name": "thinkerou", @@ -508,10 +510,33 @@ func TestRenderHTMLDebugFiles(t *testing.T) { func TestRenderHTMLDebugGlob(t *testing.T) { w := httptest.NewRecorder() htmlRender := HTMLDebug{ - Files: nil, - Glob: "../testdata/template/hello*", - Delims: Delims{Left: "{[{", Right: "}]}"}, - FuncMap: nil, + Files: nil, + Glob: "../testdata/template/hello*", + FileSystem: nil, + Patterns: nil, + Delims: Delims{Left: "{[{", Right: "}]}"}, + FuncMap: nil, + } + instance := htmlRender.Instance("hello.tmpl", map[string]any{ + "name": "thinkerou", + }) + + err := instance.Render(w) + + require.NoError(t, err) + assert.Equal(t, "

Hello thinkerou

", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) +} + +func TestRenderHTMLDebugFS(t *testing.T) { + w := httptest.NewRecorder() + htmlRender := HTMLDebug{ + Files: nil, + Glob: "", + FileSystem: http.Dir("../testdata/template"), + Patterns: []string{"hello.tmpl"}, + Delims: Delims{Left: "{[{", Right: "}]}"}, + FuncMap: nil, } instance := htmlRender.Instance("hello.tmpl", map[string]any{ "name": "thinkerou", @@ -526,10 +551,12 @@ func TestRenderHTMLDebugGlob(t *testing.T) { func TestRenderHTMLDebugPanics(t *testing.T) { htmlRender := HTMLDebug{ - Files: nil, - Glob: "", - Delims: Delims{"{{", "}}"}, - FuncMap: nil, + Files: nil, + Glob: "", + FileSystem: nil, + Patterns: nil, + Delims: Delims{"{{", "}}"}, + FuncMap: nil, } assert.Panics(t, func() { htmlRender.Instance("", nil) }) } From 255af882db4baf0ed6209f1a5471f1663c5d0060 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:14:45 +0800 Subject: [PATCH 206/332] chore(deps): bump github.com/go-playground/validator/v10 (#4208) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.22.1 to 10.26.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.22.1...v10.26.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-version: 10.26.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 | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b993ca53..397db4a4 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 require ( github.com/bytedance/sonic v1.13.1 github.com/gin-contrib/sse v1.1.0 - github.com/go-playground/validator/v10 v10.22.1 + github.com/go-playground/validator/v10 v10.26.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 @@ -22,7 +22,7 @@ require ( github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // 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/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect diff --git a/go.sum b/go.sum index c107dc03..d2fc985e 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ 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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +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/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -24,8 +24,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.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= -github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= From bb824731032856460aa3ffc23bd90e11bf7d5199 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:15:51 +0800 Subject: [PATCH 207/332] chore(deps): bump github.com/quic-go/quic-go from 0.48.2 to 0.50.1 (#4197) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.48.2 to 0.50.1. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md) - [Commits](https://github.com/quic-go/quic-go/compare/v0.48.2...v0.50.1) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go 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 | 10 +++++----- go.sum | 20 ++++++++------------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 397db4a4..3a7e1ba6 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.2 - github.com/quic-go/quic-go v0.48.2 + github.com/quic-go/quic-go v0.51.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.38.0 @@ -35,12 +35,12 @@ 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.4.0 // indirect + go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect - golang.org/x/mod v0.17.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/tools v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index d2fc985e..5a7f2adf 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,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.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= -github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= +github.com/quic-go/quic-go v0.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc= +github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= 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= @@ -81,16 +81,14 @@ 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.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +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/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= @@ -101,10 +99,8 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 67c9d4ee110e9adfe33063ef847dba56717c148a Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Mon, 21 Apr 2025 22:05:28 +0800 Subject: [PATCH 208/332] refactor: replace magic number 128 with unicode.MaxASCII in AsciiJSON Render (#4224) Co-authored-by: huangzw --- render/json.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/render/json.go b/render/json.go index a6b54dc3..23923c44 100644 --- a/render/json.go +++ b/render/json.go @@ -9,6 +9,7 @@ import ( "fmt" "html/template" "net/http" + "unicode" "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/internal/json" @@ -162,7 +163,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) error { escapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences for _, r := range bytesconv.BytesToString(ret) { - if r >= 128 { + if r > unicode.MaxASCII { escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) // Reuse escapeBuf buffer.Write(escapeBuf) } else { From 7a1b655074c313f9491c83bb8ea164cdc4a9afe9 Mon Sep 17 00:00:00 2001 From: Yash Date: Sun, 11 May 2025 20:04:09 +0530 Subject: [PATCH 209/332] fix: sonic on arm64 (#4234) --- .github/workflows/gin.yml | 2 +- docs/doc.md | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- internal/json/json.go | 2 +- internal/json/sonic.go | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 1062252a..54185a0a 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -38,7 +38,7 @@ jobs: [ "", "-tags nomsgpack", - '--ldflags="-checklinkname=0" -tags "sonic avx"', + '--ldflags="-checklinkname=0" -tags sonic', "-tags go_json", "-race", ] diff --git a/docs/doc.md b/docs/doc.md index ce466652..9b7b1ec9 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -84,10 +84,10 @@ go build -tags=jsoniter . go build -tags=go_json . ``` -[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu supports avx instruction.) +[sonic](https://github.com/bytedance/sonic) ```sh -$ go build -tags="sonic avx" . +$ go build -tags=sonic . ``` ### Build without `MsgPack` rendering feature diff --git a/go.mod b/go.mod index 3a7e1ba6..15475588 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.1 + github.com/bytedance/sonic v1.13.2 github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.26.0 github.com/goccy/go-json v0.10.2 diff --git a/go.sum b/go.sum index 5a7f2adf..58aea722 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= -github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +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= diff --git a/internal/json/json.go b/internal/json/json.go index c7ee83eb..26817786 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64) +//go:build !jsoniter && !go_json && !(sonic && (linux || windows || darwin)) package json diff --git a/internal/json/sonic.go b/internal/json/sonic.go index 529e16d0..8cd88049 100644 --- a/internal/json/sonic.go +++ b/internal/json/sonic.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build sonic && avx && (linux || windows || darwin) && amd64 +//go:build sonic && (linux || windows || darwin) package json From 4714c2a9a39f0877ccb38089894263f052025a6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 22:34:39 +0800 Subject: [PATCH 210/332] chore(deps): bump google.golang.org/protobuf from 1.34.1 to 1.36.6 (#4198) Bumps google.golang.org/protobuf from 1.34.1 to 1.36.6. --- updated-dependencies: - dependency-name: google.golang.org/protobuf 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 15475588..5aa6b36e 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.38.0 - google.golang.org/protobuf v1.34.1 + google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 58aea722..75b73d8c 100644 --- a/go.sum +++ b/go.sum @@ -101,8 +101,8 @@ golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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 cf32d2dcf8c7534f59727c5e213e45f2412c593a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 22:35:03 +0800 Subject: [PATCH 211/332] chore(deps): bump github.com/pelletier/go-toml/v2 from 2.2.2 to 2.2.4 (#4212) Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.2.2 to 2.2.4. - [Release notes](https://github.com/pelletier/go-toml/releases) - [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml) - [Commits](https://github.com/pelletier/go-toml/compare/v2.2.2...v2.2.4) --- updated-dependencies: - dependency-name: github.com/pelletier/go-toml/v2 dependency-version: 2.2.4 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 | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5aa6b36e..90014fb5 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 - github.com/pelletier/go-toml/v2 v2.2.2 + github.com/pelletier/go-toml/v2 v2.2.4 github.com/quic-go/quic-go v0.51.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 diff --git a/go.sum b/go.sum index 75b73d8c..f8d0534b 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +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= @@ -66,15 +66,12 @@ github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/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.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= From b38c59de7fef67400a1c98efeae700a689c45783 Mon Sep 17 00:00:00 2001 From: Orkhan Alikhanov Date: Sun, 11 May 2025 18:38:33 +0400 Subject: [PATCH 212/332] fix(errors): change Unwrap method receiver to value type (#4232) --- errors.go | 2 +- errors_test.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 06b53c28..b0d70a94 100644 --- a/errors.go +++ b/errors.go @@ -91,7 +91,7 @@ func (msg *Error) IsType(flags ErrorType) bool { } // Unwrap returns the wrapped error, to allow interoperability with errors.Is(), errors.As() and errors.Unwrap() -func (msg *Error) Unwrap() error { +func (msg Error) Unwrap() error { return msg.Err } diff --git a/errors_test.go b/errors_test.go index 72a36992..91ae9c92 100644 --- a/errors_test.go +++ b/errors_test.go @@ -126,4 +126,15 @@ func TestErrorUnwrap(t *testing.T) { require.ErrorIs(t, err, innerErr) var testErr TestErr require.ErrorAs(t, err, &testErr) + + // Test non-pointer usage of gin.Error + errNonPointer := Error{ + Err: innerErr, + Type: ErrorTypeAny, + } + wrappedErr := fmt.Errorf("wrapped: %w", errNonPointer) + // Check that 'errors.Is()' and 'errors.As()' behave as expected for non-pointer usage + require.ErrorIs(t, wrappedErr, innerErr) + var testErrNonPointer TestErr + require.ErrorAs(t, wrappedErr, &testErrNonPointer) } From ef68fa032c0e6ce637db56e89ec734c0de0a9f5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 22:42:01 +0800 Subject: [PATCH 213/332] chore(deps): bump golang.org/x/net from 0.38.0 to 0.40.0 (#4229) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.38.0 to 0.40.0. - [Commits](https://github.com/golang/net/compare/v0.38.0...v0.40.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.40.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 | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 90014fb5..0a5c626d 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/quic-go/quic-go v0.51.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.38.0 + golang.org/x/net v0.40.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 ) @@ -37,10 +37,10 @@ require ( 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/crypto v0.36.0 // indirect + golang.org/x/crypto v0.38.0 // indirect golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/tools v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index f8d0534b..b047363b 100644 --- a/go.sum +++ b/go.sum @@ -82,20 +82,20 @@ 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/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= From da67cc1b988ed6498eccaecb989467e87b555dfd Mon Sep 17 00:00:00 2001 From: Siddhesh Mhadnak <10049286+sid-maddy@users.noreply.github.com> Date: Tue, 20 May 2025 15:46:21 +0530 Subject: [PATCH 214/332] test: fix lint failures (#4244) --- gin_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gin_test.go b/gin_test.go index a80b690e..250269e5 100644 --- a/gin_test.go +++ b/gin_test.go @@ -338,7 +338,7 @@ func TestLoadHTMLFSTestMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -358,7 +358,7 @@ func TestLoadHTMLFSDebugMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -378,7 +378,7 @@ func TestLoadHTMLFSReleaseMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -405,7 +405,7 @@ func TestLoadHTMLFSUsingTLS(t *testing.T) { }, } client := &http.Client{Transport: tr} - res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := client.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -425,7 +425,7 @@ func TestLoadHTMLFSFuncMap(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) + res, err := http.Get(ts.URL + "/raw") if err != nil { t.Error(err) } @@ -847,7 +847,7 @@ func handlerTest1(c *Context) {} func handlerTest2(c *Context) {} func TestNewOptionFunc(t *testing.T) { - var fc = func(e *Engine) { + fc := func(e *Engine) { e.GET("/test1", handlerTest1) e.GET("/test2", handlerTest2) From 2e2bd1f408fdaa5e522bc56972e3f109fd7502fd Mon Sep 17 00:00:00 2001 From: Salim Absi Date: Tue, 20 May 2025 13:29:39 +0300 Subject: [PATCH 215/332] test(internal/fs): fix test function name (#4235) --- internal/fs/fs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index 113e92b6..f937cf7f 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -18,7 +18,7 @@ func (m *mockFileSystem) Open(name string) (http.File, error) { return m.open(name) } -func TesFileSystem_Open(t *testing.T) { +func TestFileSystem_Open(t *testing.T) { var testFile *os.File mockFS := &mockFileSystem{ open: func(name string) (http.File, error) { From 3d8e288c64aa3d3064df6264ab1b5445a28b709d Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Tue, 20 May 2025 22:58:34 +0800 Subject: [PATCH 216/332] perf(all): use strings.Cut to replace strings.SplitN (#4239) Co-authored-by: 1911860538 --- recovery.go | 6 +++--- tree.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/recovery.go b/recovery.go index 515f9d2a..8e4633b4 100644 --- a/recovery.go +++ b/recovery.go @@ -74,9 +74,9 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { httpRequest, _ := httputil.DumpRequest(c.Request, false) headers := strings.Split(string(httpRequest), "\r\n") for idx, header := range headers { - current := strings.Split(header, ":") - if current[0] == "Authorization" { - headers[idx] = current[0] + ": *" + key, _, _ := strings.Cut(header, ":") + if key == "Authorization" { + headers[idx] = key + ": *" } } headersToStr := strings.Join(headers, "\r\n") diff --git a/tree.go b/tree.go index 0d3e5a8c..a298d748 100644 --- a/tree.go +++ b/tree.go @@ -234,7 +234,7 @@ walk: // Wildcard conflict pathSeg := path if n.nType != catchAll { - pathSeg = strings.SplitN(pathSeg, "/", 2)[0] + pathSeg, _, _ = strings.Cut(pathSeg, "/") } prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path panic("'" + pathSeg + @@ -358,7 +358,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { pathSeg := "" if len(n.children) != 0 { - pathSeg = strings.SplitN(n.children[0].path, "/", 2)[0] + pathSeg, _, _ = strings.Cut(n.children[0].path, "/") } panic("catch-all wildcard '" + path + "' in new path '" + fullPath + From fb09c825e8e13134daaa90debfda198520e1b347 Mon Sep 17 00:00:00 2001 From: NARITA <58836324+Narita-1095305@users.noreply.github.com> Date: Wed, 21 May 2025 09:20:44 +0900 Subject: [PATCH 217/332] feat(context): add SetCookieData (#4240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(context): add SetCookieStruct (#4215)# This is a combination of 2 commits. feat(context): add SetCookieStruct (#4215) feat(context): add SetCookieStruct (#4215) * feat(context): add SetCookieStruct (#4215) * feat(context): fix SetCookieStructโ†’SetCookieData (gin-gonic#4215) * fix(context): respect caller-specified SameSite value in SetCookieData --- context.go | 13 +++++ context_test.go | 126 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/doc.md | 54 ++++++++++++++++++++- 3 files changed, 192 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 1c76c0f6..d3e4c6d7 100644 --- a/context.go +++ b/context.go @@ -1027,6 +1027,19 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, }) } +// SetCookieData adds a Set-Cookie header to the ResponseWriter's headers. +// It accepts a pointer to http.Cookie structure for more flexibility in setting cookie attributes. +// The provided cookie must have a valid Name. Invalid cookies may be silently dropped. +func (c *Context) SetCookieData(cookie *http.Cookie) { + if cookie.Path == "" { + cookie.Path = "/" + } + if cookie.SameSite == http.SameSiteDefaultMode { + cookie.SameSite = c.sameSite + } + http.SetCookie(c.Writer, cookie) +} + // Cookie returns the named cookie provided in the request or // ErrNoCookie if not found. And return the named cookie is unescaped. // If multiple cookies match the given name, only one cookie will diff --git a/context_test.go b/context_test.go index ef0cfccd..c2f82410 100644 --- a/context_test.go +++ b/context_test.go @@ -3123,3 +3123,129 @@ func TestContextNext(t *testing.T) { assert.True(t, exists) assert.Equal(t, "value3", value) } + +func TestContextSetCookieData(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.SetSameSite(http.SameSiteLaxMode) + var setCookie string + + // Basic cookie settings + cookie := &http.Cookie{ + Name: "user", + Value: "gin", + MaxAge: 1, + Path: "/", + Domain: "localhost", + Secure: true, + HttpOnly: true, + } + c.SetCookieData(cookie) + setCookie = c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "user=gin") + assert.Contains(t, setCookie, "Path=/") + assert.Contains(t, setCookie, "Domain=localhost") + 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 + // assert.Contains(t, setCookie, "SameSite=Lax") + + // Test that when Path is empty, "/" is automatically set + cookie = &http.Cookie{ + Name: "user", + Value: "gin", + MaxAge: 1, + Path: "", + Domain: "localhost", + Secure: true, + HttpOnly: true, + } + c.SetCookieData(cookie) + setCookie = c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "user=gin") + assert.Contains(t, setCookie, "Path=/") + assert.Contains(t, setCookie, "Domain=localhost") + 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 + // assert.Contains(t, setCookie, "SameSite=Lax") + + // Test additional cookie attributes (Expires) + expireTime := time.Now().Add(24 * time.Hour) + cookie = &http.Cookie{ + Name: "user", + Value: "gin", + Path: "/", + Domain: "localhost", + Expires: expireTime, + Secure: true, + HttpOnly: true, + } + c.SetCookieData(cookie) + + // Since the Expires value varies by time, partially verify with Contains + setCookie = c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "user=gin") + assert.Contains(t, setCookie, "Path=/") + 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 + // assert.Contains(t, setCookie, "SameSite=Lax") + + // Test for Partitioned attribute (Go 1.18+) + cookie = &http.Cookie{ + Name: "user", + Value: "gin", + Path: "/", + Domain: "localhost", + Secure: true, + HttpOnly: true, + Partitioned: true, + } + c.SetCookieData(cookie) + setCookie = c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "user=gin") + assert.Contains(t, setCookie, "Path=/") + 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 + // assert.Contains(t, setCookie, "SameSite=Lax") + // Not testing for Partitioned attribute as it may not be supported in all Go versions + + // Test that SameSiteStrictMode is explicitly included in the header + t.Run("SameSite=Strict is included", func(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + cookie := &http.Cookie{ + Name: "user", + Value: "gin", + Path: "/", + Domain: "localhost", + Secure: true, + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + } + c.SetCookieData(cookie) + setCookie := c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "SameSite=Strict") + }) + + // Test that SameSiteNoneMode is explicitly included in the header + t.Run("SameSite=None is included", func(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + cookie := &http.Cookie{ + Name: "user", + Value: "gin", + Path: "/", + Domain: "localhost", + Secure: true, + HttpOnly: true, + SameSite: http.SameSiteNoneMode, + } + c.SetCookieData(cookie) + setCookie := c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "SameSite=None") + }) +} diff --git a/docs/doc.md b/docs/doc.md index 9b7b1ec9..0e882a1b 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -2304,12 +2304,64 @@ func main() { router := gin.Default() router.GET("/cookie", func(c *gin.Context) { + cookie, err := c.Cookie("gin_cookie") + if err != nil { + cookie = "NotSet" + // Using http.Cookie struct for more control + c.SetCookieData(&http.Cookie{ + Name: "gin_cookie", + Value: "test", + Path: "/", + Domain: "localhost", + MaxAge: 3600, + Secure: false, + HttpOnly: true, + // Additional fields available in http.Cookie + Expires: time.Now().Add(24 * time.Hour), + // Partitioned: true, // Available in newer Go versions + }) + } + + fmt.Printf("Cookie value: %s \n", cookie) + }) + + router.Run() +} +``` + +You can also use the `SetCookieData` method, which accepts a `*http.Cookie` directly for more flexibility: + +```go +import ( + "fmt" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +func main() { + router := gin.Default() + + router.GET("/cookie", func(c *gin.Context) { cookie, err := c.Cookie("gin_cookie") if err != nil { cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + // Using http.Cookie struct for more control + c.SetCookieData(&http.Cookie{ + Name: "gin_cookie", + Value: "test", + Path: "/", + Domain: "localhost", + MaxAge: 3600, + Secure: false, + HttpOnly: true, + // Additional fields available in http.Cookie + Expires: time.Now().Add(24 * time.Hour), + // Partitioned: true, // Available in newer Go versions + }) } fmt.Printf("Cookie value: %s \n", cookie) From 19f5a13fb421fd71c10a06f244734bba317f63a6 Mon Sep 17 00:00:00 2001 From: Liu Ziming Date: Wed, 21 May 2025 08:25:00 +0800 Subject: [PATCH 218/332] docs(readme): add gin-gonic/contrib (#4134) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f548cc0..1a582709 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr ## Middleware -You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). +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). ## Uses From d00e6a5695c14933081faaba74e92d95cca9377f Mon Sep 17 00:00:00 2001 From: yangquanshi Date: Wed, 21 May 2025 20:14:28 +0900 Subject: [PATCH 219/332] chore: fix some function names in comment (#4131) Signed-off-by: yangquanshi --- context_test.go | 4 ++-- middleware_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/context_test.go b/context_test.go index c2f82410..9857272c 100644 --- a/context_test.go +++ b/context_test.go @@ -1142,7 +1142,7 @@ func TestContextRenderNoContentXML(t *testing.T) { assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } -// TestContextString tests that the response is returned +// TestContextRenderString tests that the response is returned // with Content-Type set to text/plain func TestContextRenderString(t *testing.T) { w := httptest.NewRecorder() @@ -1167,7 +1167,7 @@ func TestContextRenderNoContentString(t *testing.T) { assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } -// TestContextString tests that the response is returned +// TestContextRenderHTMLString tests that the response is returned // with Content-Type set to text/html func TestContextRenderHTMLString(t *testing.T) { w := httptest.NewRecorder() diff --git a/middleware_test.go b/middleware_test.go index eafc60ad..4390def7 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -203,7 +203,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { assert.Equal(t, "ACB", signature) } -// TestFailHandlersChain - ensure that Fail interrupt used middleware in fifo order as +// TestMiddlewareFailHandlersChain - ensure that Fail interrupt used middleware in fifo order as // as well as Abort func TestMiddlewareFailHandlersChain(t *testing.T) { // SETUP From 8f7c340577e19245827f7ba71ef3e0143cc7eeee Mon Sep 17 00:00:00 2001 From: Andreas Deininger Date: Wed, 21 May 2025 13:16:29 +0200 Subject: [PATCH 220/332] context_test.go: fix useless asserts (#4115) --- context_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index 9857272c..ebcd08d4 100644 --- a/context_test.go +++ b/context_test.go @@ -885,10 +885,10 @@ func TestContextGetCookie(t *testing.T) { } func TestContextBodyAllowedForStatus(t *testing.T) { - assert.False(t, false, bodyAllowedForStatus(http.StatusProcessing)) - assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent)) - assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified)) - assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) + assert.False(t, bodyAllowedForStatus(http.StatusProcessing)) + assert.False(t, bodyAllowedForStatus(http.StatusNoContent)) + assert.False(t, bodyAllowedForStatus(http.StatusNotModified)) + assert.True(t, bodyAllowedForStatus(http.StatusInternalServerError)) } type TestRender struct{} From 674522db91d637d179c16c372d87756ea26fa089 Mon Sep 17 00:00:00 2001 From: Kashiwa <13825170+ksw2000@users.noreply.github.com> Date: Wed, 21 May 2025 19:21:46 +0800 Subject: [PATCH 221/332] fix(time): binding time with empty value (#4103) * fix: binding time with empty value (#4098) * refact: simplify null-to-zero filling logic * test: add test for zeroUnixNanoTime --- binding/form_mapping.go | 10 +++++----- binding/form_mapping_test.go | 26 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 235692d2..c9f3197f 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -397,6 +397,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) @@ -420,11 +425,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 diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 1277fd5f..45cd9297 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -183,11 +183,13 @@ func TestMapFormWithTag(t *testing.T) { func TestMappingTime(t *testing.T) { var s struct { - Time time.Time - LocalTime time.Time `time_format:"2006-01-02"` - ZeroValue time.Time - CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` - UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` + Time time.Time + LocalTime time.Time `time_format:"2006-01-02"` + ZeroValue time.Time + ZeroUnixTime time.Time `time_format:"unix"` + ZeroUnixNanoTime time.Time `time_format:"unixnano"` + CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` + UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` } var err error @@ -195,11 +197,13 @@ func TestMappingTime(t *testing.T) { require.NoError(t, err) err = mapForm(&s, map[string][]string{ - "Time": {"2019-01-20T16:02:58Z"}, - "LocalTime": {"2019-01-20"}, - "ZeroValue": {}, - "CSTTime": {"2019-01-20"}, - "UTCTime": {"2019-01-20"}, + "Time": {"2019-01-20T16:02:58Z"}, + "LocalTime": {"2019-01-20"}, + "ZeroValue": {}, + "ZeroUnixTime": {}, + "ZeroUnixNanoTime": {}, + "CSTTime": {"2019-01-20"}, + "UTCTime": {"2019-01-20"}, }) require.NoError(t, err) @@ -207,6 +211,8 @@ func TestMappingTime(t *testing.T) { assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String()) assert.Equal(t, "2019-01-19 23:00:00 +0000 UTC", s.LocalTime.UTC().String()) assert.Equal(t, "0001-01-01 00:00:00 +0000 UTC", s.ZeroValue.String()) + assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", s.ZeroUnixTime.UTC().String()) + assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", s.ZeroUnixNanoTime.UTC().String()) assert.Equal(t, "2019-01-20 00:00:00 +0800 CST", s.CSTTime.String()) assert.Equal(t, "2019-01-19 16:00:00 +0000 UTC", s.CSTTime.UTC().String()) assert.Equal(t, "2019-01-20 00:00:00 +0000 UTC", s.UTCTime.String()) From 8fb3136664254d7c592127f00d52849caba18a67 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 22 May 2025 19:20:04 +0800 Subject: [PATCH 222/332] Revert "fix(time): binding time with empty value (#4103)" (#4245) This reverts commit 674522db91d637d179c16c372d87756ea26fa089. --- binding/form_mapping.go | 10 +++++----- binding/form_mapping_test.go | 26 ++++++++++---------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index c9f3197f..235692d2 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -397,11 +397,6 @@ 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) @@ -425,6 +420,11 @@ 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 diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 45cd9297..1277fd5f 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -183,13 +183,11 @@ func TestMapFormWithTag(t *testing.T) { func TestMappingTime(t *testing.T) { var s struct { - Time time.Time - LocalTime time.Time `time_format:"2006-01-02"` - ZeroValue time.Time - ZeroUnixTime time.Time `time_format:"unix"` - ZeroUnixNanoTime time.Time `time_format:"unixnano"` - CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` - UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` + Time time.Time + LocalTime time.Time `time_format:"2006-01-02"` + ZeroValue time.Time + CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` + UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` } var err error @@ -197,13 +195,11 @@ func TestMappingTime(t *testing.T) { require.NoError(t, err) err = mapForm(&s, map[string][]string{ - "Time": {"2019-01-20T16:02:58Z"}, - "LocalTime": {"2019-01-20"}, - "ZeroValue": {}, - "ZeroUnixTime": {}, - "ZeroUnixNanoTime": {}, - "CSTTime": {"2019-01-20"}, - "UTCTime": {"2019-01-20"}, + "Time": {"2019-01-20T16:02:58Z"}, + "LocalTime": {"2019-01-20"}, + "ZeroValue": {}, + "CSTTime": {"2019-01-20"}, + "UTCTime": {"2019-01-20"}, }) require.NoError(t, err) @@ -211,8 +207,6 @@ func TestMappingTime(t *testing.T) { assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String()) assert.Equal(t, "2019-01-19 23:00:00 +0000 UTC", s.LocalTime.UTC().String()) assert.Equal(t, "0001-01-01 00:00:00 +0000 UTC", s.ZeroValue.String()) - assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", s.ZeroUnixTime.UTC().String()) - assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", s.ZeroUnixNanoTime.UTC().String()) assert.Equal(t, "2019-01-20 00:00:00 +0800 CST", s.CSTTime.String()) assert.Equal(t, "2019-01-19 16:00:00 +0000 UTC", s.CSTTime.UTC().String()) assert.Equal(t, "2019-01-20 00:00:00 +0000 UTC", s.UTCTime.String()) From c4287b1300363cb3dc2c8408299d3ba6deded485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Fri, 23 May 2025 14:46:48 +0800 Subject: [PATCH 223/332] ci(golangci-lint): update configuration and fix lint issues (#4247) * ci: update golangci-lint configuration and lint settings - Update golangci-lint to version 2 - Enable new linters and adjust existing ones - Update lint settings across multiple test files - Remove unused struct and variable checks - Add new lint exclusions for generated code and specific directories Signed-off-by: Flc * ci(github): update golangci-lint-action to v8 and lint version to v2.3.4 Signed-off-by: Flc * ci: downgrade golangci-lint to v2.1.6 Signed-off-by: Flc * ci(golangci): add gofumpt linter and fix related issues- Added gofumpt linter to .golangci.yml Signed-off-by: Flc * test: ignore testifylint and gofumpt lints in specific test cases Signed-off-by: Flc * build(deps): remove golang.org/x/lint - Remove golang.org/x/lint package from go.mod - Update related dependencies in go.sum Signed-off-by: flc1125 * build(deps): downgrade golang.org/x/mod and golang.org/x/tools - Downgrade golang.org/x/mod from v0.24.0 to v0.18.0 - Downgrade golang.org/x/tools from v0.33.0 to v.22.0 These changes are made to address compatibility issues with the current project setup. Signed-off-by: flc1125 --------- Signed-off-by: Flc Signed-off-by: flc1125 --- .github/workflows/gin.yml | 4 +- .golangci.yml | 120 +++++++++++++++------------ binding/binding_test.go | 6 +- binding/default_validator_test.go | 6 +- binding/form.go | 8 +- binding/form_mapping_test.go | 36 ++++---- binding/header.go | 1 - binding/validate_test.go | 14 ++-- binding/xml.go | 1 + context_test.go | 38 ++++----- errors_test.go | 6 +- fs.go | 1 - gin.go | 3 +- ginS/gins.go | 6 +- gin_integration_test.go | 7 +- gin_test.go | 4 +- githubapi_test.go | 8 +- internal/bytesconv/bytesconv_test.go | 6 +- internal/fs/fs.go | 1 - logger_test.go | 4 +- middleware_test.go | 2 +- mode.go | 6 +- render/html.go | 1 + render/render_test.go | 12 +-- routes_test.go | 4 +- tree_test.go | 6 +- utils_test.go | 2 +- 27 files changed, 164 insertions(+), 149 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 54185a0a..6f0f7c11 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,9 +24,9 @@ jobs: with: go-version: "^1" - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: - version: v1.61.0 + version: v2.1.6 args: --verbose test: needs: lint diff --git a/.golangci.yml b/.golangci.yml index 925e1306..d9a60ce2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,16 +1,11 @@ -run: - timeout: 5m +version: "2" linters: enable: - asciicheck + - copyloopvar - dogsled - durationcheck - - errcheck - errorlint - - copyloopvar - - gci - - gofmt - - goimports - gosec - misspell - nakedret @@ -21,51 +16,66 @@ linters: - testifylint - usestdlibvars - wastedassign - -linters-settings: - gosec: - # To select a subset of rules to run. - # Available rules: https://github.com/securego/gosec#available-rules - # Default: [] - means include all rules - includes: - - G102 - - G106 - - G108 - - G109 - - G111 - - G112 - - G201 - - G203 - perfsprint: - err-error: true - errorf: true - int-conversion: true - sprintf1: true - strconcat: true - testifylint: - enable-all: true - -issues: - exclude-rules: - - linters: - - structcheck - - unused - text: "`data` is unused" - - linters: - - staticcheck - text: "SA1019:" - - linters: - - revive - text: "var-naming:" - - linters: - - revive - text: "exported:" - - path: _test\.go - linters: - - gosec # security is not make sense in tests - - linters: - - revive - path: _test\.go - - path: gin.go - linters: - - gci + settings: + gosec: + includes: + - G102 + - G106 + - G108 + - G109 + - G111 + - G112 + - G201 + - G203 + perfsprint: + int-conversion: true + err-error: true + errorf: true + sprintf1: true + strconcat: true + testifylint: + enable-all: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - structcheck + - unused + text: '`data` is unused' + - linters: + - staticcheck + text: 'SA1019:' + - linters: + - revive + text: 'var-naming:' + - linters: + - revive + text: 'exported:' + - linters: + - gosec + path: _test\.go + - linters: + - revive + path: _test\.go + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ + - gin.go diff --git a/binding/binding_test.go b/binding/binding_test.go index bdab3694..07619ebf 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -51,8 +51,6 @@ type FooBarFileStruct struct { type FooBarFileFailStruct struct { FooBarStruct File *multipart.FileHeader `invalid_name:"file" binding:"required"` - // for unexport test - data *multipart.FileHeader `form:"data" binding:"required"` } type FooDefaultBarStruct struct { @@ -1063,7 +1061,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo } err := b.Bind(req, &obj) require.NoError(t, err) - assert.Equal(t, "", obj.TestName) + assert.Empty(t, obj.TestName) obj = InvalidNameType{} req = requestWithBody(method, badPath, badBody) @@ -1318,7 +1316,7 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad req := requestWithBody(http.MethodPost, path, body) err := b.Bind(req, &obj) require.Error(t, err) - assert.Equal(t, "", obj.Foo) + assert.Empty(t, obj.Foo) obj = FooStruct{} req = requestWithBody(http.MethodPost, badPath, badBody) diff --git a/binding/default_validator_test.go b/binding/default_validator_test.go index df7742b7..fc819eb4 100644 --- a/binding/default_validator_test.go +++ b/binding/default_validator_test.go @@ -18,14 +18,16 @@ func TestSliceValidationError(t *testing.T) { {"has nil elements", SliceValidationError{errors.New("test error"), nil}, "[0]: test error"}, {"has zero elements", SliceValidationError{}, ""}, {"has one element", SliceValidationError{errors.New("test one error")}, "[0]: test one error"}, - {"has two elements", + { + "has two elements", SliceValidationError{ errors.New("first error"), errors.New("second error"), }, "[0]: first error\n[1]: second error", }, - {"has many elements", + { + "has many elements", SliceValidationError{ errors.New("first error"), errors.New("second error"), diff --git a/binding/form.go b/binding/form.go index b17352ba..06732e97 100644 --- a/binding/form.go +++ b/binding/form.go @@ -11,9 +11,11 @@ import ( const defaultMemory = 32 << 20 -type formBinding struct{} -type formPostBinding struct{} -type formMultipartBinding struct{} +type ( + formBinding struct{} + formPostBinding struct{} + formMultipartBinding struct{} +) func (formBinding) Name() string { return "form" diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 1277fd5f..55b967a3 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -509,9 +509,9 @@ func TestMappingCustomStructTypeWithFormTag(t *testing.T) { err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") require.NoError(t, err) - assert.EqualValues(t, "file", s.FileData.Protocol) - assert.EqualValues(t, "/foo", s.FileData.Path) - assert.EqualValues(t, "happiness", s.FileData.Name) + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) } func TestMappingCustomStructTypeWithURITag(t *testing.T) { @@ -521,9 +521,9 @@ func TestMappingCustomStructTypeWithURITag(t *testing.T) { err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") require.NoError(t, err) - assert.EqualValues(t, "file", s.FileData.Protocol) - assert.EqualValues(t, "/foo", s.FileData.Path) - assert.EqualValues(t, "happiness", s.FileData.Name) + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) } func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) { @@ -533,9 +533,9 @@ func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) { err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") require.NoError(t, err) - assert.EqualValues(t, "file", s.FileData.Protocol) - assert.EqualValues(t, "/foo", s.FileData.Path) - assert.EqualValues(t, "happiness", s.FileData.Name) + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) } func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { @@ -545,9 +545,9 @@ func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") require.NoError(t, err) - assert.EqualValues(t, "file", s.FileData.Protocol) - assert.EqualValues(t, "/foo", s.FileData.Path) - assert.EqualValues(t, "happiness", s.FileData.Name) + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) } type customPath []string @@ -570,8 +570,8 @@ func TestMappingCustomSliceUri(t *testing.T) { err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri") require.NoError(t, err) - assert.EqualValues(t, "bar", s.FileData[0]) - assert.EqualValues(t, "foo", s.FileData[1]) + assert.Equal(t, "bar", s.FileData[0]) + assert.Equal(t, "foo", s.FileData[1]) } func TestMappingCustomSliceForm(t *testing.T) { @@ -581,8 +581,8 @@ func TestMappingCustomSliceForm(t *testing.T) { err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form") require.NoError(t, err) - assert.EqualValues(t, "bar", s.FileData[0]) - assert.EqualValues(t, "foo", s.FileData[1]) + assert.Equal(t, "bar", s.FileData[0]) + assert.Equal(t, "foo", s.FileData[1]) } type objectID [12]byte @@ -621,7 +621,7 @@ func TestMappingCustomArrayUri(t *testing.T) { require.NoError(t, err) expected, _ := convertTo(val) - assert.EqualValues(t, expected, s.FileData) + assert.Equal(t, expected, s.FileData) } func TestMappingCustomArrayForm(t *testing.T) { @@ -633,5 +633,5 @@ func TestMappingCustomArrayForm(t *testing.T) { require.NoError(t, err) expected, _ := convertTo(val) - assert.EqualValues(t, expected, s.FileData) + assert.Equal(t, expected, s.FileData) } diff --git a/binding/header.go b/binding/header.go index 03bc78da..6ed8c0c5 100644 --- a/binding/header.go +++ b/binding/header.go @@ -17,7 +17,6 @@ func (headerBinding) Name() string { } func (headerBinding) Bind(req *http.Request, obj any) error { - if err := mapHeader(obj, req.Header); err != nil { return err } diff --git a/binding/validate_test.go b/binding/validate_test.go index c9bbe601..792f64c4 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -158,16 +158,16 @@ type structNoValidationPointer struct { } func TestValidateNoValidationPointers(t *testing.T) { - //origin := createNoValidation_values() - //test := createNoValidation_values() + // origin := createNoValidation_values() + // test := createNoValidation_values() empty := structNoValidationPointer{} - //assert.Nil(t, validate(test)) - //assert.Nil(t, validate(&test)) + // assert.Nil(t, validate(test)) + // assert.Nil(t, validate(&test)) require.NoError(t, validate(empty)) require.NoError(t, validate(&empty)) - //assert.Equal(t, origin, test) + // assert.Equal(t, origin, test) } type Object map[string]any @@ -198,7 +198,7 @@ type structModifyValidation struct { } func toZero(sl validator.StructLevel) { - var s *structModifyValidation = sl.Top().Interface().(*structModifyValidation) + s := sl.Top().Interface().(*structModifyValidation) s.Integer = 0 } @@ -249,5 +249,5 @@ func TestValidatorEngine(t *testing.T) { // Check that we got back non-nil errs require.Error(t, errs) // Check that the error matches expectation - require.Error(t, errs, "", "", "notone") + require.Error(t, errs, "notone") } diff --git a/binding/xml.go b/binding/xml.go index a70f4ad3..acd6f942 100644 --- a/binding/xml.go +++ b/binding/xml.go @@ -24,6 +24,7 @@ func (xmlBinding) Bind(req *http.Request, obj any) error { func (xmlBinding) BindBody(body []byte, obj any) error { return decodeXML(bytes.NewReader(body), obj) } + func decodeXML(r io.Reader, obj any) error { decoder := xml.NewDecoder(r) if err := decoder.Decode(obj); err != nil { diff --git a/context_test.go b/context_test.go index ebcd08d4..084bae5d 100644 --- a/context_test.go +++ b/context_test.go @@ -918,7 +918,7 @@ func TestContextRenderJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -946,7 +946,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -972,7 +972,7 @@ func TestContextRenderAPIJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } @@ -998,7 +998,7 @@ func TestContextRenderIndentedJSON(t *testing.T) { c.IndentedJSON(http.StatusCreated, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) + assert.JSONEq(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1059,7 +1059,7 @@ func TestContextRenderPureJSON(t *testing.T) { c, _ := CreateTestContext(w) c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1233,7 +1233,7 @@ func TestContextRenderSSE(t *testing.T) { "bar": "foo", }) - assert.Equal(t, strings.Replace(w.Body.String(), " ", "", -1), strings.Replace("event:float\ndata:1.5\n\nid:123\ndata:text\n\nevent:chat\ndata:{\"bar\":\"foo\",\"foo\":\"bar\"}\n\n", " ", "", -1)) + assert.Equal(t, strings.ReplaceAll(w.Body.String(), " ", ""), strings.ReplaceAll("event:float\ndata:1.5\n\nid:123\ndata:text\n\nevent:chat\ndata:{\"bar\":\"foo\",\"foo\":\"bar\"}\n\n", " ", "")) } func TestContextRenderFile(t *testing.T) { @@ -1247,7 +1247,7 @@ func TestContextRenderFile(t *testing.T) { assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' - assert.NotEqual(t, "", w.Header().Get("Content-Type")) + assert.NotEmpty(t, w.Header().Get("Content-Type")) } func TestContextRenderFileFromFS(t *testing.T) { @@ -1261,7 +1261,7 @@ func TestContextRenderFileFromFS(t *testing.T) { assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' - assert.NotEqual(t, "", w.Header().Get("Content-Type")) + assert.NotEmpty(t, w.Header().Get("Content-Type")) assert.Equal(t, "/some/path", c.Request.URL.Path) } @@ -1432,7 +1432,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { }) assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1518,7 +1518,7 @@ func TestContextNegotiationFormat(t *testing.T) { c.Request, _ = http.NewRequest(http.MethodPost, "", nil) assert.Panics(t, func() { c.NegotiateFormat() }) - assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) //nolint:testifylint assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON)) } @@ -1540,7 +1540,7 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) assert.Equal(t, "text/*", c.NegotiateFormat("text/*")) assert.Equal(t, "application/*", c.NegotiateFormat("application/*")) - assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) //nolint:testifylint assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML)) assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML)) @@ -1550,9 +1550,9 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) assert.Equal(t, "text/*", c.NegotiateFormat("text/*")) - assert.Equal(t, "", c.NegotiateFormat("application/*")) - assert.Equal(t, "", c.NegotiateFormat(MIMEJSON)) - assert.Equal(t, "", c.NegotiateFormat(MIMEXML)) + assert.Empty(t, c.NegotiateFormat("application/*")) + assert.Empty(t, c.NegotiateFormat(MIMEJSON)) + assert.Empty(t, c.NegotiateFormat(MIMEXML)) assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML)) } @@ -1564,9 +1564,9 @@ func TestContextNegotiationFormatCustom(t *testing.T) { c.Accepted = nil c.SetAccepted(MIMEJSON, MIMEXML) - assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) //nolint:testifylint assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML)) - assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) //nolint:testifylint } func TestContextNegotiationFormat2(t *testing.T) { @@ -1574,7 +1574,7 @@ func TestContextNegotiationFormat2(t *testing.T) { c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "image/tiff-fx") - assert.Equal(t, "", c.NegotiateFormat("image/tiff")) + assert.Empty(t, c.NegotiateFormat("image/tiff")) } func TestContextIsAborted(t *testing.T) { @@ -1634,7 +1634,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) { _, err := buf.ReadFrom(w.Body) require.NoError(t, err) jsonStringBody := buf.String() - assert.Equal(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) + assert.JSONEq(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) } func TestContextError(t *testing.T) { @@ -3076,7 +3076,7 @@ func TestInterceptedHeader(t *testing.T) { // Compared to this time, this is when the response headers will be flushed // As response is flushed on c.String, the Header cannot be set by the first // middleware. Assert this - assert.Equal(t, "", w.Result().Header.Get("X-Test")) + assert.Empty(t, w.Result().Header.Get("X-Test")) assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) } diff --git a/errors_test.go b/errors_test.go index 91ae9c92..85ed3dd5 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,7 +34,7 @@ func TestError(t *testing.T) { }, err.JSON()) jsonBytes, _ := json.Marshal(err) - assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) + assert.JSONEq(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) err.SetMeta(H{ //nolint: errcheck "status": "200", @@ -93,13 +93,13 @@ Error #03: third H{"error": "third", "status": "400"}, }, errs.JSON()) jsonBytes, _ := json.Marshal(errs) - assert.Equal(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes)) + assert.JSONEq(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes)) errs = errorMsgs{ {Err: errors.New("first"), Type: ErrorTypePrivate}, } assert.Equal(t, H{"error": "first"}, errs.JSON()) jsonBytes, _ = json.Marshal(errs) - assert.Equal(t, "{\"error\":\"first\"}", string(jsonBytes)) + assert.JSONEq(t, "{\"error\":\"first\"}", string(jsonBytes)) errs = errorMsgs{} assert.Nil(t, errs.Last()) diff --git a/fs.go b/fs.go index 51c3db86..ab18adb3 100644 --- a/fs.go +++ b/fs.go @@ -17,7 +17,6 @@ type OnlyFilesFS struct { // Open passes `Open` to the upstream implementation without `Readdir` functionality. func (o OnlyFilesFS) Open(name string) (http.File, error) { f, err := o.FileSystem.Open(name) - if err != nil { return nil, err } diff --git a/gin.go b/gin.go index f9813e1d..27eea83e 100644 --- a/gin.go +++ b/gin.go @@ -18,7 +18,6 @@ import ( "github.com/gin-gonic/gin/internal/bytesconv" filesystem "github.com/gin-gonic/gin/internal/fs" "github.com/gin-gonic/gin/render" - "github.com/quic-go/quic-go/http3" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" @@ -216,7 +215,7 @@ func New(opts ...OptionFunc) *Engine { trustedProxies: []string{"0.0.0.0/0", "::/0"}, trustedCIDRs: defaultTrustedCIDRs, } - engine.RouterGroup.engine = engine + engine.engine = engine engine.pool.New = func() any { return engine.allocateContext(engine.maxParams) } diff --git a/ginS/gins.go b/ginS/gins.go index 3e6a92eb..40088172 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -12,8 +12,10 @@ import ( "github.com/gin-gonic/gin" ) -var once sync.Once -var internalEngine *gin.Engine +var ( + once sync.Once + internalEngine *gin.Engine +) func engine() *gin.Engine { once.Do(func() { diff --git a/gin_integration_test.go b/gin_integration_test.go index 3082bc2c..c032d837 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -28,7 +28,6 @@ import ( // params[1]=response status (custom compare status) default:"200 OK" // params[2]=response body (custom compare content) default:"it worked" func testRequest(t *testing.T, params ...string) { - if len(params) == 0 { t.Fatal("url cannot be empty") } @@ -47,12 +46,12 @@ func testRequest(t *testing.T, params ...string) { body, ioerr := io.ReadAll(resp.Body) require.NoError(t, ioerr) - var responseStatus = "200 OK" + responseStatus := "200 OK" if len(params) > 1 && params[1] != "" { responseStatus = params[1] } - var responseBody = "it worked" + responseBody := "it worked" if len(params) > 2 && params[2] != "" { responseBody = params[2] } @@ -170,7 +169,7 @@ func TestRunTLS(t *testing.T) { } func TestPusher(t *testing.T) { - var html = template.Must(template.New("https").Parse(` + html := template.Must(template.New("https").Parse(` Https Test diff --git a/gin_test.go b/gin_test.go index 250269e5..be076537 100644 --- a/gin_test.go +++ b/gin_test.go @@ -46,7 +46,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine }) router.GET("/raw", func(c *Context) { c.HTML(http.StatusOK, "raw.tmpl", map[string]any{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), //nolint:gofumpt }) }) }) @@ -883,7 +883,7 @@ func TestWithOptionFunc(t *testing.T) { type Birthday string func (b *Birthday) UnmarshalParam(param string) error { - *b = Birthday(strings.Replace(param, "-", "/", -1)) + *b = Birthday(strings.ReplaceAll(param, "-", "/")) return nil } diff --git a/githubapi_test.go b/githubapi_test.go index 0c86af2e..20d4aeaf 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -298,8 +298,8 @@ func TestShouldBindUri(t *testing.T) { router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person require.NoError(t, c.ShouldBindUri(&person)) - assert.NotEqual(t, "", person.Name) - assert.NotEqual(t, "", person.ID) + assert.NotEmpty(t, person.Name) + assert.NotEmpty(t, person.ID) c.String(http.StatusOK, "ShouldBindUri test OK") }) @@ -320,8 +320,8 @@ func TestBindUri(t *testing.T) { router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person require.NoError(t, c.BindUri(&person)) - assert.NotEqual(t, "", person.Name) - assert.NotEqual(t, "", person.ID) + assert.NotEmpty(t, person.Name) + assert.NotEmpty(t, person.ID) c.String(http.StatusOK, "BindUri test OK") }) diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go index eeaad5ee..497069ce 100644 --- a/internal/bytesconv/bytesconv_test.go +++ b/internal/bytesconv/bytesconv_test.go @@ -12,8 +12,10 @@ import ( "time" ) -var testString = "Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere." -var testBytes = []byte(testString) +var ( + testString = "Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere." + testBytes = []byte(testString) +) func rawBytesToStr(b []byte) string { return string(b) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 524ac08b..c2530383 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -13,7 +13,6 @@ type FileSystem struct { // Open passes `Open` to the upstream implementation and return an [fs.File]. func (o FileSystem) Open(name string) (fs.File, error) { f, err := o.FileSystem.Open(name) - if err != nil { return nil, err } diff --git a/logger_test.go b/logger_test.go index de00c499..30b25290 100644 --- a/logger_test.go +++ b/logger_test.go @@ -371,11 +371,11 @@ func TestErrorLogger(t *testing.T) { w := PerformRequest(router, http.MethodGet, "/error") assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) + assert.JSONEq(t, "{\"error\":\"this is an error\"}", w.Body.String()) w = PerformRequest(router, http.MethodGet, "/abort") assert.Equal(t, http.StatusUnauthorized, w.Code) - assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) + assert.JSONEq(t, "{\"error\":\"no authorized\"}", w.Body.String()) w = PerformRequest(router, http.MethodGet, "/print") assert.Equal(t, http.StatusInternalServerError, w.Code) diff --git a/middleware_test.go b/middleware_test.go index 4390def7..8dc7c3b3 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -249,5 +249,5 @@ func TestMiddlewareWrite(t *testing.T) { w := PerformRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusBadRequest, w.Code) - assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) + assert.Equal(t, strings.ReplaceAll("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", ""), strings.ReplaceAll(w.Body.String(), " ", "")) } diff --git a/mode.go b/mode.go index 13aa3be0..cc313437 100644 --- a/mode.go +++ b/mode.go @@ -44,8 +44,10 @@ var DefaultWriter io.Writer = os.Stdout // DefaultErrorWriter is the default io.Writer used by Gin to debug errors var DefaultErrorWriter io.Writer = os.Stderr -var ginMode int32 = debugCode -var modeName atomic.Value +var ( + ginMode int32 = debugCode + modeName atomic.Value +) func init() { mode := os.Getenv(EnvGinMode) diff --git a/render/html.go b/render/html.go index f5e7455a..965d84c6 100644 --- a/render/html.go +++ b/render/html.go @@ -67,6 +67,7 @@ func (r HTMLDebug) Instance(name string, data any) Render { Data: data, } } + func (r HTMLDebug) loadTemplate() *template.Template { if r.FuncMap == nil { r.FuncMap = template.FuncMap{} diff --git a/render/render_test.go b/render/render_test.go index 4dd2a3af..447ea6a8 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -38,7 +38,7 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) require.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -60,7 +60,7 @@ func TestRenderIndentedJSON(t *testing.T) { err := (IndentedJSON{data}).Render(w) require.NoError(t, err) - assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) + assert.JSONEq(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -85,7 +85,7 @@ func TestRenderSecureJSON(t *testing.T) { err1 := (SecureJSON{"while(1);", data}).Render(w1) require.NoError(t, err1) - assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\"}", w1.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() @@ -194,7 +194,7 @@ func TestRenderJsonpJSONError2(t *testing.T) { e := (JsonpJSON{"", data}).Render(w) require.NoError(t, e) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) } @@ -217,7 +217,7 @@ func TestRenderAsciiJSON(t *testing.T) { err := (AsciiJSON{data1}).Render(w1) require.NoError(t, err) - assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) + assert.JSONEq(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() @@ -244,7 +244,7 @@ func TestRenderPureJSON(t *testing.T) { } err := (PureJSON{data}).Render(w) require.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/routes_test.go b/routes_test.go index 995ff51c..1cae3fce 100644 --- a/routes_test.go +++ b/routes_test.go @@ -484,7 +484,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { assert.Contains(t, w.Body.String(), "package gin") // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' - assert.NotEqual(t, "", w.Header().Get("Content-Type")) + assert.NotEmpty(t, w.Header().Get("Content-Type")) assert.NotEqual(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Last-Modified")) assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires")) assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN")) @@ -764,7 +764,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) { // Test not found router.Use(func(c *Context) { // For not found routes full path is empty - assert.Equal(t, "", c.FullPath()) + assert.Empty(t, c.FullPath()) }) w := PerformRequest(router, http.MethodGet, "/not-found") diff --git a/tree_test.go b/tree_test.go index 74eb6104..b580007d 100644 --- a/tree_test.go +++ b/tree_test.go @@ -481,7 +481,7 @@ func TestTreeDuplicatePath(t *testing.T) { } } - //printChildren(tree, "") + // printChildren(tree, "") checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, @@ -532,7 +532,7 @@ func TestTreeCatchAllConflictRoot(t *testing.T) { func TestTreeCatchMaxParams(t *testing.T) { tree := &node{} - var route = "/cmd/*filepath" + route := "/cmd/*filepath" tree.addRoute(route, fakeHandler(route)) } @@ -692,7 +692,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { } func TestRedirectTrailingSlash(t *testing.T) { - var data = []struct { + data := []struct { path string }{ {"/hello/:name"}, diff --git a/utils_test.go b/utils_test.go index 8098c681..dc9886d7 100644 --- a/utils_test.go +++ b/utils_test.go @@ -94,7 +94,7 @@ func somefunction() { } func TestJoinPaths(t *testing.T) { - assert.Equal(t, "", joinPaths("", "")) + assert.Empty(t, joinPaths("", "")) assert.Equal(t, "/", joinPaths("", "/")) assert.Equal(t, "/a", joinPaths("/a", "")) assert.Equal(t, "/a/", joinPaths("/a/", "")) From 40725d85badd647870df6f9fa7a75ac76341f804 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Sun, 25 May 2025 05:36:33 -0700 Subject: [PATCH 224/332] chore(bind): return 413 status code when error is `http.MaxBytesError` (#4227) * Bind: return 413 status code when error is `http.MaxBytesError` The Go standard library includes a method `http.MaxBytesReader` that allows limiting the request body. For example, users can create a middleware like: ```go func MiddlewareMaxBodySize(c *gin.Context) { // Limit request body to 100 bytes c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 100) c.Next() } ``` When the body exceeds the limit, reading from the request body returns an error of type `http.MaxBytesError`. This PR makes sure that when the error is of kind `http.MaxBytesError`, Gin returns the correct status code 413 (Request Entity Too Large) instead of a generic 400 (Bad Request). * Disable test when using sonic * Fix * Disable for go-json too * Add references to GitHub issues * Test that the response is 400 for sonic and go-json --- context.go | 15 +++++++- context_test.go | 77 ++++++++++++++++++++++++++++----------- internal/json/go_json.go | 3 ++ internal/json/json.go | 3 ++ internal/json/jsoniter.go | 3 ++ internal/json/sonic.go | 3 ++ 6 files changed, 80 insertions(+), 24 deletions(-) diff --git a/context.go b/context.go index d3e4c6d7..3ebb3ee4 100644 --- a/context.go +++ b/context.go @@ -769,8 +769,19 @@ func (c *Context) BindUri(obj any) error { // It will abort the request with HTTP 400 if any error occurs. // See the binding package. func (c *Context) MustBindWith(obj any, b binding.Binding) error { - if err := c.ShouldBindWith(obj, b); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck + err := c.ShouldBindWith(obj, b) + if err != nil { + var maxBytesErr *http.MaxBytesError + + // Note: When using sonic or go-json as JSON encoder, they do not propagate the http.MaxBytesError error + // https://github.com/goccy/go-json/issues/485 + // https://github.com/bytedance/sonic/issues/800 + switch { + case errors.As(err, &maxBytesErr): + c.AbortWithError(http.StatusRequestEntityTooLarge, err).SetType(ErrorTypeBind) //nolint: errcheck + default: + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck + } return err } return nil diff --git a/context_test.go b/context_test.go index 084bae5d..b64afba3 100644 --- a/context_test.go +++ b/context_test.go @@ -28,6 +28,7 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" + "github.com/gin-gonic/gin/internal/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -671,7 +672,7 @@ func TestContextDefaultQueryOnEmptyRequest(t *testing.T) { func TestContextQueryAndPostForm(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") + body := strings.NewReader("foo=bar&page=11&both=&foo=second") c.Request, _ = http.NewRequest(http.MethodPost, "/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) @@ -946,7 +947,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, `{"foo":"bar"}`, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -972,7 +973,7 @@ func TestContextRenderAPIJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, `{"foo":"bar"}`, w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } @@ -1432,7 +1433,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { }) assert.Equal(t, http.StatusOK, w.Code) - assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, `{"foo":"bar"}`, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1848,9 +1849,41 @@ func TestContextContentType(t *testing.T) { assert.Equal(t, "application/json", c.ContentType()) } +func TestContextBindRequestTooLarge(t *testing.T) { + // When using sonic or 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": + expectedCode = http.StatusBadRequest + default: + expectedCode = http.StatusRequestEntityTooLarge + } + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 10) + + var obj struct { + Foo string `json:"foo"` + Bar string `json:"bar"` + } + require.Error(t, c.BindJSON(&obj)) + c.Writer.WriteHeaderNow() + + assert.Empty(t, obj.Bar) + assert.Empty(t, obj.Foo) + assert.Equal(t, expectedCode, w.Code) + assert.True(t, c.IsAborted()) +} + func TestContextAutoBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { @@ -1867,7 +1900,7 @@ func TestContextBindWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -1884,7 +1917,7 @@ func TestContextBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(` + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(` FOO BAR @@ -1951,7 +1984,7 @@ func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo", strings.NewReader("foo=unused")) var obj struct { Foo string `form:"foo"` @@ -1967,7 +2000,7 @@ func TestContextBindWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo: bar\nbar: foo")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader("foo: bar\nbar: foo")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -1984,7 +2017,7 @@ func TestContextBindWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo = 'bar'\nbar = 'foo'")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader("foo = 'bar'\nbar = 'foo'")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -2001,7 +2034,7 @@ func TestContextBadAutoBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", strings.NewReader("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` @@ -2020,7 +2053,7 @@ func TestContextBadAutoBind(t *testing.T) { func TestContextAutoShouldBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { @@ -2037,7 +2070,7 @@ func TestContextShouldBindWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -2054,7 +2087,7 @@ func TestContextShouldBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(` + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(` FOO BAR @@ -2121,7 +2154,7 @@ func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", strings.NewReader("foo=unused")) var obj struct { Foo string `form:"foo"` @@ -2141,7 +2174,7 @@ func TestContextShouldBindWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo: bar\nbar: foo")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader("foo: bar\nbar: foo")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -2158,7 +2191,7 @@ func TestContextShouldBindWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader("foo='bar'\nbar= 'foo'")) c.Request.Header.Add("Content-Type", MIMETOML) // set fake content-type var obj struct { @@ -2175,7 +2208,7 @@ func TestContextBadAutoShouldBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", strings.NewReader(`"foo":"bar", "bar":"foo"}`)) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` @@ -2239,7 +2272,7 @@ func TestContextShouldBindBodyWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest( - http.MethodPost, "http://example.com", bytes.NewBufferString(tt.bodyA), + http.MethodPost, "http://example.com", strings.NewReader(tt.bodyA), ) // When it binds to typeA and typeB, it finds the body is // not typeB but typeA. @@ -2257,7 +2290,7 @@ func TestContextShouldBindBodyWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest( - http.MethodPost, "http://example.com", bytes.NewBufferString(tt.bodyB), + http.MethodPost, "http://example.com", strings.NewReader(tt.bodyB), ) objA := typeA{} require.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) @@ -2603,7 +2636,7 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) require.NoError(t, c.Err()) assert.Nil(t, c.Done()) ti, ok := c.Deadline() @@ -2651,7 +2684,7 @@ func TestGetRequestHeaderValue(t *testing.T) { func TestContextGetRawData(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - body := bytes.NewBufferString("Fetch binary post data") + body := strings.NewReader("Fetch binary post data") c.Request, _ = http.NewRequest(http.MethodPost, "/", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) diff --git a/internal/json/go_json.go b/internal/json/go_json.go index 47c35598..dee09dec 100644 --- a/internal/json/go_json.go +++ b/internal/json/go_json.go @@ -20,3 +20,6 @@ var ( // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder ) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/goccy/go-json" diff --git a/internal/json/json.go b/internal/json/json.go index 26817786..539daa78 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -20,3 +20,6 @@ var ( // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder ) + +// Package indicates what library is being used for JSON encoding. +const Package = "encoding/json" diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index 45ed16ba..287ebf70 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -21,3 +21,6 @@ var ( // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder ) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/json-iterator/go" diff --git a/internal/json/sonic.go b/internal/json/sonic.go index 8cd88049..b3f72424 100644 --- a/internal/json/sonic.go +++ b/internal/json/sonic.go @@ -21,3 +21,6 @@ var ( // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder ) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/bytedance/sonic" From c8af82af15dd00d684113334d12e05f589f33ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Sun, 25 May 2025 20:38:39 +0800 Subject: [PATCH 225/332] test(context): add cleanup for uploaded file in SaveUploadedFile test (#4248) Signed-off-by: Flc --- context_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/context_test.go b/context_test.go index b64afba3..c8559505 100644 --- a/context_test.go +++ b/context_test.go @@ -174,6 +174,9 @@ func TestSaveUploadedFileWithPermission(t *testing.T) { assert.Equal(t, "permission_test", f.Filename) var mode fs.FileMode = 0o755 require.NoError(t, c.SaveUploadedFile(f, "permission_test", mode)) + t.Cleanup(func() { + assert.NoError(t, os.Remove("permission_test")) + }) info, err := os.Stat(filepath.Dir("permission_test")) require.NoError(t, err) assert.Equal(t, info.Mode().Perm(), mode) From 848e1cdd0d1525ce0a99b7e6a2a1cf0d84d76156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Mon, 26 May 2025 23:11:05 +0800 Subject: [PATCH 226/332] refactor: replace interface{} with any in type declarations (#4249) - Update golangci.yml to use 'any' instead of 'interface{}' in gofmt - Modify debug.go, plain.go, and render_test.go to use 'any' type - Improve code readability and follow modern Go conventions Signed-off-by: Flc --- .golangci.yml | 5 +++++ binding/plain.go | 2 +- debug.go | 2 +- render/render_test.go | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d9a60ce2..d8887062 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -72,6 +72,11 @@ formatters: - gofmt - gofumpt - goimports + settings: + gofmt: + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' exclusions: generated: lax paths: diff --git a/binding/plain.go b/binding/plain.go index 3b250bb0..5d466bdd 100644 --- a/binding/plain.go +++ b/binding/plain.go @@ -15,7 +15,7 @@ func (plainBinding) Name() string { return "plain" } -func (plainBinding) Bind(req *http.Request, obj interface{}) error { +func (plainBinding) Bind(req *http.Request, obj any) error { all, err := io.ReadAll(req.Body) if err != nil { return err diff --git a/debug.go b/debug.go index f2016168..0ab14e4e 100644 --- a/debug.go +++ b/debug.go @@ -25,7 +25,7 @@ func IsDebugging() bool { var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int) // DebugPrintFunc indicates debug log output format. -var DebugPrintFunc func(format string, values ...interface{}) +var DebugPrintFunc func(format string, values ...any) func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { if IsDebugging() { diff --git a/render/render_test.go b/render/render_test.go index 447ea6a8..4d8ebb19 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -608,7 +608,7 @@ func TestRenderReaderNoContentLength(t *testing.T) { } func TestRenderWriteError(t *testing.T) { - data := []interface{}{"value1", "value2"} + data := []any{"value1", "value2"} prefix := "my-prefix:" r := SecureJSON{Data: data, Prefix: prefix} ew := &errorWriter{ From 41d8591eb16bf23732de9ae2b699d6cae54c2ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Mon, 26 May 2025 23:15:14 +0800 Subject: [PATCH 227/332] refactor(context): refactor `Keys` type to `map[any]any` (#3963) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(context): refactor keys to `map[any]any` Signed-off-by: Flcใ‚› * refactor(context): refactor keys to `map[any]any` Signed-off-by: Flcใ‚› * style(context): remove empty lines before GetInt16, GetIntSlice, and GetStringMapString methods - Remove unnecessary empty lines in the context.go file - Improve code readability and consistency Signed-off-by: flc1125 * refactor(context): simplify GetStringSlice function - Replace manual type assertion with generic getTyped function - Reduce code duplication and improve type safety Signed-off-by: flc1125 * test(context): improve context.Set and context.Get tests - Split existing test into separate functions for different scenarios - Add test for setting and getting values with any key type - Add test for handling non-comparable keys - Improve assertions to check for key existence and value correctness Signed-off-by: flc1125 * refactor(context): replace fmt.Errorf with fmt.Sprintf in panic message * test(context): remove trailing hyphen from context_test.go * refactor(context): improve error message for missing key in context - Remove unnecessary quotes around the key in the error message - Simplify the error message format for better readability * test(context): improve panic test message for non-existent key --------- Signed-off-by: Flcใ‚› Signed-off-by: flc1125 --- context.go | 81 +++++++++++++++++++++++++------------------------ context_test.go | 41 ++++++++++++++++++++++++- logger.go | 2 +- logger_test.go | 2 +- 4 files changed, 83 insertions(+), 43 deletions(-) diff --git a/context.go b/context.go index 3ebb3ee4..bf12830c 100644 --- a/context.go +++ b/context.go @@ -6,6 +6,7 @@ package gin import ( "errors" + "fmt" "io" "io/fs" "log" @@ -72,7 +73,7 @@ type Context struct { mu sync.RWMutex // Keys is a key/value pair exclusively for the context of each request. - Keys map[string]any + Keys map[any]any // Errors is a list of errors attached to all the handlers/middlewares who used this context. Errors errorMsgs @@ -129,7 +130,7 @@ func (c *Context) Copy() *Context { cp.fullPath = c.fullPath cKeys := c.Keys - cp.Keys = make(map[string]any, len(cKeys)) + cp.Keys = make(map[any]any, len(cKeys)) c.mu.RLock() for k, v := range cKeys { cp.Keys[k] = v @@ -264,11 +265,11 @@ func (c *Context) Error(err error) *Error { // Set is used to store a new key/value pair exclusively for this context. // It also lazy initializes c.Keys if it was not used previously. -func (c *Context) Set(key string, value any) { +func (c *Context) Set(key any, value any) { c.mu.Lock() defer c.mu.Unlock() if c.Keys == nil { - c.Keys = make(map[string]any) + c.Keys = make(map[any]any) } c.Keys[key] = value @@ -276,7 +277,7 @@ func (c *Context) Set(key string, value any) { // Get returns the value for the given key, ie: (value, true). // If the value does not exist it returns (nil, false) -func (c *Context) Get(key string) (value any, exists bool) { +func (c *Context) Get(key any) (value any, exists bool) { c.mu.RLock() defer c.mu.RUnlock() value, exists = c.Keys[key] @@ -284,14 +285,14 @@ func (c *Context) Get(key string) (value any, exists bool) { } // MustGet returns the value for the given key if it exists, otherwise it panics. -func (c *Context) MustGet(key string) any { +func (c *Context) MustGet(key any) any { if value, exists := c.Get(key); exists { return value } - panic("Key \"" + key + "\" does not exist") + panic(fmt.Sprintf("key %v does not exist", key)) } -func getTyped[T any](c *Context, key string) (res T) { +func getTyped[T any](c *Context, key any) (res T) { if val, ok := c.Get(key); ok && val != nil { res, _ = val.(T) } @@ -299,162 +300,162 @@ func getTyped[T any](c *Context, key string) (res T) { } // GetString returns the value associated with the key as a string. -func (c *Context) GetString(key string) (s string) { +func (c *Context) GetString(key any) (s string) { return getTyped[string](c, key) } // GetBool returns the value associated with the key as a boolean. -func (c *Context) GetBool(key string) (b bool) { +func (c *Context) GetBool(key any) (b bool) { return getTyped[bool](c, key) } // GetInt returns the value associated with the key as an integer. -func (c *Context) GetInt(key string) (i int) { +func (c *Context) GetInt(key any) (i int) { return getTyped[int](c, key) } // GetInt8 returns the value associated with the key as an integer 8. -func (c *Context) GetInt8(key string) (i8 int8) { +func (c *Context) GetInt8(key any) (i8 int8) { return getTyped[int8](c, key) } // GetInt16 returns the value associated with the key as an integer 16. -func (c *Context) GetInt16(key string) (i16 int16) { +func (c *Context) GetInt16(key any) (i16 int16) { return getTyped[int16](c, key) } // GetInt32 returns the value associated with the key as an integer 32. -func (c *Context) GetInt32(key string) (i32 int32) { +func (c *Context) GetInt32(key any) (i32 int32) { return getTyped[int32](c, key) } // GetInt64 returns the value associated with the key as an integer 64. -func (c *Context) GetInt64(key string) (i64 int64) { +func (c *Context) GetInt64(key any) (i64 int64) { return getTyped[int64](c, key) } // GetUint returns the value associated with the key as an unsigned integer. -func (c *Context) GetUint(key string) (ui uint) { +func (c *Context) GetUint(key any) (ui uint) { return getTyped[uint](c, key) } // GetUint8 returns the value associated with the key as an unsigned integer 8. -func (c *Context) GetUint8(key string) (ui8 uint8) { +func (c *Context) GetUint8(key any) (ui8 uint8) { return getTyped[uint8](c, key) } // GetUint16 returns the value associated with the key as an unsigned integer 16. -func (c *Context) GetUint16(key string) (ui16 uint16) { +func (c *Context) GetUint16(key any) (ui16 uint16) { return getTyped[uint16](c, key) } // GetUint32 returns the value associated with the key as an unsigned integer 32. -func (c *Context) GetUint32(key string) (ui32 uint32) { +func (c *Context) GetUint32(key any) (ui32 uint32) { return getTyped[uint32](c, key) } // GetUint64 returns the value associated with the key as an unsigned integer 64. -func (c *Context) GetUint64(key string) (ui64 uint64) { +func (c *Context) GetUint64(key any) (ui64 uint64) { return getTyped[uint64](c, key) } // GetFloat32 returns the value associated with the key as a float32. -func (c *Context) GetFloat32(key string) (f32 float32) { +func (c *Context) GetFloat32(key any) (f32 float32) { return getTyped[float32](c, key) } // GetFloat64 returns the value associated with the key as a float64. -func (c *Context) GetFloat64(key string) (f64 float64) { +func (c *Context) GetFloat64(key any) (f64 float64) { return getTyped[float64](c, key) } // GetTime returns the value associated with the key as time. -func (c *Context) GetTime(key string) (t time.Time) { +func (c *Context) GetTime(key any) (t time.Time) { return getTyped[time.Time](c, key) } // GetDuration returns the value associated with the key as a duration. -func (c *Context) GetDuration(key string) (d time.Duration) { +func (c *Context) GetDuration(key any) (d 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 string) (is []int) { +func (c *Context) GetIntSlice(key any) (is []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 string) (i8s []int8) { +func (c *Context) GetInt8Slice(key any) (i8s []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 string) (i16s []int16) { +func (c *Context) GetInt16Slice(key any) (i16s []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 string) (i32s []int32) { +func (c *Context) GetInt32Slice(key any) (i32s []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 string) (i64s []int64) { +func (c *Context) GetInt64Slice(key any) (i64s []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 string) (uis []uint) { +func (c *Context) GetUintSlice(key any) (uis []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 string) (ui8s []uint8) { +func (c *Context) GetUint8Slice(key any) (ui8s []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 string) (ui16s []uint16) { +func (c *Context) GetUint16Slice(key any) (ui16s []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 string) (ui32s []uint32) { +func (c *Context) GetUint32Slice(key any) (ui32s []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 string) (ui64s []uint64) { +func (c *Context) GetUint64Slice(key any) (ui64s []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 string) (f32s []float32) { +func (c *Context) GetFloat32Slice(key any) (f32s []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 string) (f64s []float64) { +func (c *Context) GetFloat64Slice(key any) (f64s []float64) { return getTyped[[]float64](c, key) } // GetStringSlice returns the value associated with the key as a slice of strings. -func (c *Context) GetStringSlice(key string) (ss []string) { +func (c *Context) GetStringSlice(key any) (ss []string) { return getTyped[[]string](c, key) } // GetStringMap returns the value associated with the key as a map of interfaces. -func (c *Context) GetStringMap(key string) (sm map[string]any) { +func (c *Context) GetStringMap(key any) (sm 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 string) (sms map[string]string) { +func (c *Context) GetStringMapString(key any) (sms 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 string) (smss map[string][]string) { +func (c *Context) GetStringMapStringSlice(key any) (smss map[string][]string) { return getTyped[map[string][]string](c, key) } diff --git a/context_test.go b/context_test.go index c8559505..ff43cd0a 100644 --- a/context_test.go +++ b/context_test.go @@ -257,7 +257,46 @@ func TestContextSetGet(t *testing.T) { assert.False(t, err) assert.Equal(t, "bar", c.MustGet("foo")) - assert.Panics(t, func() { c.MustGet("no_exist") }) + assert.Panicsf(t, func() { + c.MustGet("no_exist") + }, "key no_exist does not exist") +} + +func TestContextSetGetAnyKey(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + + type key struct{} + + tests := []struct { + key any + }{ + {1}, + {int32(1)}, + {int64(1)}, + {uint(1)}, + {float32(1)}, + {key{}}, + {&key{}}, + } + + for _, tt := range tests { + t.Run(reflect.TypeOf(tt.key).String(), func(t *testing.T) { + c.Set(tt.key, 1) + value, ok := c.Get(tt.key) + assert.True(t, ok) + assert.Equal(t, 1, value) + }) + } +} + +func TestContextSetGetPanicsWhenKeyNotComparable(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + + assert.Panics(t, func() { + c.Set([]int{1}, 1) + c.Set(func() {}, 1) + c.Set(make(chan int), 1) + }) } func TestContextSetGetValues(t *testing.T) { diff --git a/logger.go b/logger.go index db2c6832..f4a250ac 100644 --- a/logger.go +++ b/logger.go @@ -82,7 +82,7 @@ type LogFormatterParams struct { // BodySize is the size of the Response Body BodySize int // Keys are the keys set on the request's context. - Keys map[string]any + Keys map[any]any } // StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal. diff --git a/logger_test.go b/logger_test.go index 30b25290..8a542e97 100644 --- a/logger_test.go +++ b/logger_test.go @@ -181,7 +181,7 @@ func TestLoggerWithFormatter(t *testing.T) { func TestLoggerWithConfigFormatting(t *testing.T) { var gotParam LogFormatterParams - var gotKeys map[string]any + var gotKeys map[any]any buffer := new(strings.Builder) router := New() From 61c2b1c28f0c5a754330545e31f02cd6d6f7944e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 20:27:26 +0800 Subject: [PATCH 228/332] chore(deps): bump github.com/quic-go/quic-go from 0.51.0 to 0.52.0 (#4250) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.51.0 to 0.52.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.51.0...v0.52.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.52.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 0a5c626d..83ec4978 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.51.0 + github.com/quic-go/quic-go v0.52.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.40.0 diff --git a/go.sum b/go.sum index b047363b..f3b9bbad 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,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.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc= -github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= +github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA= +github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= 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 3c12d2a80e40930632fc4a4a4e1a45140f33fb12 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sat, 31 May 2025 08:41:13 +0800 Subject: [PATCH 229/332] perf(recover): replace bytes with strings in function for better performance (#4252) Co-authored-by: huangzw --- recovery.go | 21 +++++++++------------ recovery_test.go | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/recovery.go b/recovery.go index 8e4633b4..b3543e42 100644 --- a/recovery.go +++ b/recovery.go @@ -19,12 +19,9 @@ import ( "time" ) -var ( - dunno = []byte("???") - centerDot = []byte("ยท") - dot = []byte(".") - slash = []byte("/") -) +const dunno = "???" + +var dunnoBytes = []byte(dunno) // RecoveryFunc defines the function passable to CustomRecovery. type RecoveryFunc func(c *Context, err any) @@ -138,18 +135,18 @@ func stack(skip int) []byte { func source(lines [][]byte, n int) []byte { n-- // in stack trace, lines are 1-indexed but our array is 0-indexed if n < 0 || n >= len(lines) { - return dunno + return dunnoBytes } return bytes.TrimSpace(lines[n]) } // function returns, if possible, the name of the function containing the PC. -func function(pc uintptr) []byte { +func function(pc uintptr) string { fn := runtime.FuncForPC(pc) if fn == nil { return dunno } - name := []byte(fn.Name()) + name := fn.Name() // The name includes the path name to the package, which is unnecessary // since the file name is already included. Plus, it has center dots. // That is, we see @@ -158,13 +155,13 @@ func function(pc uintptr) []byte { // *T.ptrmethod // Also the package path might contain dot (e.g. code.google.com/...), // so first eliminate the path prefix - if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { + if lastSlash := strings.LastIndexByte(name, '/'); lastSlash >= 0 { name = name[lastSlash+1:] } - if period := bytes.Index(name, dot); period >= 0 { + if period := strings.IndexByte(name, '.'); period >= 0 { name = name[period+1:] } - name = bytes.ReplaceAll(name, centerDot, dot) + name = strings.ReplaceAll(name, "ยท", ".") return name } diff --git a/recovery_test.go b/recovery_test.go index 08eec1e4..a6e61a97 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -90,14 +90,14 @@ func TestPanicWithAbort(t *testing.T) { func TestSource(t *testing.T) { bs := source(nil, 0) - assert.Equal(t, dunno, bs) + assert.Equal(t, dunnoBytes, bs) in := [][]byte{ []byte("Hello world."), []byte("Hi, gin.."), } bs = source(in, 10) - assert.Equal(t, dunno, bs) + assert.Equal(t, dunnoBytes, bs) bs = source(in, 1) assert.Equal(t, []byte("Hello world."), bs) From e30123ad7314411664a693f23ed0ade498ccdaf0 Mon Sep 17 00:00:00 2001 From: OHZEKI Naoki <0h23k1.n40k1@gmail.com> Date: Mon, 2 Jun 2025 13:38:19 +0900 Subject: [PATCH 230/332] refactor(recovery): extract Authorization header masking into maskAuthorization func (#4143) * refactor(recovery): extract Authorization header masking into maskAuthorization func * test(recovery): Add a test for maskAuthorization --- recovery.go | 17 +++++++++++------ recovery_test.go | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/recovery.go b/recovery.go index b3543e42..8a077dbb 100644 --- a/recovery.go +++ b/recovery.go @@ -70,12 +70,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { stack := stack(3) httpRequest, _ := httputil.DumpRequest(c.Request, false) headers := strings.Split(string(httpRequest), "\r\n") - for idx, header := range headers { - key, _, _ := strings.Cut(header, ":") - if key == "Authorization" { - headers[idx] = key + ": *" - } - } + maskAuthorization(headers) headersToStr := strings.Join(headers, "\r\n") if brokenPipe { logger.Printf("%s\n%s%s", err, headersToStr, reset) @@ -131,6 +126,16 @@ func stack(skip int) []byte { return buf.Bytes() } +// maskAuthorization replaces any "Authorization: " header with "Authorization: *", hiding sensitive credentials. +func maskAuthorization(headers []string) { + for idx, header := range headers { + key, _, _ := strings.Cut(header, ":") + if strings.EqualFold(key, "Authorization") { + headers[idx] = key + ": *" + } + } +} + // source returns a space-trimmed slice of the n'th line. func source(lines [][]byte, n int) []byte { n-- // in stack trace, lines are 1-indexed but our array is 0-indexed diff --git a/recovery_test.go b/recovery_test.go index a6e61a97..3a36fad9 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -88,6 +88,24 @@ func TestPanicWithAbort(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) } +func TestMaskAuthorization(t *testing.T) { + secret := "Bearer aaaabbbbccccddddeeeeffff" + headers := []string{ + "Host: www.example.com", + "Authorization: " + secret, + "User-Agent: curl/7.51.0", + "Accept: */*", + "Content-Type: application/json", + "Content-Length: 1", + } + maskAuthorization(headers) + + for _, h := range headers { + assert.NotContains(t, h, secret) + } + assert.Contains(t, headers, "Authorization: *") +} + func TestSource(t *testing.T) { bs := source(nil, 0) assert.Equal(t, dunnoBytes, bs) From a9c5b36578be3bb817c3578d13de666b0d4c8549 Mon Sep 17 00:00:00 2001 From: eqsdxr Date: Mon, 9 Jun 2025 18:04:23 +0500 Subject: [PATCH 231/332] docs: small changes (#4261) --- gin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index 27eea83e..1965a429 100644 --- a/gin.go +++ b/gin.go @@ -334,7 +334,7 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { return engine } -// With returns a Engine with the configuration set in the OptionFunc. +// With returns an Engine with the configuration set in the OptionFunc. func (engine *Engine) With(opts ...OptionFunc) *Engine { for _, opt := range opts { opt(engine) @@ -376,7 +376,7 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { } // Routes returns a slice of registered routes, including some useful information, such as: -// the http method, path and the handler name. +// the http method, path, and the handler name. func (engine *Engine) Routes() (routes RoutesInfo) { for _, tree := range engine.trees { routes = iterate("", tree.method, routes, tree.root) From 77d70e5858278193abfab732164b0c1415d8d4ba Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Mon, 9 Jun 2025 21:05:34 +0800 Subject: [PATCH 232/332] refactor(internal/bytesconv): replace rand usage with crypto/rand and rand.Int63 (#4259) Co-authored-by: huangzw --- internal/bytesconv/bytesconv_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go index 497069ce..ff26e35e 100644 --- a/internal/bytesconv/bytesconv_test.go +++ b/internal/bytesconv/bytesconv_test.go @@ -6,6 +6,7 @@ package bytesconv import ( "bytes" + cRand "crypto/rand" "math/rand" "strings" "testing" @@ -30,7 +31,10 @@ func rawStrToBytes(s string) []byte { func TestBytesToString(t *testing.T) { data := make([]byte, 1024) for i := 0; i < 100; i++ { - rand.Read(data) + _, err := cRand.Read(data) + if err != nil { + t.Fatal(err) + } if rawBytesToStr(data) != BytesToString(data) { t.Fatal("don't match") } @@ -44,7 +48,7 @@ const ( letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits ) -var src = rand.NewSource(time.Now().UnixNano()) +var src = rand.New(rand.NewSource(time.Now().UnixNano())) func RandStringBytesMaskImprSrcSB(n int) string { sb := strings.Builder{} From dd33ff793861cee3baa77d575ff319119c366f3a Mon Sep 17 00:00:00 2001 From: Victor Dusart <43795504+vdusart@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:59:53 +0200 Subject: [PATCH 233/332] fix(docs): missing go markdown codeblock (#4266) --- docs/doc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doc.md b/docs/doc.md index 0e882a1b..249f93a0 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -872,7 +872,7 @@ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-1 If the server should bind a default value to a field when the client does not provide one, specify the default value using the `default` key within the `form` tag: -``` +```go package main import ( From 0a864884de806386e275ee096f681520799911fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 23:01:46 +0800 Subject: [PATCH 234/332] chore(deps): bump golang.org/x/net from 0.40.0 to 0.41.0 (#4262) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.40.0 to 0.41.0. - [Commits](https://github.com/golang/net/compare/v0.40.0...v0.41.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.41.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 83ec4978..24d3a895 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/quic-go/quic-go v0.52.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.40.0 + golang.org/x/net v0.41.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 ) @@ -37,10 +37,10 @@ require ( 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/crypto v0.38.0 // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.14.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/tools v0.33.0 // indirect ) diff --git a/go.sum b/go.sum index f3b9bbad..3971e4b2 100644 --- a/go.sum +++ b/go.sum @@ -82,22 +82,22 @@ 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/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 688a429d19d8c804447bb889d3635e2c31a5564d Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 16 Jun 2025 23:16:36 +0800 Subject: [PATCH 235/332] feat: support custom json codec at runtime (#3391) * refactor(json): export json codec * feat(json): support custom json codec at runtime * chore(copyright): update copyright to 2025 gin core team * docs(gin): add custom json codec examples in doc file --- binding/form_mapping.go | 6 +- binding/json.go | 4 +- binding/json_test.go | 186 ++++++++++++++++++++++++++++++++++++++ codec/json/api.go | 57 ++++++++++++ codec/json/go_json.go | 42 +++++++++ codec/json/json.go | 41 +++++++++ codec/json/jsoniter.go | 44 +++++++++ codec/json/sonic.go | 44 +++++++++ context_test.go | 2 +- docs/doc.md | 60 ++++++++++++ errors.go | 6 +- errors_test.go | 8 +- go.mod | 2 +- internal/json/go_json.go | 25 ----- internal/json/json.go | 25 ----- internal/json/jsoniter.go | 26 ------ internal/json/sonic.go | 26 ------ render/json.go | 14 +-- render/render_test.go | 4 +- 19 files changed, 497 insertions(+), 125 deletions(-) create mode 100644 codec/json/api.go create mode 100644 codec/json/go_json.go create mode 100644 codec/json/json.go create mode 100644 codec/json/jsoniter.go create mode 100644 codec/json/sonic.go delete mode 100644 internal/json/go_json.go delete mode 100644 internal/json/json.go delete mode 100644 internal/json/jsoniter.go delete mode 100644 internal/json/sonic.go diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 235692d2..1501209e 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -13,8 +13,8 @@ import ( "strings" "time" + "github.com/gin-gonic/gin/codec/json" "github.com/gin-gonic/gin/internal/bytesconv" - "github.com/gin-gonic/gin/internal/json" ) var ( @@ -333,9 +333,9 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel case multipart.FileHeader: return nil } - return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) + return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Map: - return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) + return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Ptr: if !value.Elem().IsValid() { value.Set(reflect.New(value.Type().Elem())) diff --git a/binding/json.go b/binding/json.go index e21c2ee3..f4ae921a 100644 --- a/binding/json.go +++ b/binding/json.go @@ -10,7 +10,7 @@ import ( "io" "net/http" - "github.com/gin-gonic/gin/internal/json" + "github.com/gin-gonic/gin/codec/json" ) // EnableDecoderUseNumber is used to call the UseNumber method on the JSON @@ -42,7 +42,7 @@ func (jsonBinding) BindBody(body []byte, obj any) error { } func decodeJSON(r io.Reader, obj any) error { - decoder := json.NewDecoder(r) + decoder := json.API.NewDecoder(r) if EnableDecoderUseNumber { decoder.UseNumber() } diff --git a/binding/json_test.go b/binding/json_test.go index fbd5c527..942ee3eb 100644 --- a/binding/json_test.go +++ b/binding/json_test.go @@ -5,8 +5,16 @@ package binding import ( + "io" + "net/http/httptest" "testing" + "time" + "unsafe" + "github.com/gin-gonic/gin/codec/json" + "github.com/gin-gonic/gin/render" + jsoniter "github.com/json-iterator/go" + "github.com/modern-go/reflect2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -28,3 +36,181 @@ func TestJSONBindingBindBodyMap(t *testing.T) { assert.Equal(t, "FOO", s["foo"]) assert.Equal(t, "world", s["hello"]) } + +func TestCustomJsonCodec(t *testing.T) { + // Restore json encoding configuration after testing + oldMarshal := json.API + defer func() { + json.API = oldMarshal + }() + // Custom json api + json.API = customJsonApi{} + + // test decode json + obj := customReq{} + err := jsonBinding{}.BindBody([]byte(`{"time_empty":null,"time_struct": "2001-12-05 10:01:02.345","time_nil":null,"time_pointer":"2002-12-05 10:01:02.345"}`), &obj) + require.NoError(t, err) + assert.Equal(t, zeroTime, obj.TimeEmpty) + assert.Equal(t, time.Date(2001, 12, 5, 10, 1, 2, 345000000, time.Local), obj.TimeStruct) + assert.Nil(t, obj.TimeNil) + assert.Equal(t, time.Date(2002, 12, 5, 10, 1, 2, 345000000, time.Local), *obj.TimePointer) + // test encode json + w := httptest.NewRecorder() + err2 := (render.PureJSON{Data: obj}).Render(w) + require.NoError(t, err2) + assert.JSONEq(t, "{\"time_empty\":null,\"time_struct\":\"2001-12-05 10:01:02.345\",\"time_nil\":null,\"time_pointer\":\"2002-12-05 10:01:02.345\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) +} + +type customReq struct { + TimeEmpty time.Time `json:"time_empty"` + TimeStruct time.Time `json:"time_struct"` + TimeNil *time.Time `json:"time_nil"` + TimePointer *time.Time `json:"time_pointer"` +} + +var customConfig = jsoniter.Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: true, +}.Froze() + +func init() { + customConfig.RegisterExtension(&TimeEx{}) + customConfig.RegisterExtension(&TimePointerEx{}) +} + +type customJsonApi struct{} + +func (j customJsonApi) Marshal(v any) ([]byte, error) { + return customConfig.Marshal(v) +} + +func (j customJsonApi) Unmarshal(data []byte, v any) error { + return customConfig.Unmarshal(data, v) +} + +func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return customConfig.MarshalIndent(v, prefix, indent) +} + +func (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder { + return customConfig.NewEncoder(writer) +} + +func (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder { + return customConfig.NewDecoder(reader) +} + +// region Time Extension + +var ( + zeroTime = time.Time{} + timeType = reflect2.TypeOfPtr((*time.Time)(nil)).Elem() + defaultTimeCodec = &timeCodec{} +) + +type TimeEx struct { + jsoniter.DummyExtension +} + +func (te *TimeEx) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder { + if typ == timeType { + return defaultTimeCodec + } + return nil +} + +func (te *TimeEx) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder { + if typ == timeType { + return defaultTimeCodec + } + return nil +} + +type timeCodec struct{} + +func (tc timeCodec) IsEmpty(ptr unsafe.Pointer) bool { + t := *((*time.Time)(ptr)) + return t.Equal(zeroTime) +} + +func (tc timeCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { + t := *((*time.Time)(ptr)) + if t.Equal(zeroTime) { + stream.WriteNil() + return + } + stream.WriteString(t.In(time.Local).Format("2006-01-02 15:04:05.000")) +} + +func (tc timeCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + ts := iter.ReadString() + if len(ts) == 0 { + *((*time.Time)(ptr)) = zeroTime + return + } + t, err := time.ParseInLocation("2006-01-02 15:04:05.000", ts, time.Local) + if err != nil { + panic(err) + } + *((*time.Time)(ptr)) = t +} + +// endregion + +// region *Time Extension + +var ( + timePointerType = reflect2.TypeOfPtr((**time.Time)(nil)).Elem() + defaultTimePointerCodec = &timePointerCodec{} +) + +type TimePointerEx struct { + jsoniter.DummyExtension +} + +func (tpe *TimePointerEx) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder { + if typ == timePointerType { + return defaultTimePointerCodec + } + return nil +} + +func (tpe *TimePointerEx) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder { + if typ == timePointerType { + return defaultTimePointerCodec + } + return nil +} + +type timePointerCodec struct{} + +func (tpc timePointerCodec) IsEmpty(ptr unsafe.Pointer) bool { + t := *((**time.Time)(ptr)) + return t == nil || (*t).Equal(zeroTime) +} + +func (tpc timePointerCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { + t := *((**time.Time)(ptr)) + if t == nil || (*t).Equal(zeroTime) { + stream.WriteNil() + return + } + stream.WriteString(t.In(time.Local).Format("2006-01-02 15:04:05.000")) +} + +func (tpc timePointerCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + ts := iter.ReadString() + if len(ts) == 0 { + *((**time.Time)(ptr)) = nil + return + } + t, err := time.ParseInLocation("2006-01-02 15:04:05.000", ts, time.Local) + if err != nil { + panic(err) + } + *((**time.Time)(ptr)) = &t +} + +// endregion diff --git a/codec/json/api.go b/codec/json/api.go new file mode 100644 index 00000000..f2135683 --- /dev/null +++ b/codec/json/api.go @@ -0,0 +1,57 @@ +// 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 json + +import "io" + +// API the json codec in use. +var API Core + +// Core the api for json codec. +type Core interface { + Marshal(v any) ([]byte, error) + Unmarshal(data []byte, v any) error + MarshalIndent(v any, prefix, indent string) ([]byte, error) + NewEncoder(writer io.Writer) Encoder + NewDecoder(reader io.Reader) Decoder +} + +// Encoder an interface writes JSON values to an output stream. +type Encoder interface { + // SetEscapeHTML specifies whether problematic HTML characters + // should be escaped inside JSON quoted strings. + // The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e + // to avoid certain safety problems that can arise when embedding JSON in HTML. + // + // In non-HTML settings where the escaping interferes with the readability + // of the output, SetEscapeHTML(false) disables this behavior. + SetEscapeHTML(on bool) + + // Encode writes the JSON encoding of v to the stream, + // followed by a newline character. + // + // See the documentation for Marshal for details about the + // conversion of Go values to JSON. + Encode(v any) error +} + +// Decoder an interface reads and decodes JSON values from an input stream. +type Decoder interface { + // UseNumber causes the Decoder to unmarshal a number into an any as a + // Number instead of as a float64. + UseNumber() + + // DisallowUnknownFields causes the Decoder to return an error when the destination + // is a struct and the input contains object keys which do not match any + // non-ignored, exported fields in the destination. + DisallowUnknownFields() + + // Decode reads the next JSON-encoded value from its + // input and stores it in the value pointed to by v. + // + // See the documentation for Unmarshal for details about + // the conversion of JSON into a Go value. + Decode(v any) error +} diff --git a/codec/json/go_json.go b/codec/json/go_json.go new file mode 100644 index 00000000..42a476ac --- /dev/null +++ b/codec/json/go_json.go @@ -0,0 +1,42 @@ +// 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. + +//go:build go_json + +package json + +import ( + "io" + + "github.com/goccy/go-json" +) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/goccy/go-json" + +func init() { + API = gojsonApi{} +} + +type gojsonApi struct{} + +func (j gojsonApi) Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +func (j gojsonApi) Unmarshal(data []byte, v any) error { + return json.Unmarshal(data, v) +} + +func (j gojsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return json.MarshalIndent(v, prefix, indent) +} + +func (j gojsonApi) NewEncoder(writer io.Writer) Encoder { + return json.NewEncoder(writer) +} + +func (j gojsonApi) NewDecoder(reader io.Reader) Decoder { + return json.NewDecoder(reader) +} diff --git a/codec/json/json.go b/codec/json/json.go new file mode 100644 index 00000000..2971f42f --- /dev/null +++ b/codec/json/json.go @@ -0,0 +1,41 @@ +// 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. + +//go:build !jsoniter && !go_json && !(sonic && (linux || windows || darwin)) + +package json + +import ( + "encoding/json" + "io" +) + +// Package indicates what library is being used for JSON encoding. +const Package = "encoding/json" + +func init() { + API = jsonApi{} +} + +type jsonApi struct{} + +func (j jsonApi) Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +func (j jsonApi) Unmarshal(data []byte, v any) error { + return json.Unmarshal(data, v) +} + +func (j jsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return json.MarshalIndent(v, prefix, indent) +} + +func (j jsonApi) NewEncoder(writer io.Writer) Encoder { + return json.NewEncoder(writer) +} + +func (j jsonApi) NewDecoder(reader io.Reader) Decoder { + return json.NewDecoder(reader) +} diff --git a/codec/json/jsoniter.go b/codec/json/jsoniter.go new file mode 100644 index 00000000..ea624e77 --- /dev/null +++ b/codec/json/jsoniter.go @@ -0,0 +1,44 @@ +// 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. + +//go:build jsoniter + +package json + +import ( + "io" + + jsoniter "github.com/json-iterator/go" +) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/json-iterator/go" + +func init() { + API = jsoniterApi{} +} + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +type jsoniterApi struct{} + +func (j jsoniterApi) Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +func (j jsoniterApi) Unmarshal(data []byte, v any) error { + return json.Unmarshal(data, v) +} + +func (j jsoniterApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return json.MarshalIndent(v, prefix, indent) +} + +func (j jsoniterApi) NewEncoder(writer io.Writer) Encoder { + return json.NewEncoder(writer) +} + +func (j jsoniterApi) NewDecoder(reader io.Reader) Decoder { + return json.NewDecoder(reader) +} diff --git a/codec/json/sonic.go b/codec/json/sonic.go new file mode 100644 index 00000000..69496565 --- /dev/null +++ b/codec/json/sonic.go @@ -0,0 +1,44 @@ +// 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. + +//go:build sonic && (linux || windows || darwin) + +package json + +import ( + "io" + + "github.com/bytedance/sonic" +) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/bytedance/sonic" + +func init() { + API = sonicApi{} +} + +var json = sonic.ConfigStd + +type sonicApi struct{} + +func (j sonicApi) Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +func (j sonicApi) Unmarshal(data []byte, v any) error { + return json.Unmarshal(data, v) +} + +func (j sonicApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return json.MarshalIndent(v, prefix, indent) +} + +func (j sonicApi) NewEncoder(writer io.Writer) Encoder { + return json.NewEncoder(writer) +} + +func (j sonicApi) NewDecoder(reader io.Reader) Decoder { + return json.NewDecoder(reader) +} diff --git a/context_test.go b/context_test.go index ff43cd0a..fe3ccd69 100644 --- a/context_test.go +++ b/context_test.go @@ -28,7 +28,7 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" - "github.com/gin-gonic/gin/internal/json" + "github.com/gin-gonic/gin/codec/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/docs/doc.md b/docs/doc.md index 249f93a0..0dd86684 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -63,6 +63,7 @@ - [http2 server push](#http2-server-push) - [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Set and get a cookie](#set-and-get-a-cookie) + - [Custom json codec at runtime](#custom-json-codec-at-runtime) - [Don't trust all proxies](#dont-trust-all-proxies) - [Testing](#testing) @@ -2371,6 +2372,65 @@ func main() { } ``` +### Custom json codec at runtime + +Gin support custom json serialization and deserialization logic without using compile tags. + +1. Define a custom struct implements the `json.Core` interface. + +2. Before your engine starts, assign values to `json.API` using the custom struct. + +```go +package main + +import ( + "io" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/codec/json" + jsoniter "github.com/json-iterator/go" +) + +var customConfig = jsoniter.Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: true, +}.Froze() + +// implement api.JsonApi +type customJsonApi struct { +} + +func (j customJsonApi) Marshal(v any) ([]byte, error) { + return customConfig.Marshal(v) +} + +func (j customJsonApi) Unmarshal(data []byte, v any) error { + return customConfig.Unmarshal(data, v) +} + +func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return customConfig.MarshalIndent(v, prefix, indent) +} + +func (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder { + return customConfig.NewEncoder(writer) +} + +func (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder { + return customConfig.NewDecoder(reader) +} + +func main() { + //Replace the default json api + json.API = customJsonApi{} + + //Start your gin engine + router := gin.Default() + router.Run(":8080") +} +``` + ## Don't trust all proxies Gin lets you specify which headers to hold the real client IP (if any), diff --git a/errors.go b/errors.go index b0d70a94..829e9d2c 100644 --- a/errors.go +++ b/errors.go @@ -9,7 +9,7 @@ import ( "reflect" "strings" - "github.com/gin-gonic/gin/internal/json" + "github.com/gin-gonic/gin/codec/json" ) // ErrorType is an unsigned 64-bit error code as defined in the gin spec. @@ -77,7 +77,7 @@ func (msg *Error) JSON() any { // MarshalJSON implements the json.Marshaller interface. func (msg *Error) MarshalJSON() ([]byte, error) { - return json.Marshal(msg.JSON()) + return json.API.Marshal(msg.JSON()) } // Error implements the error interface. @@ -157,7 +157,7 @@ func (a errorMsgs) JSON() any { // MarshalJSON implements the json.Marshaller interface. func (a errorMsgs) MarshalJSON() ([]byte, error) { - return json.Marshal(a.JSON()) + return json.API.Marshal(a.JSON()) } func (a errorMsgs) String() string { diff --git a/errors_test.go b/errors_test.go index 85ed3dd5..6d8df278 100644 --- a/errors_test.go +++ b/errors_test.go @@ -9,7 +9,7 @@ import ( "fmt" "testing" - "github.com/gin-gonic/gin/internal/json" + "github.com/gin-gonic/gin/codec/json" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -33,7 +33,7 @@ func TestError(t *testing.T) { "meta": "some data", }, err.JSON()) - jsonBytes, _ := json.Marshal(err) + jsonBytes, _ := json.API.Marshal(err) assert.JSONEq(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) err.SetMeta(H{ //nolint: errcheck @@ -92,13 +92,13 @@ Error #03: third H{"error": "second", "meta": "some data"}, H{"error": "third", "status": "400"}, }, errs.JSON()) - jsonBytes, _ := json.Marshal(errs) + jsonBytes, _ := json.API.Marshal(errs) assert.JSONEq(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes)) errs = errorMsgs{ {Err: errors.New("first"), Type: ErrorTypePrivate}, } assert.Equal(t, H{"error": "first"}, errs.JSON()) - jsonBytes, _ = json.Marshal(errs) + jsonBytes, _ = json.API.Marshal(errs) assert.JSONEq(t, "{\"error\":\"first\"}", string(jsonBytes)) errs = errorMsgs{} diff --git a/go.mod b/go.mod index 24d3a895..1d480f84 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 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.52.0 github.com/stretchr/testify v1.10.0 @@ -30,7 +31,6 @@ require ( github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect diff --git a/internal/json/go_json.go b/internal/json/go_json.go deleted file mode 100644 index dee09dec..00000000 --- a/internal/json/go_json.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 Bo-Yi Wu. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build go_json - -package json - -import json "github.com/goccy/go-json" - -var ( - // Marshal is exported by gin/json package. - Marshal = json.Marshal - // Unmarshal is exported by gin/json package. - Unmarshal = json.Unmarshal - // MarshalIndent is exported by gin/json package. - MarshalIndent = json.MarshalIndent - // NewDecoder is exported by gin/json package. - NewDecoder = json.NewDecoder - // NewEncoder is exported by gin/json package. - NewEncoder = json.NewEncoder -) - -// Package indicates what library is being used for JSON encoding. -const Package = "github.com/goccy/go-json" diff --git a/internal/json/json.go b/internal/json/json.go deleted file mode 100644 index 539daa78..00000000 --- a/internal/json/json.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 Bo-Yi Wu. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !jsoniter && !go_json && !(sonic && (linux || windows || darwin)) - -package json - -import "encoding/json" - -var ( - // Marshal is exported by gin/json package. - Marshal = json.Marshal - // Unmarshal is exported by gin/json package. - Unmarshal = json.Unmarshal - // MarshalIndent is exported by gin/json package. - MarshalIndent = json.MarshalIndent - // NewDecoder is exported by gin/json package. - NewDecoder = json.NewDecoder - // NewEncoder is exported by gin/json package. - NewEncoder = json.NewEncoder -) - -// Package indicates what library is being used for JSON encoding. -const Package = "encoding/json" diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go deleted file mode 100644 index 287ebf70..00000000 --- a/internal/json/jsoniter.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2017 Bo-Yi Wu. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build jsoniter - -package json - -import jsoniter "github.com/json-iterator/go" - -var ( - json = jsoniter.ConfigCompatibleWithStandardLibrary - // Marshal is exported by gin/json package. - Marshal = json.Marshal - // Unmarshal is exported by gin/json package. - Unmarshal = json.Unmarshal - // MarshalIndent is exported by gin/json package. - MarshalIndent = json.MarshalIndent - // NewDecoder is exported by gin/json package. - NewDecoder = json.NewDecoder - // NewEncoder is exported by gin/json package. - NewEncoder = json.NewEncoder -) - -// Package indicates what library is being used for JSON encoding. -const Package = "github.com/json-iterator/go" diff --git a/internal/json/sonic.go b/internal/json/sonic.go deleted file mode 100644 index b3f72424..00000000 --- a/internal/json/sonic.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build sonic && (linux || windows || darwin) - -package json - -import "github.com/bytedance/sonic" - -var ( - json = sonic.ConfigStd - // Marshal is exported by gin/json package. - Marshal = json.Marshal - // Unmarshal is exported by gin/json package. - Unmarshal = json.Unmarshal - // MarshalIndent is exported by gin/json package. - MarshalIndent = json.MarshalIndent - // NewDecoder is exported by gin/json package. - NewDecoder = json.NewDecoder - // NewEncoder is exported by gin/json package. - NewEncoder = json.NewEncoder -) - -// Package indicates what library is being used for JSON encoding. -const Package = "github.com/bytedance/sonic" diff --git a/render/json.go b/render/json.go index 23923c44..2f98676c 100644 --- a/render/json.go +++ b/render/json.go @@ -11,8 +11,8 @@ import ( "net/http" "unicode" + "github.com/gin-gonic/gin/codec/json" "github.com/gin-gonic/gin/internal/bytesconv" - "github.com/gin-gonic/gin/internal/json" ) // JSON contains the given interface object. @@ -66,7 +66,7 @@ func (r JSON) WriteContentType(w http.ResponseWriter) { // WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj any) error { writeContentType(w, jsonContentType) - jsonBytes, err := json.Marshal(obj) + jsonBytes, err := json.API.Marshal(obj) if err != nil { return err } @@ -77,7 +77,7 @@ func WriteJSON(w http.ResponseWriter, obj any) error { // Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. func (r IndentedJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) - jsonBytes, err := json.MarshalIndent(r.Data, "", " ") + jsonBytes, err := json.API.MarshalIndent(r.Data, "", " ") if err != nil { return err } @@ -93,7 +93,7 @@ func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { // Render (SecureJSON) marshals the given interface object and writes it with custom ContentType. func (r SecureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) - jsonBytes, err := json.Marshal(r.Data) + jsonBytes, err := json.API.Marshal(r.Data) if err != nil { return err } @@ -116,7 +116,7 @@ func (r SecureJSON) WriteContentType(w http.ResponseWriter) { // Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType. func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) - ret, err := json.Marshal(r.Data) + ret, err := json.API.Marshal(r.Data) if err != nil { return err } @@ -154,7 +154,7 @@ func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { // Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType. func (r AsciiJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) - ret, err := json.Marshal(r.Data) + ret, err := json.API.Marshal(r.Data) if err != nil { return err } @@ -183,7 +183,7 @@ func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { // Render (PureJSON) writes custom ContentType and encodes the given interface object. func (r PureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) - encoder := json.NewEncoder(w) + encoder := json.API.NewEncoder(w) encoder.SetEscapeHTML(false) return encoder.Encode(r.Data) } diff --git a/render/render_test.go b/render/render_test.go index 4d8ebb19..0b98e63d 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -15,7 +15,7 @@ import ( "strings" "testing" - "github.com/gin-gonic/gin/internal/json" + "github.com/gin-gonic/gin/codec/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -173,7 +173,7 @@ func TestRenderJsonpJSONError(t *testing.T) { err = jsonpJSON.Render(ew) assert.Equal(t, `write "`+`(`+`" error`, err.Error()) - data, _ := json.Marshal(jsonpJSON.Data) // error was returned while writing data + data, _ := json.API.Marshal(jsonpJSON.Data) // error was returned while writing data ew.bufString = string(data) err = jsonpJSON.Render(ew) assert.Equal(t, `write "`+string(data)+`" error`, err.Error()) From cf4775283ec30cda685355b5016c5abd2a56884e Mon Sep 17 00:00:00 2001 From: "M. Ilham Surya Pratama" Date: Sat, 21 Jun 2025 11:38:28 +0700 Subject: [PATCH 236/332] chroe: migrate yaml package to github.com/goccy/go-yaml (#4272) --- binding/yaml.go | 2 +- go.mod | 3 ++- go.sum | 2 ++ render/render_test.go | 9 ++++++++- render/yaml.go | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/binding/yaml.go b/binding/yaml.go index 2535f8c3..6638e739 100644 --- a/binding/yaml.go +++ b/binding/yaml.go @@ -9,7 +9,7 @@ import ( "io" "net/http" - "gopkg.in/yaml.v3" + "github.com/goccy/go-yaml" ) type yamlBinding struct{} diff --git a/go.mod b/go.mod index 1d480f84..9112f9d1 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.26.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 github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 @@ -16,7 +17,6 @@ require ( github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.41.0 google.golang.org/protobuf v1.36.6 - gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -43,4 +43,5 @@ require ( golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect golang.org/x/tools v0.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3971e4b2..09a7c5a1 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= diff --git a/render/render_test.go b/render/render_test.go index 0b98e63d..d9ae2067 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -285,7 +285,14 @@ b: err := (YAML{data}).Render(w) require.NoError(t, err) - assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String()) + + // With github.com/goccy/go-yaml, the output format is different from gopkg.in/yaml.v3 + // We're checking that the output contains the expected data, not the exact formatting + output := w.Body.String() + assert.Contains(t, output, "a : Easy!") + assert.Contains(t, output, "b:") + assert.Contains(t, output, "c: 2") + assert.Contains(t, output, "d: [3, 4]") assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/render/yaml.go b/render/yaml.go index 042bb821..98b06442 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -7,7 +7,7 @@ package render import ( "net/http" - "gopkg.in/yaml.v3" + "github.com/goccy/go-yaml" ) // YAML contains the given interface object. From 76dd08d512504b80ef76a76c9e6bd1831e121b71 Mon Sep 17 00:00:00 2001 From: Denny Septian Panggabean <97607754+ddevsr@users.noreply.github.com> Date: Mon, 7 Jul 2025 16:20:47 +0700 Subject: [PATCH 237/332] docs: wrong badge workflow in README.md (#4286) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a582709..990ff142 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -[![Build Status](https://github.com/gin-gonic/gin/workflows/Run%20Tests/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions?query=branch%3Amaster) +[![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) [![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) From 4bdcd9d0f1154ea5e07fbe7855dd09ee14a6f1e9 Mon Sep 17 00:00:00 2001 From: Denny Septian Panggabean <97607754+ddevsr@users.noreply.github.com> Date: Sun, 13 Jul 2025 08:26:26 +0700 Subject: [PATCH 238/332] docs: added available `ID` documentation (#4287) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 990ff142..94e08a78 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in - [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/) ### Articles From b7d6308bcc84066df79a047ae363a6120fe87808 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 09:27:07 +0800 Subject: [PATCH 239/332] chore(deps): bump github.com/quic-go/quic-go from 0.52.0 to 0.53.0 (#4281) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.52.0 to 0.53.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.52.0...v0.53.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.53.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 | 5 +---- go.sum | 22 ++-------------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 9112f9d1..1ee9d8be 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.52.0 + github.com/quic-go/quic-go v0.53.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.41.0 @@ -26,12 +26,9 @@ require ( 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/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/onsi/ginkgo/v2 v2.9.5 // 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 diff --git a/go.sum b/go.sum index 09a7c5a1..979c20d3 100644 --- a/go.sum +++ b/go.sum @@ -3,9 +3,6 @@ github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1 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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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= @@ -16,8 +13,6 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 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-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -26,20 +21,13 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 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= @@ -53,23 +41,18 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 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.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA= -github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= +github.com/quic-go/quic-go v0.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI= +github.com/quic-go/quic-go v0.53.0/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= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -92,7 +75,6 @@ golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From dbd8a2515093ad47cadc5c1fface89861a0b765c Mon Sep 17 00:00:00 2001 From: Denny Septian Panggabean <97607754+ddevsr@users.noreply.github.com> Date: Sun, 13 Jul 2025 08:40:35 +0700 Subject: [PATCH 240/332] feat: added `AbortWithStatusPureJSON()` in `Context` (#4290) * feat: added `AbortWithStatusPureJSON()` in context * Update context_test.go --- context.go | 8 ++++++++ context_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/context.go b/context.go index bf12830c..b1846785 100644 --- a/context.go +++ b/context.go @@ -216,6 +216,14 @@ func (c *Context) AbortWithStatus(code int) { c.Abort() } +// AbortWithStatusJSON calls `Abort()` and then `PureJSON` internally. +// This method stops the chain, writes the status code and return a JSON body without escaping. +// It also sets the Content-Type as "application/json". +func (c *Context) AbortWithStatusPureJSON(code int, jsonObj any) { + c.Abort() + c.PureJSON(code, jsonObj) +} + // AbortWithStatusJSON calls `Abort()` and then `JSON` internally. // This method stops the chain, writes the status code and return a JSON body. // It also sets the Content-Type as "application/json". diff --git a/context_test.go b/context_test.go index fe3ccd69..74f0842a 100644 --- a/context_test.go +++ b/context_test.go @@ -1680,6 +1680,32 @@ func TestContextAbortWithStatusJSON(t *testing.T) { assert.JSONEq(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) } +func TestContextAbortWithStatusPureJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.index = 4 + + in := new(testJSONAbortMsg) + in.Bar = "barValue" + in.Foo = "fooValue" + + c.AbortWithStatusPureJSON(http.StatusUnsupportedMediaType, in) + + assert.Equal(t, abortIndex, c.index) + assert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status()) + assert.Equal(t, http.StatusUnsupportedMediaType, w.Code) + assert.True(t, c.IsAborted()) + + contentType := w.Header().Get("Content-Type") + assert.Equal(t, "application/json; charset=utf-8", contentType) + + buf := new(bytes.Buffer) + _, err := buf.ReadFrom(w.Body) + require.NoError(t, err) + jsonStringBody := buf.String() + assert.JSONEq(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) +} + func TestContextError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.Empty(t, c.Errors) From a6287825c95821a378a34f8a5c9139ea1f6ebd96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 09:41:31 +0800 Subject: [PATCH 241/332] chore(deps): bump github.com/ugorji/go/codec from 1.2.12 to 1.3.0 (#4268) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.2.12 to 1.3.0. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/v1.2.12...codec/v1.3.0) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec dependency-version: 1.3.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 | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 1ee9d8be..9193bd2e 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.53.0 github.com/stretchr/testify v1.10.0 - github.com/ugorji/go/codec v1.2.12 + github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.41.0 google.golang.org/protobuf v1.36.6 ) diff --git a/go.sum b/go.sum index 979c20d3..ab2a7750 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,8 @@ 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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -61,8 +61,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +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= From 545fd74379a0b167a918e38626ae5f7eb83fb243 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 09:41:58 +0800 Subject: [PATCH 242/332] chore(deps): bump github.com/go-playground/validator/v10 (#4289) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.26.0 to 10.27.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.26.0...v10.27.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-version: 10.27.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 9193bd2e..386a03bc 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 require ( github.com/bytedance/sonic v1.13.2 github.com/gin-contrib/sse v1.1.0 - github.com/go-playground/validator/v10 v10.26.0 + github.com/go-playground/validator/v10 v10.27.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 diff --git a/go.sum b/go.sum index ab2a7750..fa948c7b 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,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.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= -github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +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/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= From bdc1ad7987b6931801af9d50e2df25667fdfaaf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AB=8F=E8=A8=AA=E5=8E=9F=E6=85=B6=E6=96=97?= <131525647+suwakei@users.noreply.github.com> Date: Sun, 13 Jul 2025 10:43:32 +0900 Subject: [PATCH 243/332] docs: added comment in doc.go (#4274) --- doc.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc.go b/doc.go index 1bd03864..9442aa70 100644 --- a/doc.go +++ b/doc.go @@ -2,5 +2,21 @@ Package gin implements a HTTP web framework called gin. See https://gin-gonic.com/ for more information about gin. + +Example: + + package main + + import "github.com/gin-gonic/gin" + + func main() { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "pong", + }) + }) + r.Run() // listen and serve on 0.0.0.0:8080 + } */ package gin // import "github.com/gin-gonic/gin" From 5826722a87cf5855fcc4b794cbef11612352771d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AB=8F=E8=A8=AA=E5=8E=9F=E6=85=B6=E6=96=97?= <131525647+suwakei@users.noreply.github.com> Date: Thu, 17 Jul 2025 20:51:40 +0900 Subject: [PATCH 244/332] fix: version number discrepancy (#4299) --- debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug.go b/debug.go index 0ab14e4e..7fe2762e 100644 --- a/debug.go +++ b/debug.go @@ -13,7 +13,7 @@ import ( "sync/atomic" ) -const ginSupportMinGoVer = 21 +const ginSupportMinGoVer = 23 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. From ad4f436ae9823ec577b746d0560201557f8c9691 Mon Sep 17 00:00:00 2001 From: Leon cap Date: Sat, 19 Jul 2025 14:58:12 +0800 Subject: [PATCH 245/332] docs: correct article usage in comments (#4301) - Fix 'an url' to 'a URL' in logger.go comment - Fix 'an form' to 'a form' in binding/form_mapping.go comment - Improve grammar consistency in code documentation --- binding/form_mapping.go | 2 +- logger.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 1501209e..45a39e15 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -175,7 +175,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter // BindUnmarshaler is the interface used to wrap the UnmarshalParam method. type BindUnmarshaler interface { - // UnmarshalParam decodes and assigns a value from an form or query param. + // UnmarshalParam decodes and assigns a value from a form or query param. UnmarshalParam(param string) error } diff --git a/logger.go b/logger.go index f4a250ac..47827787 100644 --- a/logger.go +++ b/logger.go @@ -44,7 +44,7 @@ type LoggerConfig struct { // Optional. Default value is gin.DefaultWriter. Output io.Writer - // SkipPaths is an url path array which logs are not written. + // SkipPaths is a URL path array which logs are not written. // Optional. SkipPaths []string From 57ec9e603642dd8a48fbd860e1f4fc5de7be37c0 Mon Sep 17 00:00:00 2001 From: maskpp Date: Sat, 19 Jul 2025 15:07:44 +0800 Subject: [PATCH 246/332] chore(mode): remove impossible case (empty value for mode) (#4303) --- mode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode.go b/mode.go index cc313437..dfef07d6 100644 --- a/mode.go +++ b/mode.go @@ -65,7 +65,7 @@ func SetMode(value string) { } switch value { - case DebugMode, "": + case DebugMode: atomic.StoreInt32(&ginMode, debugCode) case ReleaseMode: atomic.StoreInt32(&ginMode, releaseCode) From ae5be7fcb726ac6417f6b5deb70afd4a274f64f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:08:22 +0800 Subject: [PATCH 247/332] chore(deps): bump golang.org/x/net from 0.41.0 to 0.42.0 (#4297) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.41.0 to 0.42.0. - [Commits](https://github.com/golang/net/compare/v0.41.0...v0.42.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.42.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 386a03bc..8d0e48d1 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/quic-go/quic-go v0.53.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.3.0 - golang.org/x/net v0.41.0 + golang.org/x/net v0.42.0 google.golang.org/protobuf v1.36.6 ) @@ -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.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.39.0 // indirect + golang.org/x/crypto v0.40.0 // indirect golang.org/x/mod v0.25.0 // indirect - golang.org/x/sync v0.15.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect - golang.org/x/tools v0.33.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.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 fa948c7b..1d220589 100644 --- a/go.sum +++ b/go.sum @@ -67,21 +67,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.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.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= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From a4ac275e079d46d493965491d686bfe72d121e85 Mon Sep 17 00:00:00 2001 From: chenhuiluo <41547730+chenhuiluo@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:49:41 +0800 Subject: [PATCH 248/332] test(route): add some test for routergroup (#4291) Co-authored-by: chenhuiluo --- routergroup_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/routergroup_test.go b/routergroup_test.go index 6848063e..182c5589 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -11,6 +11,8 @@ import ( "github.com/stretchr/testify/assert" ) +var MaxHandlers = 32 + func init() { SetMode(TestMode) } @@ -193,3 +195,25 @@ func testRoutesInterface(t *testing.T, r IRoutes) { assert.Equal(t, r, r.Static("/static", ".")) assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false))) } + +func TestRouterGroupCombineHandlersTooManyHandlers(t *testing.T) { + group := &RouterGroup{ + Handlers: make(HandlersChain, MaxHandlers), // Assume group already has MaxHandlers middleware + } + tooManyHandlers := make(HandlersChain, MaxHandlers) // Add MaxHandlers more, total 2 * MaxHandlers + + // This should trigger panic + assert.Panics(t, func() { + group.combineHandlers(tooManyHandlers) + }, "should panic due to too many handlers") +} + +func TestRouterGroupCombineHandlersEmptySliceNotNil(t *testing.T) { + group := &RouterGroup{ + Handlers: HandlersChain{}, + } + + result := group.combineHandlers(HandlersChain{}) + assert.NotNil(t, result, "result should not be nil even with empty handlers") + assert.Empty(t, result, "empty handlers should return empty chain") +} From e4c2a2762448fbd094463a2022e97bc5be98ec63 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Tue, 22 Jul 2025 11:19:08 +0800 Subject: [PATCH 249/332] refactor(context): remove unused Context dependency in get method (#4304) Co-authored-by: 1911860538 --- context.go | 19 +++---- context_test.go | 131 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index b1846785..cb3362fb 100644 --- a/context.go +++ b/context.go @@ -573,7 +573,7 @@ func (c *Context) QueryMap(key string) (dicts map[string]string) { // whether at least one value exists for the given key. func (c *Context) GetQueryMap(key string) (map[string]string, bool) { c.initQueryCache() - return c.get(c.queryCache, key) + return getMapFromFormData(c.queryCache, key) } // PostForm returns the specified key from a POST urlencoded form or multipart form @@ -646,22 +646,23 @@ func (c *Context) PostFormMap(key string) (dicts map[string]string) { // whether at least one value exists for the given key. func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { c.initFormCache() - return c.get(c.formCache, key) + return getMapFromFormData(c.formCache, key) } -// get is an internal method and returns a map which satisfies conditions. -func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) { - dicts := make(map[string]string) - exist := false +// getMapFromFormData return a map which satisfies conditions. +// It parses from data with bracket notation like "key[subkey]=value" into a map. +func getMapFromFormData(m map[string][]string, key string) (map[string]string, bool) { + d := make(map[string]string) + found := false 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 { - exist = true - dicts[k[i+1:][:j]] = v[0] + found = true + d[k[i+1:][:j]] = v[0] } } } - return dicts, exist + return d, found } // FormFile returns the first file for the provided form key. diff --git a/context_test.go b/context_test.go index 74f0842a..895cd8ad 100644 --- a/context_test.go +++ b/context_test.go @@ -3350,3 +3350,134 @@ func TestContextSetCookieData(t *testing.T) { assert.Contains(t, setCookie, "SameSite=None") }) } + +func TestGetMapFromFormData(t *testing.T) { + testCases := []struct { + name string + data map[string][]string + key string + expected map[string]string + found bool + }{ + { + name: "Basic bracket notation", + data: map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + }, + key: "ids", + expected: map[string]string{ + "a": "hi", + "b": "3.14", + }, + found: true, + }, + { + name: "Mixed data with bracket notation", + data: map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "names[a]": {"mike"}, + "names[b]": {"maria"}, + "other[key]": {"value"}, + "simple": {"data"}, + }, + key: "ids", + expected: map[string]string{ + "a": "hi", + "b": "3.14", + }, + found: true, + }, + { + name: "Names key", + data: map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "names[a]": {"mike"}, + "names[b]": {"maria"}, + "other[key]": {"value"}, + }, + key: "names", + expected: map[string]string{ + "a": "mike", + "b": "maria", + }, + found: true, + }, + { + name: "Key not found", + data: map[string][]string{ + "ids[a]": {"hi"}, + "names[b]": {"maria"}, + }, + key: "notfound", + expected: map[string]string{}, + found: false, + }, + { + name: "Empty data", + data: map[string][]string{}, + key: "ids", + expected: map[string]string{}, + found: false, + }, + { + name: "Malformed bracket notation", + data: map[string][]string{ + "ids[a": {"hi"}, // Missing closing bracket + "ids]b": {"3.14"}, // Missing opening bracket + "idsab": {"value"}, // No brackets + }, + key: "ids", + expected: map[string]string{}, + found: false, + }, + { + name: "Nested bracket notation", + data: map[string][]string{ + "ids[a][b]": {"nested"}, + "ids[c]": {"simple"}, + }, + key: "ids", + expected: map[string]string{ + "a": "nested", + "c": "simple", + }, + found: true, + }, + { + name: "Simple key without brackets", + data: map[string][]string{ + "simple": {"data"}, + "ids[a]": {"hi"}, + }, + key: "simple", + expected: map[string]string{}, + found: false, + }, + { + name: "Mixed simple and bracket keys", + data: map[string][]string{ + "simple": {"data"}, + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "other": {"value"}, + }, + key: "ids", + expected: map[string]string{ + "a": "hi", + "b": "3.14", + }, + found: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, found := getMapFromFormData(tc.data, tc.key) + assert.Equal(t, tc.expected, result, "result mismatch") + assert.Equal(t, tc.found, found, "found mismatch") + }) + } +} From 9708475b3b2a4e1ac09cdf31f34398cab1b3e277 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Tue, 22 Jul 2025 21:36:47 +0800 Subject: [PATCH 250/332] docs(context): fix AbortWithStatusPureJSON comment typo (#4310) Co-authored-by: 1911860538 --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index cb3362fb..842ad2ff 100644 --- a/context.go +++ b/context.go @@ -216,7 +216,7 @@ func (c *Context) AbortWithStatus(code int) { c.Abort() } -// AbortWithStatusJSON calls `Abort()` and then `PureJSON` internally. +// AbortWithStatusPureJSON calls `Abort()` and then `PureJSON` internally. // This method stops the chain, writes the status code and return a JSON body without escaping. // It also sets the Content-Type as "application/json". func (c *Context) AbortWithStatusPureJSON(code int, jsonObj any) { From dab5944a7bca8ae37d947dda02ac591afc1177d3 Mon Sep 17 00:00:00 2001 From: Leon cap Date: Tue, 22 Jul 2025 21:38:32 +0800 Subject: [PATCH 251/332] test(context): add comprehensive unit tests for `Context.File()` method (#4307) * test: add comprehensive unit tests for Context.File() method - Add TestContextFile with multiple test scenarios in context_test.go - Add simplified tests in context_file_test.go - Cover file serving, 404 handling, directory access, HEAD requests, and Range requests - All tests pass successfully * fix: resolve gci formatting issues in test files * fix: correct test file paths and add test file to gin directory * move test_file.txt to testdata directory as suggested by maintainer * fix: update test file paths to use testdata directory --- context_file_test.go | 35 ++++++++++++++++++++ context_test.go | 73 ++++++++++++++++++++++++++++++++++++++++++ testdata/test_file.txt | 2 ++ 3 files changed, 110 insertions(+) create mode 100644 context_file_test.go create mode 100644 testdata/test_file.txt diff --git a/context_file_test.go b/context_file_test.go new file mode 100644 index 00000000..50cc3c8e --- /dev/null +++ b/context_file_test.go @@ -0,0 +1,35 @@ +package gin + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestContextFileSimple tests the Context.File() method with a simple case +func TestContextFileSimple(t *testing.T) { + // Test serving an existing file + testFile := "testdata/test_file.txt" + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + + c.File(testFile) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "This is a test file") + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) +} + +// TestContextFileNotFound tests serving a non-existent file +func TestContextFileNotFound(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + + c.File("non_existent_file.txt") + + assert.Equal(t, http.StatusNotFound, w.Code) +} diff --git a/context_test.go b/context_test.go index 895cd8ad..f51c147f 100644 --- a/context_test.go +++ b/context_test.go @@ -75,6 +75,79 @@ func must(err error) { } } +// TestContextFile tests the Context.File() method +func TestContextFile(t *testing.T) { + // Test serving an existing file + t.Run("serve existing file", func(t *testing.T) { + // Create a temporary test file + testFile := "testdata/test_file.txt" + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + + c.File(testFile) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "This is a test file") + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + }) + + // Test serving a non-existent file + t.Run("serve non-existent file", func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + + c.File("non_existent_file.txt") + + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + // Test serving a directory (should return 200 with directory listing or 403 Forbidden) + t.Run("serve directory", func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + + c.File(".") + + // Directory serving can return either 200 (with listing) or 403 (forbidden) + assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusForbidden) + }) + + // Test with HEAD request + t.Run("HEAD request", func(t *testing.T) { + testFile := "testdata/test_file.txt" + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodHead, "/test", nil) + + c.File(testFile) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Empty(t, w.Body.String()) // HEAD request should not return body + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + }) + + // Test with Range request + t.Run("Range request", func(t *testing.T) { + testFile := "testdata/test_file.txt" + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + c.Request.Header.Set("Range", "bytes=0-10") + + c.File(testFile) + + assert.Equal(t, http.StatusPartialContent, w.Code) + assert.Equal(t, "bytes", w.Header().Get("Accept-Ranges")) + assert.Contains(t, w.Header().Get("Content-Range"), "bytes 0-10") + }) +} + func TestContextFormFile(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) diff --git a/testdata/test_file.txt b/testdata/test_file.txt new file mode 100644 index 00000000..05fc0842 --- /dev/null +++ b/testdata/test_file.txt @@ -0,0 +1,2 @@ +This is a test file for Context.File() method testing. +It contains some sample content to verify file serving functionality. \ No newline at end of file From b987b6206f13a4c244739e4f4e6c6a2b7dfff9d3 Mon Sep 17 00:00:00 2001 From: Denny Septian Panggabean <97607754+ddevsr@users.noreply.github.com> Date: Sat, 26 Jul 2025 20:02:59 +0700 Subject: [PATCH 252/332] build: make automatically update package in golang (#4311) --- .github/workflows/goreleaser.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 22edf453..3ca5eb20 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -29,3 +29,8 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Trigger Go module reindex (pkg.go.dev) + run: | + echo "Triggering Go module reindex at proxy.golang.org" + curl -sSf "https://proxy.golang.org/github.com/${GITHUB_REPOSITORY,,}/@v/${GITHUB_REF_NAME}.info" From 32065bbd4298d566d060d234e452bbf44e92161d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AB=8F=E8=A8=AA=E5=8E=9F=E6=85=B6=E6=96=97?= <131525647+suwakei@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:16:58 +0900 Subject: [PATCH 253/332] chore(response): prevention of Hijack() runtime panics (#4295) * Prevention of Hijack() runtime panics * added test of Hijack() * fix review * fix lint error * added check assertion of Wrrten() condition before calling Hijack() --- response_writer.go | 6 ++++ response_writer_test.go | 70 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/response_writer.go b/response_writer.go index 753a0b09..ab2f5fec 100644 --- a/response_writer.go +++ b/response_writer.go @@ -6,6 +6,7 @@ package gin import ( "bufio" + "errors" "io" "net" "net/http" @@ -16,6 +17,8 @@ const ( defaultStatus = http.StatusOK ) +var errHijackAlreadyWritten = errors.New("gin: response already written") + // ResponseWriter ... type ResponseWriter interface { http.ResponseWriter @@ -106,6 +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() { + return nil, nil, errHijackAlreadyWritten + } if w.size < 0 { w.size = 0 } diff --git a/response_writer_test.go b/response_writer_test.go index 259b8fa8..ef198418 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -5,6 +5,8 @@ package gin import ( + "bufio" + "net" "net/http" "net/http/httptest" "testing" @@ -124,6 +126,74 @@ func TestResponseWriterHijack(t *testing.T) { w.Flush() } +type mockHijacker struct { + *httptest.ResponseRecorder + hijacked bool +} + +// Hijack implements the http.Hijacker interface. It just records that it was called. +func (m *mockHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) { + m.hijacked = true + return nil, nil, nil +} + +func TestResponseWriterHijackAfterWrite(t *testing.T) { + tests := []struct { + name string + action func(w ResponseWriter) error // Action to perform before hijacking + expectWrittenBeforeHijack bool + expectHijackSuccess bool + expectWrittenAfterHijack bool + expectError error + }{ + { + name: "hijack before write should succeed", + action: func(w ResponseWriter) error { return nil }, + expectWrittenBeforeHijack: false, + expectHijackSuccess: true, + expectWrittenAfterHijack: true, // Hijack itself marks the writer as written + expectError: nil, + }, + { + name: "hijack after write should fail", + action: func(w ResponseWriter) error { + _, 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) + + // Check initial state + assert.False(t, w.Written(), "should not be written initially") + + // Perform pre-hijack action + require.NoError(t, tc.action(w), "unexpected error during pre-hijack action") + + // Check state before hijacking + assert.Equal(t, tc.expectWrittenBeforeHijack, w.Written(), "unexpected w.Written() state before hijack") + + // Attempt to hijack + _, _, hijackErr := w.Hijack() + + // Check results + 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 42f93283cf4a37bf16ac045284b335a24b44aeca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AB=8F=E8=A8=AA=E5=8E=9F=E6=85=B6=E6=96=97?= <131525647+suwakei@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:23:20 +0900 Subject: [PATCH 254/332] docs(test): improved GoDoc in test_helpers.go (#4270) --- test_helpers.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test_helpers.go b/test_helpers.go index 7508c5c9..a1a7c562 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -6,7 +6,10 @@ package gin import "net/http" -// CreateTestContext returns a fresh engine and context for testing purposes +// 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 +// along with a context, for example, to test middleware that doesn't depend on +// specific routes. The ResponseWriter `w` is used to initialize the context's writer. func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { r = New() c = r.allocateContext(0) @@ -15,7 +18,11 @@ func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { return } -// CreateTestContextOnly returns a fresh context base on the engine for testing purposes +// CreateTestContextOnly returns a fresh Context associated with the provided Engine `r`. +// This is useful for tests that operate on an existing, possibly pre-configured, +// Gin engine instance and need a new context for it. +// The ResponseWriter `w` is used to initialize the context's writer. +// The context is allocated with the `maxParams` setting from the provided engine. func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) { c = r.allocateContext(r.maxParams) c.reset() From 17d0b553eac2a32fe82b06e3b4fa0d0cac3bff57 Mon Sep 17 00:00:00 2001 From: Varus Hsu Date: Sat, 2 Aug 2025 12:27:59 +0800 Subject: [PATCH 255/332] chore(render): do not export tomlContentType anymore (#4319) --- render/toml.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/render/toml.go b/render/toml.go index 40f044c8..379ac72d 100644 --- a/render/toml.go +++ b/render/toml.go @@ -15,7 +15,7 @@ type TOML struct { Data any } -var TOMLContentType = []string{"application/toml; charset=utf-8"} +var tomlContentType = []string{"application/toml; charset=utf-8"} // Render (TOML) marshals the given interface object and writes data with custom ContentType. func (r TOML) Render(w http.ResponseWriter) error { @@ -32,5 +32,5 @@ func (r TOML) Render(w http.ResponseWriter) error { // WriteContentType (TOML) writes TOML ContentType for response. func (r TOML) WriteContentType(w http.ResponseWriter) { - writeContentType(w, TOMLContentType) + writeContentType(w, tomlContentType) } From 45b805f6d59ba0b4f315adffe81ed4a82a51a591 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sat, 2 Aug 2025 12:30:14 +0800 Subject: [PATCH 256/332] perf(recovery): optimize the log output of CustomRecoveryWithWriter (#4258) Co-authored-by: 1911860538 --- recovery.go | 39 ++++++++++++----------- recovery_test.go | 80 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 83 insertions(+), 36 deletions(-) diff --git a/recovery.go b/recovery.go index 8a077dbb..fdd463f3 100644 --- a/recovery.go +++ b/recovery.go @@ -17,6 +17,8 @@ import ( "runtime" "strings" "time" + + "github.com/gin-gonic/gin/internal/bytesconv" ) const dunno = "???" @@ -67,19 +69,15 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } } if logger != nil { - stack := stack(3) - httpRequest, _ := httputil.DumpRequest(c.Request, false) - headers := strings.Split(string(httpRequest), "\r\n") - maskAuthorization(headers) - headersToStr := strings.Join(headers, "\r\n") + const stackSkip = 3 if brokenPipe { - logger.Printf("%s\n%s%s", err, headersToStr, reset) + logger.Printf("%s\n%s%s", err, secureRequestDump(c.Request), reset) } else if IsDebugging() { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", - timeFormat(time.Now()), headersToStr, err, stack, reset) + timeFormat(time.Now()), secureRequestDump(c.Request), err, stack(stackSkip), reset) } else { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", - timeFormat(time.Now()), err, stack, reset) + timeFormat(time.Now()), err, stack(stackSkip), reset) } } if brokenPipe { @@ -95,6 +93,21 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } } +// secureRequestDump returns a sanitized HTTP request dump where the Authorization header, +// if present, is replaced with a masked value ("Authorization: *") to avoid leaking sensitive credentials. +// +// Currently, only the Authorization header is sanitized. All other headers and request data remain unchanged. +func secureRequestDump(r *http.Request) string { + httpRequest, _ := httputil.DumpRequest(r, false) + lines := strings.Split(bytesconv.BytesToString(httpRequest), "\r\n") + for i, line := range lines { + if strings.HasPrefix(line, "Authorization:") { + lines[i] = "Authorization: *" + } + } + return strings.Join(lines, "\r\n") +} + func defaultHandleRecovery(c *Context, _ any) { c.AbortWithStatus(http.StatusInternalServerError) } @@ -126,16 +139,6 @@ func stack(skip int) []byte { return buf.Bytes() } -// maskAuthorization replaces any "Authorization: " header with "Authorization: *", hiding sensitive credentials. -func maskAuthorization(headers []string) { - for idx, header := range headers { - key, _, _ := strings.Cut(header, ":") - if strings.EqualFold(key, "Authorization") { - headers[idx] = key + ": *" - } - } -} - // source returns a space-trimmed slice of the n'th line. func source(lines [][]byte, n int) []byte { n-- // in stack trace, lines are 1-indexed but our array is 0-indexed diff --git a/recovery_test.go b/recovery_test.go index 3a36fad9..8a9e3475 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -88,24 +88,6 @@ func TestPanicWithAbort(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) } -func TestMaskAuthorization(t *testing.T) { - secret := "Bearer aaaabbbbccccddddeeeeffff" - headers := []string{ - "Host: www.example.com", - "Authorization: " + secret, - "User-Agent: curl/7.51.0", - "Accept: */*", - "Content-Type: application/json", - "Content-Length: 1", - } - maskAuthorization(headers) - - for _, h := range headers { - assert.NotContains(t, h, secret) - } - assert.Contains(t, headers, "Authorization: *") -} - func TestSource(t *testing.T) { bs := source(nil, 0) assert.Equal(t, dunnoBytes, bs) @@ -263,3 +245,65 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { SetMode(TestMode) } + +func TestSecureRequestDump(t *testing.T) { + tests := []struct { + name string + req *http.Request + wantContains string + wantNotContain string + }{ + { + name: "Authorization header standard case", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header.Set("Authorization", "Bearer secret-token") + return r + }(), + wantContains: "Authorization: *", + wantNotContain: "Bearer secret-token", + }, + { + name: "authorization header lowercase", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header.Set("authorization", "some-secret") + return r + }(), + wantContains: "Authorization: *", + wantNotContain: "some-secret", + }, + { + name: "Authorization header mixed case", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header.Set("AuThOrIzAtIoN", "token123") + return r + }(), + wantContains: "Authorization: *", + wantNotContain: "token123", + }, + { + name: "No Authorization header", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header.Set("Content-Type", "application/json") + return r + }(), + wantContains: "", + wantNotContain: "Authorization: *", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := secureRequestDump(tt.req) + if tt.wantContains != "" && !strings.Contains(result, tt.wantContains) { + t.Errorf("maskHeaders() = %q, want contains %q", result, tt.wantContains) + } + if tt.wantNotContain != "" && strings.Contains(result, tt.wantNotContain) { + t.Errorf("maskHeaders() = %q, want NOT contain %q", result, tt.wantNotContain) + } + }) + } +} From 077a2f39c85700ba0823f85ed29cec0c8f2cbdfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 22:16:53 +0800 Subject: [PATCH 257/332] chore(deps): bump github.com/quic-go/quic-go from 0.53.0 to 0.54.0 (#4328) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.53.0 to 0.54.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.53.0...v0.54.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.54.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 8d0e48d1..5e3e7f76 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.53.0 + github.com/quic-go/quic-go v0.54.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 diff --git a/go.sum b/go.sum index 1d220589..c97f1d95 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,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.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI= -github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +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/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 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 258/332] 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 259/332] 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 260/332] 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 261/332] 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 262/332] 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 263/332] 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 264/332] 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 265/332] 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 266/332] 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 267/332] 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 268/332] 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 269/332] 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 270/332] 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 271/332] 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 272/332] 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 273/332] 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 274/332] 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 275/332] 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 276/332] 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 277/332] 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 278/332] 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 279/332] 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 280/332] 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 281/332] 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 282/332] 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 283/332] 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 284/332] 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 285/332] 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 286/332] 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 287/332] 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 288/332] 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 289/332] 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 290/332] 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 291/332] 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 292/332] 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 293/332] 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 294/332] 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 295/332] 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 296/332] 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 297/332] 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 298/332] 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 299/332] 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 300/332] 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 301/332] 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 302/332] 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 303/332] 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 304/332] 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 305/332] 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 306/332] 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 307/332] 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 308/332] 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 309/332] 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 310/332] 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 311/332] 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 312/332] 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 313/332] 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 314/332] 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 315/332] 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 316/332] 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 317/332] 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 318/332] 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 319/332] 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 320/332] 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 321/332] 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 322/332] 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 323/332] 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 324/332] 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. From 19b877fa50cbbb9282763099fb177a1e5cc5c850 Mon Sep 17 00:00:00 2001 From: OHZEKI Naoki <0h23k1.n40k1@gmail.com> Date: Fri, 5 Dec 2025 12:18:08 +0900 Subject: [PATCH 325/332] test(debug): improve the test coverage of debug.go to 100% (#4404) --- debug.go | 4 +++- debug_test.go | 34 +++++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/debug.go b/debug.go index e07c8b46..1cfa3721 100644 --- a/debug.go +++ b/debug.go @@ -15,6 +15,8 @@ import ( const ginSupportMinGoVer = 24 +var runtimeVersion = runtime.Version() + // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { @@ -77,7 +79,7 @@ func getMinVer(v string) (uint64, error) { } func debugPrintWARNINGDefault() { - if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { + if v, e := getMinVer(runtimeVersion); e == nil && v < ginSupportMinGoVer { debugPrint(`[WARNING] Now Gin requires Go 1.24+. `) diff --git a/debug_test.go b/debug_test.go index e9d8fe01..dab02133 100644 --- a/debug_test.go +++ b/debug_test.go @@ -12,7 +12,6 @@ import ( "log" "net/http" "os" - "runtime" "strings" "sync" "testing" @@ -21,10 +20,6 @@ import ( "github.com/stretchr/testify/require" ) -// TODO -// func debugRoute(httpMethod, absolutePath string, handlers HandlersChain) { -// func debugPrint(format string, values ...any) { - func TestIsDebugging(t *testing.T) { SetMode(DebugMode) assert.True(t, IsDebugging()) @@ -48,6 +43,18 @@ func TestDebugPrint(t *testing.T) { assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re) } +func TestDebugPrintFunc(t *testing.T) { + DebugPrintFunc = func(format string, values ...any) { + fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...) + } + re := captureOutput(t, func() { + SetMode(DebugMode) + debugPrint("debug print func test: %d", 123) + SetMode(TestMode) + }) + assert.Regexp(t, `^\[GIN-debug\] debug print func test: 123`, re) +} + func TestDebugPrintError(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) @@ -104,12 +111,17 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { debugPrintWARNINGDefault() SetMode(TestMode) }) - m, e := getMinVer(runtime.Version()) - if e == nil && m < ginSupportMinGoVer { - 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) - } + assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) +} + +func TestDebugPrintWARNINGDefaultWithUnsupportedVersion(t *testing.T) { + runtimeVersion = "go1.23.12" + re := captureOutput(t, func() { + SetMode(DebugMode) + debugPrintWARNINGDefault() + SetMode(TestMode) + }) + 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) } func TestDebugPrintWARNINGNew(t *testing.T) { From 64a6ed9a41ab72076506bd590dc4b916ca4b66a5 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 12 Dec 2025 13:42:03 +0800 Subject: [PATCH 326/332] perf(recovery): optimize line reading in stack function (#4466) Co-authored-by: 1911860538 --- recovery.go | 54 +++++++++++++++++++++++++++++----------- recovery_test.go | 65 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 89 insertions(+), 30 deletions(-) diff --git a/recovery.go b/recovery.go index e79e118a..6d4b4b2b 100644 --- a/recovery.go +++ b/recovery.go @@ -5,7 +5,9 @@ package gin import ( + "bufio" "bytes" + "cmp" "errors" "fmt" "io" @@ -21,9 +23,10 @@ import ( "github.com/gin-gonic/gin/internal/bytesconv" ) -const dunno = "???" - -var dunnoBytes = []byte(dunno) +const ( + dunno = "???" + stackSkip = 3 +) // RecoveryFunc defines the function passable to CustomRecovery. type RecoveryFunc func(c *Context, err any) @@ -72,7 +75,6 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { brokenPipe = true } if logger != nil { - const stackSkip = 3 if brokenPipe { logger.Printf("%s\n%s%s", err, secureRequestDump(c.Request), reset) } else if IsDebugging() { @@ -120,8 +122,11 @@ func stack(skip int) []byte { buf := new(bytes.Buffer) // the returned data // As we loop, we open files and read them. These variables record the currently // loaded file. - var lines [][]byte - var lastFile string + var ( + nLine string + lastFile string + err error + ) for i := skip; ; i++ { // Skip the expected number of frames pc, file, line, ok := runtime.Caller(i) if !ok { @@ -130,25 +135,44 @@ func stack(skip int) []byte { // Print this much at least. If we can't find the source, it won't show. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) if file != lastFile { - data, err := os.ReadFile(file) + nLine, err = readNthLine(file, line-1) if err != nil { continue } - lines = bytes.Split(data, []byte{'\n'}) lastFile = file } - fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) + fmt.Fprintf(buf, "\t%s: %s\n", function(pc), cmp.Or(nLine, dunno)) } return buf.Bytes() } -// source returns a space-trimmed slice of the n'th line. -func source(lines [][]byte, n int) []byte { - n-- // in stack trace, lines are 1-indexed but our array is 0-indexed - if n < 0 || n >= len(lines) { - return dunnoBytes +// readNthLine reads the nth line from the file. +// It returns the trimmed content of the line if found, +// or an empty string if the line doesn't exist. +// If there's an error opening the file, it returns the error. +func readNthLine(file string, n int) (string, error) { + if n < 0 { + return "", nil } - return bytes.TrimSpace(lines[n]) + + f, err := os.Open(file) + if err != nil { + return "", err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for i := 0; i < n; i++ { + if !scanner.Scan() { + return "", nil + } + } + + if scanner.Scan() { + return strings.TrimSpace(scanner.Text()), nil + } + + return "", nil } // function returns, if possible, the name of the function containing the PC. diff --git a/recovery_test.go b/recovery_test.go index 073f4858..912ab601 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -88,21 +88,6 @@ func TestPanicWithAbort(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) } -func TestSource(t *testing.T) { - bs := source(nil, 0) - assert.Equal(t, dunnoBytes, bs) - - in := [][]byte{ - []byte("Hello world."), - []byte("Hi, gin.."), - } - bs = source(in, 10) - assert.Equal(t, dunnoBytes, bs) - - bs = source(in, 1) - assert.Equal(t, []byte("Hello world."), bs) -} - func TestFunction(t *testing.T) { bs := function(1) assert.Equal(t, dunno, bs) @@ -331,3 +316,53 @@ func TestSecureRequestDump(t *testing.T) { }) } } + +// TestReadNthLine tests the readNthLine function with various scenarios. +func TestReadNthLine(t *testing.T) { + // Create a temporary test file + testContent := "line 0 \n line 1 \nline 2 \nline 3 \nline 4" + tempFile, err := os.CreateTemp("", "testfile*.txt") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tempFile.Name()) + + // Write test content to the temporary file + if _, err := tempFile.WriteString(testContent); err != nil { + t.Fatal(err) + } + if err := tempFile.Close(); err != nil { + t.Fatal(err) + } + + // Test cases + tests := []struct { + name string + lineNum int + fileName string + want string + wantErr bool + }{ + {name: "Read first line", lineNum: 0, fileName: tempFile.Name(), want: "line 0", wantErr: false}, + {name: "Read middle line", lineNum: 2, fileName: tempFile.Name(), want: "line 2", wantErr: false}, + {name: "Read last line", lineNum: 4, fileName: tempFile.Name(), want: "line 4", wantErr: false}, + {name: "Line number exceeds file length", lineNum: 10, fileName: tempFile.Name(), want: "", wantErr: false}, + {name: "Negative line number", lineNum: -1, fileName: tempFile.Name(), want: "", wantErr: false}, + {name: "Non-existent file", lineNum: 1, fileName: "/non/existent/file.txt", want: "", wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := readNthLine(tt.fileName, tt.lineNum) + assert.Equal(t, tt.wantErr, err != nil) + assert.Equal(t, tt.want, got) + }) + } +} + +func BenchmarkStack(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + _ = stack(stackSkip) + } +} From d1a15347b1e45a8ee816193d3578a93bfd73b70f Mon Sep 17 00:00:00 2001 From: OHZEKI Naoki <0h23k1.n40k1@gmail.com> Date: Fri, 12 Dec 2025 14:43:25 +0900 Subject: [PATCH 327/332] refactor(utils): move util functions to utils.go (#4467) Co-authored-by: Bo-Yi Wu --- context.go | 8 -------- tree.go | 9 --------- utils.go | 17 +++++++++++++++++ utils_test.go | 11 +++++++++++ 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/context.go b/context.go index 112f0ee0..e1d9be43 100644 --- a/context.go +++ b/context.go @@ -55,14 +55,6 @@ 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 { diff --git a/tree.go b/tree.go index eff07734..88f25fcb 100644 --- a/tree.go +++ b/tree.go @@ -5,7 +5,6 @@ package gin import ( - "math" "net/url" "strings" "unicode" @@ -78,14 +77,6 @@ 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, "*") diff --git a/utils.go b/utils.go index 47106a7a..971e9433 100644 --- a/utils.go +++ b/utils.go @@ -6,6 +6,7 @@ package gin import ( "encoding/xml" + "math" "net/http" "os" "path" @@ -162,3 +163,19 @@ func isASCII(s string) bool { } return true } + +// 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) +} + +// 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) +} diff --git a/utils_test.go b/utils_test.go index 8bcf00e4..893ebc88 100644 --- a/utils_test.go +++ b/utils_test.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/xml" "fmt" + "math" "net/http" "testing" @@ -148,3 +149,13 @@ func TestIsASCII(t *testing.T) { assert.True(t, isASCII("test")) assert.False(t, isASCII("๐Ÿงก๐Ÿ’›๐Ÿ’š๐Ÿ’™๐Ÿ’œ")) } + +func TestSafeInt8(t *testing.T) { + assert.Equal(t, int8(100), safeInt8(100)) + assert.Equal(t, int8(math.MaxInt8), safeInt8(int(math.MaxInt8)+123)) +} + +func TestSafeUint16(t *testing.T) { + assert.Equal(t, uint16(100), safeUint16(100)) + assert.Equal(t, uint16(math.MaxUint16), safeUint16(int(math.MaxUint16)+123)) +} From 22c274c84ba5a502ce6453a9d2bb9e05ed91b911 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:33:46 +0800 Subject: [PATCH 328/332] chore(deps): bump actions/cache from 4 to 5 in the actions group (#4469) Bumps the actions group with 1 update: [actions/cache](https://github.com/actions/cache). Updates `actions/cache` from 4 to 5 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/cache 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/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 4e3b8753..8ece7f1d 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -65,7 +65,7 @@ jobs: with: ref: ${{ github.ref }} - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: path: | ${{ matrix.go-build }} From 26c3a628655cad2388380cb8102d6ce7d4875f3b Mon Sep 17 00:00:00 2001 From: Twacqwq <69360546+Twacqwq@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:35:20 +0800 Subject: [PATCH 329/332] chore(response): prevent Flush() panic when `http.Flusher` (#4479) --- response_writer.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/response_writer.go b/response_writer.go index 6907e514..9035e6f1 100644 --- a/response_writer.go +++ b/response_writer.go @@ -128,7 +128,9 @@ func (w *responseWriter) CloseNotify() <-chan bool { // Flush implements the http.Flusher interface. func (w *responseWriter) Flush() { w.WriteHeaderNow() - w.ResponseWriter.(http.Flusher).Flush() + if f, ok := w.ResponseWriter.(http.Flusher); ok { + f.Flush() + } } func (w *responseWriter) Pusher() (pusher http.Pusher) { From 915e4c90d28ec4cffc6eb146e208ab5a65eac772 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Sat, 27 Dec 2025 08:25:17 -0300 Subject: [PATCH 330/332] refactor(context): replace hardcoded localhost IPs with constants (#4481) --- context_test.go | 4 ++-- gin_test.go | 2 +- utils.go | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/context_test.go b/context_test.go index 126646fc..016fa86d 100644 --- a/context_test.go +++ b/context_test.go @@ -1910,7 +1910,7 @@ func TestContextClientIP(t *testing.T) { resetContextForClientIPTests(c) // IPv6 support - c.Request.RemoteAddr = "[::1]:12345" + c.Request.RemoteAddr = fmt.Sprintf("[%s]:12345", localhostIPv6) assert.Equal(t, "20.20.20.20", c.ClientIP()) resetContextForClientIPTests(c) @@ -3212,7 +3212,7 @@ func TestContextCopyShouldNotCancel(t *testing.T) { }() addr := strings.Split(l.Addr().String(), ":") - res, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s/", addr[len(addr)-1])) + res, err := http.Get(fmt.Sprintf("http://%s:%s/", localhostIP, addr[len(addr)-1])) if err != nil { t.Error(fmt.Errorf("request error: %w", err)) return diff --git a/gin_test.go b/gin_test.go index 81343d88..43c9494d 100644 --- a/gin_test.go +++ b/gin_test.go @@ -83,7 +83,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { } func TestH2c(t *testing.T) { - ln, err := net.Listen("tcp", "127.0.0.1:0") + ln, err := net.Listen("tcp", localhostIP+":0") if err != nil { t.Error(err) } diff --git a/utils.go b/utils.go index 971e9433..62517784 100644 --- a/utils.go +++ b/utils.go @@ -19,6 +19,12 @@ import ( // BindKey indicates a default bind key. const BindKey = "_gin-gonic/gin/bindkey" +// localhostIP indicates the default localhost IP address. +const localhostIP = "127.0.0.1" + +// localhostIPv6 indicates the default localhost IPv6 address. +const localhostIPv6 = "::1" + // Bind is a helper function for given interface object and returns a Gin middleware. func Bind(val any) HandlerFunc { value := reflect.ValueOf(val) From 9914178584e42458ff7d23891463a880f58c9d86 Mon Sep 17 00:00:00 2001 From: Nurysso <152021471+Nurysso@users.noreply.github.com> Date: Fri, 2 Jan 2026 02:15:27 +0000 Subject: [PATCH 331/332] fix(context): ClientIP handling for multiple X-Forwarded-For header values (#4472) * Fix ClientIP calculation by concatenating all RemoteIPHeaders values * test: used http.MethodGet instead constants and fix lints * lint error fixed * Refactor ClientIP X-Forwarded-For tests --------- Co-authored-by: Bo-Yi Wu --- context.go | 3 ++- context_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index e1d9be43..c42459ff 100644 --- a/context.go +++ b/context.go @@ -989,7 +989,8 @@ func (c *Context) ClientIP() string { if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil { for _, headerName := range c.engine.RemoteIPHeaders { - ip, valid := c.engine.validateHeader(c.requestHeader(headerName)) + headerValue := strings.Join(c.Request.Header.Values(headerName), ",") + ip, valid := c.engine.validateHeader(headerValue) if valid { return ip } diff --git a/context_test.go b/context_test.go index 016fa86d..3080015c 100644 --- a/context_test.go +++ b/context_test.go @@ -1143,6 +1143,37 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } +func TestContextClientIPWithMultipleHeaders(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/test", nil) + + // Multiple X-Forwarded-For headers + c.Request.Header.Add("X-Forwarded-For", "1.2.3.4, "+localhostIP) + c.Request.Header.Add("X-Forwarded-For", "5.6.7.8") + c.Request.RemoteAddr = localhostIP + ":1234" + + c.engine.ForwardedByClientIP = true + c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"} + _ = c.engine.SetTrustedProxies([]string{localhostIP}) + + // Should return 5.6.7.8 (last non-trusted IP) + assert.Equal(t, "5.6.7.8", c.ClientIP()) +} + +func TestContextClientIPWithSingleHeader(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/test", nil) + c.Request.Header.Set("X-Forwarded-For", "1.2.3.4, "+localhostIP) + c.Request.RemoteAddr = localhostIP + ":1234" + + c.engine.ForwardedByClientIP = true + c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"} + _ = c.engine.SetTrustedProxies([]string{localhostIP}) + + // Should return 1.2.3.4 + assert.Equal(t, "1.2.3.4", c.ClientIP()) +} + // Tests that the response is serialized as Secure JSON // and Content-Type is set to application/json func TestContextRenderSecureJSON(t *testing.T) { From 3ab698dc5110af1977d57226e4995c57dd34c233 Mon Sep 17 00:00:00 2001 From: OHZEKI Naoki <0h23k1.n40k1@gmail.com> Date: Sat, 17 Jan 2026 17:40:43 +0900 Subject: [PATCH 332/332] refactor(recovery): smart error comparison (#4142) * refactor(recovery): rename var in CustomRecoveryWithWriter * refactor(recovery): smart error comparison * test(recovery): Directly reference the syscall error string --- recovery.go | 37 +++++++++++++++---------------------- recovery_test.go | 13 +++++++------ 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/recovery.go b/recovery.go index 6d4b4b2b..bbf1d565 100644 --- a/recovery.go +++ b/recovery.go @@ -12,12 +12,12 @@ import ( "fmt" "io" "log" - "net" "net/http" "net/http/httputil" "os" "runtime" "strings" + "syscall" "time" "github.com/gin-gonic/gin/internal/bytesconv" @@ -57,40 +57,33 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } return func(c *Context) { defer func() { - if err := recover(); err != nil { + if rec := recover(); rec != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. - var brokenPipe bool - if ne, ok := err.(*net.OpError); ok { - var se *os.SyscallError - if errors.As(ne, &se) { - seStr := strings.ToLower(se.Error()) - if strings.Contains(seStr, "broken pipe") || - strings.Contains(seStr, "connection reset by peer") { - brokenPipe = true - } - } - } - if e, ok := err.(error); ok && errors.Is(e, http.ErrAbortHandler) { - brokenPipe = true + var isBrokenPipe bool + err, ok := rec.(error) + if ok { + isBrokenPipe = errors.Is(err, syscall.EPIPE) || + errors.Is(err, syscall.ECONNRESET) || + errors.Is(err, http.ErrAbortHandler) } if logger != nil { - if brokenPipe { - logger.Printf("%s\n%s%s", err, secureRequestDump(c.Request), reset) + if isBrokenPipe { + logger.Printf("%s\n%s%s", rec, secureRequestDump(c.Request), reset) } else if IsDebugging() { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", - timeFormat(time.Now()), secureRequestDump(c.Request), err, stack(stackSkip), reset) + timeFormat(time.Now()), secureRequestDump(c.Request), rec, stack(stackSkip), reset) } else { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", - timeFormat(time.Now()), err, stack(stackSkip), reset) + timeFormat(time.Now()), rec, stack(stackSkip), reset) } } - if brokenPipe { + if isBrokenPipe { // If the connection is dead, we can't write a status to it. - c.Error(err.(error)) //nolint: errcheck + c.Error(err) //nolint: errcheck c.Abort() } else { - handle(c, err) + handle(c, rec) } } }() diff --git a/recovery_test.go b/recovery_test.go index 912ab601..0faa3280 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -98,13 +98,13 @@ func TestFunction(t *testing.T) { func TestPanicWithBrokenPipe(t *testing.T) { const expectCode = 204 - expectMsgs := map[syscall.Errno]string{ - syscall.EPIPE: "broken pipe", - syscall.ECONNRESET: "connection reset by peer", + expectErrnos := []syscall.Errno{ + syscall.EPIPE, + syscall.ECONNRESET, } - for errno, expectMsg := range expectMsgs { - t.Run(expectMsg, func(t *testing.T) { + for _, errno := range expectErrnos { + t.Run("Recovery from "+errno.Error(), func(t *testing.T) { var buf strings.Builder router := New() @@ -122,7 +122,8 @@ func TestPanicWithBrokenPipe(t *testing.T) { w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, expectCode, w.Code) - assert.Contains(t, strings.ToLower(buf.String()), expectMsg) + assert.Contains(t, strings.ToLower(buf.String()), errno.Error()) + assert.NotContains(t, strings.ToLower(buf.String()), "[Recovery]") }) } }