From c79f5d466ee780c329663d5b599d40639b33cd93 Mon Sep 17 00:00:00 2001 From: wanghaolong613 Date: Tue, 2 Jun 2026 21:45:58 +0800 Subject: [PATCH 1/5] refactor: optimize error message concatenation in default_validator (#4685) --- binding/default_validator.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/binding/default_validator.go b/binding/default_validator.go index 8203bcaa..f27d437b 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -32,7 +32,10 @@ func (err SliceValidationError) Error() string { if b.Len() > 0 { b.WriteString("\n") } - b.WriteString("[" + strconv.Itoa(i) + "]: " + err[i].Error()) + b.WriteString("[") + b.WriteString(strconv.Itoa(i)) + b.WriteString("]: ") + b.WriteString(err[i].Error()) } } return b.String() From 96ece6a14123923d00081320486042f568adef32 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <73926176+raju-mechatronics@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:48:53 +0600 Subject: [PATCH 2/5] feat(context): add Scheme() with proper reverse proxy support (#4655) * feat(context): add Scheme method to determine HTTP scheme from request * test(context): add tests for Scheme method --- context.go | 27 ++++++++++++++++++++++ context_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/context.go b/context.go index 5174033e..3850667d 100644 --- a/context.go +++ b/context.go @@ -1047,6 +1047,33 @@ func (c *Context) IsWebsocket() bool { return false } +// Scheme returns the HTTP scheme of the request ("http" or "https"). +// When running behind reverse proxies or load balancers `Request.URL.Scheme` is usually empty. +// the original scheme is commonly forwarded via headers such as X-Forwarded-Proto. +// Reference: +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto +func (c *Context) Scheme() string { + if c.Request.TLS != nil { + return "https" + } + if scheme := c.requestHeader("X-Forwarded-Proto"); scheme != "" { + return scheme + } + if scheme := c.requestHeader("X-Forwarded-Protocol"); scheme != "" { + return scheme + } + if ssl := c.requestHeader("X-Forwarded-Ssl"); ssl == "on" { + return "https" + } + if scheme := c.requestHeader("X-Url-Scheme"); scheme != "" { + return scheme + } + if scheme := c.Request.URL.Scheme; scheme != "" { + return scheme + } + return "http" +} + func (c *Context) requestHeader(key string) string { return c.Request.Header.Get(key) } diff --git a/context_test.go b/context_test.go index ef60379d..364a92ae 100644 --- a/context_test.go +++ b/context_test.go @@ -7,6 +7,7 @@ package gin import ( "bytes" "context" + "crypto/tls" "errors" "fmt" "html/template" @@ -2955,6 +2956,65 @@ func TestWebsocketsRequired(t *testing.T) { assert.False(t, c.IsWebsocket()) } +func TestContextScheme(t *testing.T) { + // TLS connection takes highest priority. + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + c.Request.TLS = &tls.ConnectionState{} + assert.Equal(t, "https", c.Scheme()) + + // X-Forwarded-Proto header. + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + c.Request.Header.Set("X-Forwarded-Proto", "https") + assert.Equal(t, "https", c.Scheme()) + + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + c.Request.Header.Set("X-Forwarded-Proto", "http") + assert.Equal(t, "http", c.Scheme()) + + // X-Forwarded-Protocol header. + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + c.Request.Header.Set("X-Forwarded-Protocol", "https") + assert.Equal(t, "https", c.Scheme()) + + // X-Forwarded-Ssl: on header. + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + c.Request.Header.Set("X-Forwarded-Ssl", "on") + assert.Equal(t, "https", c.Scheme()) + + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + c.Request.Header.Set("X-Forwarded-Ssl", "off") + assert.Equal(t, "http", c.Scheme()) + + // X-Url-Scheme header. + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + c.Request.Header.Set("X-Url-Scheme", "https") + assert.Equal(t, "https", c.Scheme()) + + // Request.URL.Scheme fallback. + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "https://example.com/", nil) + assert.Equal(t, "https", c.Scheme()) + + // Default fallback: plain http. + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + assert.Equal(t, "http", c.Scheme()) + + // TLS takes priority over X-Forwarded-Proto. + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + c.Request.TLS = &tls.ConnectionState{} + c.Request.Header.Set("X-Forwarded-Proto", "http") + assert.Equal(t, "https", c.Scheme()) +} + func TestGetRequestHeaderValue(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest(http.MethodGet, "/chat", nil) From 88c42635384d52ccc28bbcc2750cc040e07e2e82 Mon Sep 17 00:00:00 2001 From: Leehainuo Date: Tue, 2 Jun 2026 21:49:36 +0800 Subject: [PATCH 3/5] docs(context): align inline comments in GetPostForm example (#4675) Standardize comment alignment in GetPostForm documentation example to improve readability and maintain consistency with code formatting conventions. --- context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 3850667d..a2e28e5b 100644 --- a/context.go +++ b/context.go @@ -619,8 +619,8 @@ func (c *Context) DefaultPostForm(key, defaultValue string) string { // For example, during a PATCH request to update the user's email: // // email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com" -// email= --> ("", true) := GetPostForm("email") // set email to "" -// --> ("", false) := GetPostForm("email") // do nothing with email +// email= --> ("", true) := GetPostForm("email") // set email to "" +// --> ("", false) := GetPostForm("email") // do nothing with email func (c *Context) GetPostForm(key string) (string, bool) { if values, ok := c.GetPostFormArray(key); ok { return values[0], ok From 8d0468f72897652485933b845253386f9147a8bf Mon Sep 17 00:00:00 2001 From: Tero Saarni Date: Tue, 2 Jun 2026 16:50:57 +0300 Subject: [PATCH 4/5] chore(deps): bump golang.org/x/net to v0.55.0 (#4678) Signed-off-by: Tero Saarni --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index c5481db5..df181253 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.1 go.mongodb.org/mongo-driver/v2 v2.5.0 - golang.org/x/net v0.52.0 + golang.org/x/net v0.55.0 google.golang.org/protobuf v1.36.11 ) @@ -39,7 +39,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.6.0 // indirect golang.org/x/arch v0.25.0 // indirect - golang.org/x/crypto v0.49.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect ) diff --git a/go.sum b/go.sum index a3b5b8a3..f7f9e27b 100644 --- a/go.sum +++ b/go.sum @@ -77,15 +77,15 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE= golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From d75fcd4c9ab260e5225de590f1f0f8c0e0e12d11 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Tue, 2 Jun 2026 07:02:14 -0700 Subject: [PATCH 5/5] fix(response): panic on Hijack/CloseNotify when wrapper unsupported (#4645) * response_writer: don't panic on Hijack/CloseNotify when wrapper unsupported Closes #4638. http.TimeoutHandler's writer doesn't implement http.Hijacker/CloseNotifier; mirror Flush's graceful degradation. * response_writer: keep Written() false when Hijack is unsupported Signed-off-by: Sai Asish Y --------- Signed-off-by: Sai Asish Y Co-authored-by: Bo-Yi Wu --- response_writer.go | 11 +++++++++-- response_writer_test.go | 19 +++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/response_writer.go b/response_writer.go index 9035e6f1..8e2d8b30 100644 --- a/response_writer.go +++ b/response_writer.go @@ -114,15 +114,22 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if w.size > 0 { return nil, nil, errHijackAlreadyWritten } + hijacker, ok := w.ResponseWriter.(http.Hijacker) + if !ok { + return nil, nil, http.ErrNotSupported + } if w.size < 0 { w.size = 0 } - return w.ResponseWriter.(http.Hijacker).Hijack() + return hijacker.Hijack() } // CloseNotify implements the http.CloseNotifier interface. func (w *responseWriter) CloseNotify() <-chan bool { - return w.ResponseWriter.(http.CloseNotifier).CloseNotify() + if cn, ok := w.ResponseWriter.(http.CloseNotifier); ok { + return cn.CloseNotify() + } + return nil } // Flush implements the http.Flusher interface. diff --git a/response_writer_test.go b/response_writer_test.go index dfc1d2c6..83d3fc8b 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -113,15 +113,18 @@ func TestResponseWriterHijack(t *testing.T) { writer.reset(testWriter) w := ResponseWriter(writer) - assert.Panics(t, func() { - _, _, err := w.Hijack() - require.NoError(t, err) - }) - assert.True(t, w.Written()) + // httptest.ResponseRecorder doesn't implement http.Hijacker; return + // http.ErrNotSupported instead of panicking (#4638). On unsupported the + // writer state stays untouched so the handler can still emit a normal + // HTTP response as a fallback. + conn, buf, err := w.Hijack() + assert.Nil(t, conn) + assert.Nil(t, buf) + require.ErrorIs(t, err, http.ErrNotSupported) + assert.False(t, w.Written()) - assert.Panics(t, func() { - w.CloseNotify() - }) + // CloseNotify on a non-CloseNotifier returns nil instead of panicking. + assert.Nil(t, w.CloseNotify()) w.Flush() }