mirror of https://github.com/gofiber/fiber.git
Merge 0d2286814e
into 36b93818f9
commit
93587d8418
44
ctx.go
44
ctx.go
|
@ -444,6 +444,28 @@ func (c *DefaultCtx) Cookie(cookie *Cookie) {
|
|||
fasthttp.ReleaseCookie(fcookie)
|
||||
}
|
||||
|
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
//
|
||||
// Due to current limitations in how fasthttp works, Deadline operates as a nop.
|
||||
// See: https://github.com/valyala/fasthttp/issues/965#issuecomment-777268945
|
||||
func (*DefaultCtx) Deadline() (time.Time, bool) {
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
// The close of the Done channel may happen asynchronously,
|
||||
// after the cancel function returns.
|
||||
//
|
||||
// Due to current limitations in how fasthttp works, Done operates as a nop.
|
||||
// See: https://github.com/valyala/fasthttp/issues/965#issuecomment-777268945
|
||||
func (*DefaultCtx) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cookies are used for getting a cookie value by key.
|
||||
// Defaults to the empty string "" if the cookie doesn't exist.
|
||||
// If a default value is given, it will return that value if the cookie doesn't exist.
|
||||
|
@ -468,6 +490,18 @@ func (c *DefaultCtx) Download(file string, filename ...string) error {
|
|||
return c.SendFile(file)
|
||||
}
|
||||
|
||||
// If Done is not yet closed, Err returns nil.
|
||||
// If Done is closed, Err returns a non-nil error explaining why:
|
||||
// context.DeadlineExceeded if the context's deadline passed,
|
||||
// or context.Canceled if the context was canceled for some other reason.
|
||||
// After Err returns a non-nil error, successive calls to Err return the same error.
|
||||
//
|
||||
// Due to current limitations in how fasthttp works, Err operates as a nop.
|
||||
// See: https://github.com/valyala/fasthttp/issues/965#issuecomment-777268945
|
||||
func (*DefaultCtx) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Request return the *fasthttp.Request object
|
||||
// This allows you to use all fasthttp request methods
|
||||
// https://godoc.org/github.com/valyala/fasthttp#Request
|
||||
|
@ -1804,6 +1838,16 @@ func (c *DefaultCtx) Vary(fields ...string) {
|
|||
c.Append(HeaderVary, fields...)
|
||||
}
|
||||
|
||||
// Value makes it possible to pass any values under keys scoped to the request
|
||||
// and therefore available to all following routes that match the request.
|
||||
//
|
||||
// All the values are removed from ctx after returning from the top
|
||||
// RequestHandler. Additionally, Close method is called on each value
|
||||
// implementing io.Closer before removing the value from ctx.
|
||||
func (c *DefaultCtx) Value(key any) any {
|
||||
return c.fasthttp.UserValue(key)
|
||||
}
|
||||
|
||||
// Write appends p into response body.
|
||||
func (c *DefaultCtx) Write(p []byte) (int, error) {
|
||||
c.fasthttp.Response.AppendBody(p)
|
||||
|
|
|
@ -4,10 +4,10 @@ package fiber
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"time"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
@ -49,11 +49,6 @@ type Ctx interface {
|
|||
// RequestCtx returns *fasthttp.RequestCtx that carries a deadline
|
||||
// a cancellation signal, and other values across API boundaries.
|
||||
RequestCtx() *fasthttp.RequestCtx
|
||||
// Context returns a context implementation that was set by
|
||||
// user earlier or returns a non-nil, empty context,if it was not set earlier.
|
||||
Context() context.Context
|
||||
// SetContext sets a context implementation by user.
|
||||
SetContext(ctx context.Context)
|
||||
// Cookie sets a cookie by passing a cookie struct.
|
||||
Cookie(cookie *Cookie)
|
||||
// Cookies are used for getting a cookie value by key.
|
||||
|
@ -62,11 +57,53 @@ type Ctx interface {
|
|||
// The returned value is only valid within the handler. Do not store any references.
|
||||
// Make copies or use the Immutable setting to use the value outside the Handler.
|
||||
Cookies(key string, defaultValue ...string) string
|
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
Deadline() (deadline time.Time, ok bool)
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
// The close of the Done channel may happen asynchronously,
|
||||
// after the cancel function returns.
|
||||
//
|
||||
// WithCancel arranges for Done to be closed when cancel is called;
|
||||
// WithDeadline arranges for Done to be closed when the deadline
|
||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||
// elapses.
|
||||
//
|
||||
// Done is provided for use in select statements:
|
||||
//
|
||||
// // Stream generates values with DoSomething and sends them to out
|
||||
// // until DoSomething returns an error or ctx.Done is closed.
|
||||
// func Stream(ctx context.Context, out chan<- Value) error {
|
||||
// for {
|
||||
// v, err := DoSomething(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// return ctx.Err()
|
||||
// case out <- v:
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// See https://blog.golang.org/pipelines for more examples of how to use
|
||||
// a Done channel for cancellation.
|
||||
Done() <-chan struct{}
|
||||
// Download transfers the file from path as an attachment.
|
||||
// Typically, browsers will prompt the user for download.
|
||||
// By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog).
|
||||
// Override this default with the filename parameter.
|
||||
Download(file string, filename ...string) error
|
||||
// If Done is not yet closed, Err returns nil.
|
||||
// If Done is closed, Err returns a non-nil error explaining why:
|
||||
// DeadlineExceeded if the context's deadline passed,
|
||||
// or Canceled if the context was canceled for some other reason.
|
||||
// After Err returns a non-nil error, successive calls to Err return the same error.
|
||||
Err() error
|
||||
// Request return the *fasthttp.Request object
|
||||
// This allows you to use all fasthttp request methods
|
||||
// https://godoc.org/github.com/valyala/fasthttp#Request
|
||||
|
@ -317,6 +354,13 @@ type Ctx interface {
|
|||
// Vary adds the given header field to the Vary response header.
|
||||
// This will append the header, if not already listed, otherwise leaves it listed in the current location.
|
||||
Vary(fields ...string)
|
||||
// Value makes it possible to pass any values under keys scoped to the request
|
||||
// and therefore available to all following routes that match the request.
|
||||
//
|
||||
// All the values are removed from ctx after returning from the top
|
||||
// RequestHandler. Additionally, Close method is called on each value
|
||||
// implementing io.Closer before removing the value from ctx.
|
||||
Value(key any) any
|
||||
// Write appends p into response body.
|
||||
Write(p []byte) (int, error)
|
||||
// Writef appends f & a into response body writer.
|
||||
|
|
138
ctx_test.go
138
ctx_test.go
|
@ -9,7 +9,6 @@ import (
|
|||
"bytes"
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"embed"
|
||||
"encoding/hex"
|
||||
|
@ -882,76 +881,6 @@ func Test_Ctx_RequestCtx(t *testing.T) {
|
|||
require.Equal(t, "*fasthttp.RequestCtx", fmt.Sprintf("%T", c.RequestCtx()))
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_Context
|
||||
func Test_Ctx_Context(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
|
||||
t.Run("Nil_Context", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := c.Context()
|
||||
require.Equal(t, ctx, context.Background())
|
||||
})
|
||||
t.Run("ValueContext", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testKey := struct{}{}
|
||||
testValue := "Test Value"
|
||||
ctx := context.WithValue(context.Background(), testKey, testValue) //nolint:staticcheck // not needed for tests
|
||||
require.Equal(t, testValue, ctx.Value(testKey))
|
||||
})
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_SetContext
|
||||
func Test_Ctx_SetContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
|
||||
testKey := struct{}{}
|
||||
testValue := "Test Value"
|
||||
ctx := context.WithValue(context.Background(), testKey, testValue) //nolint:staticcheck // not needed for tests
|
||||
c.SetContext(ctx)
|
||||
require.Equal(t, testValue, c.Context().Value(testKey))
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_Context_Multiple_Requests
|
||||
func Test_Ctx_Context_Multiple_Requests(t *testing.T) {
|
||||
t.Parallel()
|
||||
testKey := struct{}{}
|
||||
testValue := "foobar-value"
|
||||
|
||||
app := New()
|
||||
app.Get("/", func(c Ctx) error {
|
||||
ctx := c.Context()
|
||||
|
||||
if ctx.Value(testKey) != nil {
|
||||
return c.SendStatus(StatusInternalServerError)
|
||||
}
|
||||
|
||||
input := utils.CopyString(Query(c, "input", "NO_VALUE"))
|
||||
ctx = context.WithValue(ctx, testKey, fmt.Sprintf("%s_%s", testValue, input)) //nolint:staticcheck // not needed for tests
|
||||
c.SetContext(ctx)
|
||||
|
||||
return c.Status(StatusOK).SendString(fmt.Sprintf("resp_%s_returned", input))
|
||||
})
|
||||
|
||||
// Consecutive Requests
|
||||
for i := 1; i <= 10; i++ {
|
||||
t.Run(fmt.Sprintf("request_%d", i), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, fmt.Sprintf("/?input=%d", i), nil))
|
||||
|
||||
require.NoError(t, err, "Unexpected error from response")
|
||||
require.Equal(t, StatusOK, resp.StatusCode, "context.Context returned from c.Context() is reused")
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "Unexpected error from reading response body")
|
||||
require.Equal(t, fmt.Sprintf("resp_%d_returned", i), string(b), "response text incorrect")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_Cookie
|
||||
func Test_Ctx_Cookie(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
@ -2258,6 +2187,73 @@ func Test_Ctx_Locals(t *testing.T) {
|
|||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_Deadline
|
||||
func Test_Ctx_Deadline(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
app.Use(func(c Ctx) error {
|
||||
return c.Next()
|
||||
})
|
||||
app.Get("/test", func(c Ctx) error {
|
||||
deadline, ok := c.Deadline()
|
||||
require.Equal(t, time.Time{}, deadline)
|
||||
require.Equal(t, require.False, ok)
|
||||
return nil
|
||||
})
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_Done
|
||||
func Test_Ctx_Done(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
app.Use(func(c Ctx) error {
|
||||
return c.Next()
|
||||
})
|
||||
app.Get("/test", func(c Ctx) error {
|
||||
require.Equal(t, (<-chan struct{})(nil), c.Done())
|
||||
return nil
|
||||
})
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_Err
|
||||
func Test_Ctx_Err(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
app.Use(func(c Ctx) error {
|
||||
return c.Next()
|
||||
})
|
||||
app.Get("/test", func(c Ctx) error {
|
||||
require.Equal(t, require.NoError, c.Err())
|
||||
return nil
|
||||
})
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_Value
|
||||
func Test_Ctx_Value(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
app.Use(func(c Ctx) error {
|
||||
c.Locals("john", "doe")
|
||||
return c.Next()
|
||||
})
|
||||
app.Get("/test", func(c Ctx) error {
|
||||
require.Equal(t, "doe", c.Value("john"))
|
||||
return nil
|
||||
})
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_Locals_Generic
|
||||
func Test_Ctx_Locals_Generic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
|
|
@ -62,8 +62,6 @@ func New(config ...Config) fiber.Handler {
|
|||
if err == nil && valid {
|
||||
// Store in both Locals and Context
|
||||
c.Locals(tokenKey, key)
|
||||
ctx := context.WithValue(c.Context(), tokenKey, key)
|
||||
c.SetContext(ctx)
|
||||
return cfg.SuccessHandler(c)
|
||||
}
|
||||
return cfg.ErrorHandler(c, err)
|
||||
|
@ -74,14 +72,14 @@ func New(config ...Config) fiber.Handler {
|
|||
// returns an empty string if the token does not exist
|
||||
func TokenFromContext(c any) string {
|
||||
switch ctx := c.(type) {
|
||||
case context.Context:
|
||||
if token, ok := ctx.Value(tokenKey).(string); ok {
|
||||
return token
|
||||
}
|
||||
case fiber.Ctx:
|
||||
if token, ok := ctx.Locals(tokenKey).(string); ok {
|
||||
return token
|
||||
}
|
||||
case context.Context:
|
||||
if token, ok := ctx.Value(tokenKey).(string); ok {
|
||||
return token
|
||||
}
|
||||
default:
|
||||
panic("unsupported context type, expected fiber.Ctx or context.Context")
|
||||
}
|
||||
|
|
|
@ -544,8 +544,7 @@ func Test_TokenFromContext(t *testing.T) {
|
|||
}))
|
||||
// Verify that TokenFromContext works with context.Context
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
ctx := c.Context()
|
||||
token := TokenFromContext(ctx)
|
||||
token := TokenFromContext(c)
|
||||
return c.SendString(token)
|
||||
})
|
||||
|
||||
|
|
|
@ -39,10 +39,6 @@ func New(config ...Config) fiber.Handler {
|
|||
// Add the request ID to locals
|
||||
c.Locals(requestIDKey, rid)
|
||||
|
||||
// Add the request ID to UserContext
|
||||
ctx := context.WithValue(c.Context(), requestIDKey, rid)
|
||||
c.SetContext(ctx)
|
||||
|
||||
// Continue stack
|
||||
return c.Next()
|
||||
}
|
||||
|
|
|
@ -73,14 +73,6 @@ func Test_RequestID_FromContext(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "From context.Context",
|
||||
args: args{
|
||||
inputFunc: func(c fiber.Ctx) any {
|
||||
return c.Context()
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -19,12 +19,9 @@ func New(h fiber.Handler, timeout time.Duration, tErrs ...error) fiber.Handler {
|
|||
|
||||
// Create a context with the specified timeout; any operation exceeding
|
||||
// this deadline will be canceled automatically.
|
||||
timeoutContext, cancel := context.WithTimeout(ctx.Context(), timeout)
|
||||
timeoutContext, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
// Replace the default Fiber context with our timeout-bound context.
|
||||
ctx.SetContext(timeoutContext)
|
||||
|
||||
// Run the handler and check for relevant errors.
|
||||
err := runHandler(ctx, h, tErrs)
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ func TestTimeout_Success(t *testing.T) {
|
|||
// Our middleware wraps a handler that sleeps for 10ms, well under the 50ms limit.
|
||||
app.Get("/fast", New(func(c fiber.Ctx) error {
|
||||
// Simulate some work
|
||||
if err := sleepWithContext(c.Context(), 10*time.Millisecond, context.DeadlineExceeded); err != nil {
|
||||
if err := sleepWithContext(c, 10*time.Millisecond, context.DeadlineExceeded); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SendString("OK")
|
||||
|
@ -60,7 +60,7 @@ func TestTimeout_Exceeded(t *testing.T) {
|
|||
|
||||
// This handler sleeps 200ms, exceeding the 100ms limit.
|
||||
app.Get("/slow", New(func(c fiber.Ctx) error {
|
||||
if err := sleepWithContext(c.Context(), 200*time.Millisecond, context.DeadlineExceeded); err != nil {
|
||||
if err := sleepWithContext(c, 200*time.Millisecond, context.DeadlineExceeded); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SendString("Should never get here")
|
||||
|
@ -81,7 +81,7 @@ func TestTimeout_CustomError(t *testing.T) {
|
|||
app.Get("/custom", New(func(c fiber.Ctx) error {
|
||||
// Sleep might time out, or might return early. If the context is canceled,
|
||||
// we treat errCustomTimeout as a 'timeout-like' condition.
|
||||
if err := sleepWithContext(c.Context(), 200*time.Millisecond, errCustomTimeout); err != nil {
|
||||
if err := sleepWithContext(c, 200*time.Millisecond, errCustomTimeout); err != nil {
|
||||
return fmt.Errorf("wrapped: %w", err)
|
||||
}
|
||||
return c.SendString("Should never get here")
|
||||
|
|
Loading…
Reference in New Issue