mirror of
https://github.com/gin-gonic/gin.git
synced 2026-06-06 20:18:19 +08:00
Merge branch 'master' into feat/sse-helpers-and-stream-fix
This commit is contained in:
commit
e1331f6efa
@ -32,7 +32,10 @@ func (err SliceValidationError) Error() string {
|
|||||||
if b.Len() > 0 {
|
if b.Len() > 0 {
|
||||||
b.WriteString("\n")
|
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()
|
return b.String()
|
||||||
|
|||||||
31
context.go
31
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:
|
// 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=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
|
||||||
// email= --> ("", true) := GetPostForm("email") // set email to ""
|
// email= --> ("", true) := GetPostForm("email") // set email to ""
|
||||||
// --> ("", false) := GetPostForm("email") // do nothing with email
|
// --> ("", false) := GetPostForm("email") // do nothing with email
|
||||||
func (c *Context) GetPostForm(key string) (string, bool) {
|
func (c *Context) GetPostForm(key string) (string, bool) {
|
||||||
if values, ok := c.GetPostFormArray(key); ok {
|
if values, ok := c.GetPostFormArray(key); ok {
|
||||||
return values[0], ok
|
return values[0], ok
|
||||||
@ -1047,6 +1047,33 @@ func (c *Context) IsWebsocket() bool {
|
|||||||
return false
|
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 {
|
func (c *Context) requestHeader(key string) string {
|
||||||
return c.Request.Header.Get(key)
|
return c.Request.Header.Get(key)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ package gin
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
@ -3024,6 +3025,65 @@ func TestWebsocketsRequired(t *testing.T) {
|
|||||||
assert.False(t, c.IsWebsocket())
|
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) {
|
func TestGetRequestHeaderValue(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request, _ = http.NewRequest(http.MethodGet, "/chat", nil)
|
c.Request, _ = http.NewRequest(http.MethodGet, "/chat", nil)
|
||||||
|
|||||||
8
go.mod
8
go.mod
@ -16,7 +16,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/ugorji/go/codec v1.3.1
|
github.com/ugorji/go/codec v1.3.1
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0
|
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
|
google.golang.org/protobuf v1.36.11
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ require (
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
go.uber.org/mock v0.6.0 // indirect
|
go.uber.org/mock v0.6.0 // indirect
|
||||||
golang.org/x/arch v0.25.0 // indirect
|
golang.org/x/arch v0.25.0 // indirect
|
||||||
golang.org/x/crypto v0.49.0 // indirect
|
golang.org/x/crypto v0.51.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.37.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
16
go.sum
16
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=
|
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 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
|
||||||
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
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.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
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 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@ -114,15 +114,22 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|||||||
if w.size > 0 {
|
if w.size > 0 {
|
||||||
return nil, nil, errHijackAlreadyWritten
|
return nil, nil, errHijackAlreadyWritten
|
||||||
}
|
}
|
||||||
|
hijacker, ok := w.ResponseWriter.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, http.ErrNotSupported
|
||||||
|
}
|
||||||
if w.size < 0 {
|
if w.size < 0 {
|
||||||
w.size = 0
|
w.size = 0
|
||||||
}
|
}
|
||||||
return w.ResponseWriter.(http.Hijacker).Hijack()
|
return hijacker.Hijack()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseNotify implements the http.CloseNotifier interface.
|
// CloseNotify implements the http.CloseNotifier interface.
|
||||||
func (w *responseWriter) CloseNotify() <-chan bool {
|
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.
|
// Flush implements the http.Flusher interface.
|
||||||
|
|||||||
@ -113,15 +113,18 @@ func TestResponseWriterHijack(t *testing.T) {
|
|||||||
writer.reset(testWriter)
|
writer.reset(testWriter)
|
||||||
w := ResponseWriter(writer)
|
w := ResponseWriter(writer)
|
||||||
|
|
||||||
assert.Panics(t, func() {
|
// httptest.ResponseRecorder doesn't implement http.Hijacker; return
|
||||||
_, _, err := w.Hijack()
|
// http.ErrNotSupported instead of panicking (#4638). On unsupported the
|
||||||
require.NoError(t, err)
|
// writer state stays untouched so the handler can still emit a normal
|
||||||
})
|
// HTTP response as a fallback.
|
||||||
assert.True(t, w.Written())
|
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() {
|
// CloseNotify on a non-CloseNotifier returns nil instead of panicking.
|
||||||
w.CloseNotify()
|
assert.Nil(t, w.CloseNotify())
|
||||||
})
|
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user