mirror of
https://github.com/gin-gonic/gin.git
synced 2026-06-11 16:20:37 +08:00
Compare commits
5 Commits
a0c8aed380
...
d660a2aa54
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d660a2aa54 | ||
|
|
5260de6a83 | ||
|
|
5f424ff6f6 | ||
|
|
216a4a7c28 | ||
|
|
a7b757e338 |
100
context.go
100
context.go
@ -5,6 +5,7 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -94,6 +95,10 @@ type Context struct {
|
||||
// SameSite allows a server to define a cookie attribute making it impossible for
|
||||
// the browser to send this cookie along with cross-site requests.
|
||||
sameSite http.SameSite
|
||||
|
||||
internalContextMu sync.RWMutex
|
||||
internalContext context.Context
|
||||
internalContextCancelCause context.CancelCauseFunc
|
||||
}
|
||||
|
||||
/************************************/
|
||||
@ -115,6 +120,10 @@ func (c *Context) reset() {
|
||||
c.sameSite = 0
|
||||
*c.params = (*c.params)[:0]
|
||||
*c.skippedNodes = (*c.skippedNodes)[:0]
|
||||
|
||||
if c.useInternalContext() {
|
||||
c.WithInternalContext(context.Background())
|
||||
}
|
||||
}
|
||||
|
||||
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
||||
@ -1429,6 +1438,49 @@ func (c *Context) SetAccepted(formats ...string) {
|
||||
/***** GOLANG.ORG/X/NET/CONTEXT *****/
|
||||
/************************************/
|
||||
|
||||
// WithInternalContext replaces the internal context stored with the provided one in a thread safe manner.
|
||||
// It's important that any context you pass in is not something the wraps *gin.Context,
|
||||
// if you want to wrap a context and then provide it to WithInternalContext, use InternalContext().
|
||||
// If you don't plan to provide the context back to WithInternalContext you can safely use *Context directly.
|
||||
// Otherwise you'll end up with a stack overflow.
|
||||
//
|
||||
// For example:
|
||||
// var c *Context // given a context
|
||||
// // you can safely wrap it and pass it downstream
|
||||
// myDownstreamFunction(context.WithValue(c, ...))
|
||||
//
|
||||
// // but when you want to call WithInternalContext you should do it like this
|
||||
// c.WithInternalContext(context.WithValue(c.InternalContext(), ...))
|
||||
func (c *Context) WithInternalContext(ctx context.Context) {
|
||||
if !c.useInternalContext() {
|
||||
panic("Can't use WithInternalContext when UseInternalContext is false")
|
||||
}
|
||||
|
||||
c.internalContextMu.Lock()
|
||||
defer c.internalContextMu.Unlock()
|
||||
|
||||
c.internalContext, c.internalContextCancelCause = context.WithCancelCause(ctx)
|
||||
}
|
||||
|
||||
// InternalContext provides the currently stored internal context in a thread safe manner.
|
||||
// Use this if you want to wrap a context.Context which you'll end up providing to WithInternalContext.
|
||||
// If you don't plan to provide the context back to WithInternalContext you can safely use *Context directly.
|
||||
func (c *Context) InternalContext() context.Context {
|
||||
if !c.useInternalContext() {
|
||||
panic("Can't use InternalContext when UseInternalContext is false")
|
||||
}
|
||||
|
||||
c.internalContextMu.RLock()
|
||||
defer c.internalContextMu.RUnlock()
|
||||
|
||||
return c.internalContext
|
||||
}
|
||||
|
||||
// hasRequestContext returns whether c.Request has Context and fallback.
|
||||
func (c *Context) useInternalContext() bool {
|
||||
return c.engine != nil && c.engine.UseInternalContext
|
||||
}
|
||||
|
||||
// hasRequestContext returns whether c.Request has Context and fallback.
|
||||
func (c *Context) hasRequestContext() bool {
|
||||
hasFallback := c.engine != nil && c.engine.ContextWithFallback
|
||||
@ -1438,26 +1490,44 @@ func (c *Context) hasRequestContext() bool {
|
||||
|
||||
// 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.hasRequestContext() {
|
||||
return
|
||||
if c.useInternalContext() {
|
||||
c.internalContextMu.RLock()
|
||||
defer c.internalContextMu.RUnlock()
|
||||
|
||||
return c.internalContext.Deadline()
|
||||
} else if c.hasRequestContext() {
|
||||
return c.Request.Context().Deadline()
|
||||
}
|
||||
return c.Request.Context().Deadline()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Done returns nil (chan which will wait forever) when c.Request has no Context.
|
||||
func (c *Context) Done() <-chan struct{} {
|
||||
if !c.hasRequestContext() {
|
||||
return nil
|
||||
if c.useInternalContext() {
|
||||
c.internalContextMu.RLock()
|
||||
defer c.internalContextMu.RUnlock()
|
||||
|
||||
return c.internalContext.Done()
|
||||
} else if c.hasRequestContext() {
|
||||
return c.Request.Context().Done()
|
||||
}
|
||||
return c.Request.Context().Done()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Err returns nil when c.Request has no Context.
|
||||
func (c *Context) Err() error {
|
||||
if !c.hasRequestContext() {
|
||||
return nil
|
||||
if c.useInternalContext() {
|
||||
c.internalContextMu.RLock()
|
||||
defer c.internalContextMu.RUnlock()
|
||||
|
||||
return c.internalContext.Err()
|
||||
} else if c.hasRequestContext() {
|
||||
return c.Request.Context().Err()
|
||||
}
|
||||
return c.Request.Context().Err()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
@ -1475,8 +1545,14 @@ func (c *Context) Value(key any) any {
|
||||
return val
|
||||
}
|
||||
}
|
||||
if !c.hasRequestContext() {
|
||||
return nil
|
||||
if c.useInternalContext() {
|
||||
c.internalContextMu.RLock()
|
||||
defer c.internalContextMu.RUnlock()
|
||||
|
||||
return c.internalContext.Value(key)
|
||||
} else if c.hasRequestContext() {
|
||||
return c.Request.Context().Value(key)
|
||||
}
|
||||
return c.Request.Context().Value(key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
136
context_test.go
136
context_test.go
@ -3239,6 +3239,142 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextUseInternalContextDeadline(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder(), func(c *Context) {
|
||||
// enable UseInternalContext feature flag
|
||||
c.engine.UseInternalContext = true
|
||||
})
|
||||
|
||||
deadline, ok := c.Deadline()
|
||||
assert.Zero(t, deadline)
|
||||
assert.False(t, ok)
|
||||
|
||||
c2, _ := CreateTestContext(httptest.NewRecorder(), func(c *Context) {
|
||||
// enable UseInternalContext feature flag
|
||||
c.engine.UseInternalContext = true
|
||||
})
|
||||
|
||||
d := time.Now().Add(time.Second)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), d)
|
||||
defer cancel()
|
||||
c2.WithInternalContext(ctx)
|
||||
deadline, ok = c2.Deadline()
|
||||
assert.Equal(t, d, deadline)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestContextUseInternalContextDone(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder(), func(c *Context) {
|
||||
// enable UseInternalContext feature flag
|
||||
c.engine.UseInternalContext = true
|
||||
})
|
||||
|
||||
assert.Nil(t, c.Done())
|
||||
|
||||
c2, _ := CreateTestContext(httptest.NewRecorder(), func(c *Context) {
|
||||
// enable UseInternalContext feature flag
|
||||
c.engine.UseInternalContext = true
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c2.WithInternalContext(ctx)
|
||||
cancel()
|
||||
assert.NotNil(t, <-c2.Done())
|
||||
}
|
||||
|
||||
func TestContextUseInternalContextErr(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder(), func(c *Context) {
|
||||
// enable UseInternalContext feature flag
|
||||
c.engine.UseInternalContext = true
|
||||
})
|
||||
|
||||
require.NoError(t, c.Err())
|
||||
|
||||
c2, _ := CreateTestContext(httptest.NewRecorder(), func(c *Context) {
|
||||
// enable UseInternalContext feature flag
|
||||
c.engine.UseInternalContext = true
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c2.WithInternalContext(ctx)
|
||||
cancel()
|
||||
|
||||
assert.EqualError(t, c2.Err(), context.Canceled.Error())
|
||||
}
|
||||
|
||||
func TestContextUseInternalContextValue(t *testing.T) {
|
||||
type contextKey string
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
getContextAndKey func() (*Context, any)
|
||||
value any
|
||||
}{
|
||||
{
|
||||
name: "c with struct context key",
|
||||
getContextAndKey: func() (*Context, any) {
|
||||
type KeyStruct struct{} // https://staticcheck.dev/docs/checks/#SA1029
|
||||
var key KeyStruct
|
||||
c, _ := CreateTestContext(httptest.NewRecorder(), func(c *Context) {
|
||||
// enable UseInternalContext feature flag
|
||||
c.engine.UseInternalContext = true
|
||||
})
|
||||
c.WithInternalContext(context.WithValue(context.TODO(), key, "value"))
|
||||
return c, key
|
||||
},
|
||||
value: "value",
|
||||
},
|
||||
{
|
||||
name: "c with struct context key and request context with different value",
|
||||
getContextAndKey: func() (*Context, any) {
|
||||
type KeyStruct struct{} // https://staticcheck.dev/docs/checks/#SA1029
|
||||
var key KeyStruct
|
||||
c, _ := CreateTestContext(httptest.NewRecorder(), func(c *Context) {
|
||||
// enable UseInternalContext feature flag
|
||||
c.engine.UseInternalContext = true
|
||||
// enable ContextWithFallback feature flag
|
||||
c.engine.ContextWithFallback = true
|
||||
c.Request, _ = http.NewRequest(http.MethodPost, "/", nil)
|
||||
})
|
||||
c.WithInternalContext(context.WithValue(context.TODO(), key, "value"))
|
||||
c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "other value"))
|
||||
return c, key
|
||||
},
|
||||
value: "value",
|
||||
},
|
||||
{
|
||||
name: "c with string context key",
|
||||
getContextAndKey: func() (*Context, any) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder(), func(c *Context) {
|
||||
// enable UseInternalContext feature flag
|
||||
c.engine.UseInternalContext = true
|
||||
})
|
||||
c.WithInternalContext(context.WithValue(context.TODO(), contextKey("key"), "value"))
|
||||
return c, contextKey("key")
|
||||
},
|
||||
value: "value",
|
||||
},
|
||||
{
|
||||
name: "c with background internal context",
|
||||
getContextAndKey: func() (*Context, any) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder(), func(c *Context) {
|
||||
// enable UseInternalContext feature flag
|
||||
c.engine.UseInternalContext = true
|
||||
})
|
||||
c.WithInternalContext(context.Background())
|
||||
return c, "key"
|
||||
},
|
||||
value: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c, key := tt.getContextAndKey()
|
||||
assert.Equal(t, tt.value, c.Value(key))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextCopyShouldNotCancel(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
25
gin.go
25
gin.go
@ -169,9 +169,14 @@ type Engine struct {
|
||||
// UseH2C enable h2c support.
|
||||
UseH2C bool
|
||||
|
||||
// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.
|
||||
// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value()
|
||||
// through Context.Request when Context.Request.Context() is not nil.
|
||||
ContextWithFallback bool
|
||||
|
||||
// UseInternalContext enable fallback Context.Deadline(), Context.Done(), Context.Err()
|
||||
// through InternalContext and supersedes ContextWithFallback
|
||||
UseInternalContext bool
|
||||
|
||||
delims render.Delims
|
||||
secureJSONPrefix string
|
||||
HTMLRender render.HTMLRender
|
||||
@ -669,6 +674,24 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
c.Request = req
|
||||
c.reset()
|
||||
|
||||
// If we're using internalContext then we need to pass on errors from the request context
|
||||
if c.useInternalContext() {
|
||||
reqCtx := req.Context()
|
||||
|
||||
// we need to get the cancelCause function now, so that we have the function for this request
|
||||
// because the c is a pointer to a Context that will possibly go back into the pool and be reused
|
||||
c.internalContextMu.RLock()
|
||||
cancelCause := c.internalContextCancelCause
|
||||
c.internalContextMu.RUnlock()
|
||||
|
||||
go func() {
|
||||
<-reqCtx.Done()
|
||||
if err := reqCtx.Err(); err != nil {
|
||||
cancelCause(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
engine.handleHTTPRequest(c)
|
||||
|
||||
engine.pool.Put(c)
|
||||
|
||||
12
go.mod
12
go.mod
@ -5,7 +5,7 @@ go 1.24.0
|
||||
toolchain go1.24.7
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.14.2
|
||||
github.com/bytedance/sonic v1.15.0
|
||||
github.com/gin-contrib/sse v1.1.0
|
||||
github.com/go-playground/validator/v10 v10.28.0
|
||||
github.com/goccy/go-json v0.10.5
|
||||
@ -18,7 +18,7 @@ require (
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/ugorji/go/codec v1.3.1
|
||||
go.mongodb.org/mongo-driver v1.17.9
|
||||
golang.org/x/net v0.49.0
|
||||
golang.org/x/net v0.50.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
)
|
||||
|
||||
@ -26,7 +26,7 @@ require gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
require (
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.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
|
||||
@ -41,7 +41,7 @@ require (
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
golang.org/x/arch v0.22.0 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
)
|
||||
|
||||
24
go.sum
24
go.sum
@ -1,9 +1,9 @@
|
||||
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/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.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=
|
||||
@ -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.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
@ -16,9 +16,6 @@ import (
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
|
||||
// TODO unit tests
|
||||
// test errors
|
||||
|
||||
func TestRenderMsgPack(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]any{
|
||||
@ -32,13 +29,52 @@ func TestRenderMsgPack(t *testing.T) {
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
h := new(codec.MsgpackHandle)
|
||||
assert.NotNil(t, h)
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
assert.NotNil(t, buf)
|
||||
err = codec.NewEncoder(buf, h).Encode(data)
|
||||
|
||||
var decoded map[string]any
|
||||
var mh codec.MsgpackHandle
|
||||
mh.RawToString = true
|
||||
err = codec.NewDecoderBytes(w.Body.Bytes(), &mh).Decode(&decoded)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, w.Body.String(), buf.String())
|
||||
assert.Equal(t, data, decoded)
|
||||
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestWriteMsgPack(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]any{
|
||||
"foo": "bar",
|
||||
"num": 42,
|
||||
}
|
||||
|
||||
err := WriteMsgPack(w, data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
|
||||
var decoded map[string]any
|
||||
var mh codec.MsgpackHandle
|
||||
mh.RawToString = true
|
||||
err = codec.NewDecoderBytes(w.Body.Bytes(), &mh).Decode(&decoded)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, decoded, 2)
|
||||
assert.Equal(t, "bar", decoded["foo"])
|
||||
assert.EqualValues(t, 42, decoded["num"])
|
||||
}
|
||||
|
||||
type failWriter struct {
|
||||
*httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (w *failWriter) Write(data []byte) (int, error) {
|
||||
return 0, errors.New("write error")
|
||||
}
|
||||
|
||||
func TestRenderMsgPackError(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]any{
|
||||
"foo": "bar",
|
||||
}
|
||||
|
||||
err := (MsgPack{data}).Render(&failWriter{w})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "write error")
|
||||
}
|
||||
|
||||
@ -14,9 +14,12 @@ import (
|
||||
// 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) {
|
||||
func CreateTestContext(w http.ResponseWriter, opts ...func(c *Context)) (c *Context, r *Engine) {
|
||||
r = New()
|
||||
c = r.allocateContext(0)
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
c.reset()
|
||||
c.writermem.reset(w)
|
||||
return
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user