mirror of https://github.com/gofiber/fiber.git
Ctx implements context.Context
parent
e90fe8afbc
commit
fea24d46f7
44
ctx.go
44
ctx.go
|
@ -444,6 +444,28 @@ func (c *DefaultCtx) Cookie(cookie *Cookie) {
|
||||||
fasthttp.ReleaseCookie(fcookie)
|
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 (c *DefaultCtx) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (c *DefaultCtx) Done() <-chan struct{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Cookies are used for getting a cookie value by key.
|
// Cookies are used for getting a cookie value by key.
|
||||||
// Defaults to the empty string "" if the cookie doesn't exist.
|
// 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.
|
// 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)
|
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 (c *DefaultCtx) Err() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Request return the *fasthttp.Request object
|
// Request return the *fasthttp.Request object
|
||||||
// This allows you to use all fasthttp request methods
|
// This allows you to use all fasthttp request methods
|
||||||
// https://godoc.org/github.com/valyala/fasthttp#Request
|
// https://godoc.org/github.com/valyala/fasthttp#Request
|
||||||
|
@ -1804,6 +1838,16 @@ func (c *DefaultCtx) Vary(fields ...string) {
|
||||||
c.Append(HeaderVary, fields...)
|
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.
|
// Write appends p into response body.
|
||||||
func (c *DefaultCtx) Write(p []byte) (int, error) {
|
func (c *DefaultCtx) Write(p []byte) (int, error) {
|
||||||
c.fasthttp.Response.AppendBody(p)
|
c.fasthttp.Response.AppendBody(p)
|
||||||
|
|
|
@ -4,10 +4,10 @@ package fiber
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
)
|
)
|
||||||
|
@ -49,11 +49,6 @@ type Ctx interface {
|
||||||
// RequestCtx returns *fasthttp.RequestCtx that carries a deadline
|
// RequestCtx returns *fasthttp.RequestCtx that carries a deadline
|
||||||
// a cancellation signal, and other values across API boundaries.
|
// a cancellation signal, and other values across API boundaries.
|
||||||
RequestCtx() *fasthttp.RequestCtx
|
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 sets a cookie by passing a cookie struct.
|
||||||
Cookie(cookie *Cookie)
|
Cookie(cookie *Cookie)
|
||||||
// Cookies are used for getting a cookie value by key.
|
// 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.
|
// 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.
|
// Make copies or use the Immutable setting to use the value outside the Handler.
|
||||||
Cookies(key string, defaultValue ...string) string
|
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.
|
// Download transfers the file from path as an attachment.
|
||||||
// Typically, browsers will prompt the user for download.
|
// 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).
|
// 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.
|
// Override this default with the filename parameter.
|
||||||
Download(file string, filename ...string) error
|
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
|
// Request return the *fasthttp.Request object
|
||||||
// This allows you to use all fasthttp request methods
|
// This allows you to use all fasthttp request methods
|
||||||
// https://godoc.org/github.com/valyala/fasthttp#Request
|
// 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.
|
// 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.
|
// This will append the header, if not already listed, otherwise leaves it listed in the current location.
|
||||||
Vary(fields ...string)
|
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 appends p into response body.
|
||||||
Write(p []byte) (int, error)
|
Write(p []byte) (int, error)
|
||||||
// Writef appends f & a into response body writer.
|
// Writef appends f & a into response body writer.
|
||||||
|
|
71
ctx_test.go
71
ctx_test.go
|
@ -9,7 +9,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
@ -882,76 +881,6 @@ func Test_Ctx_RequestCtx(t *testing.T) {
|
||||||
require.Equal(t, "*fasthttp.RequestCtx", fmt.Sprintf("%T", c.RequestCtx()))
|
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
|
// go test -run Test_Ctx_Cookie
|
||||||
func Test_Ctx_Cookie(t *testing.T) {
|
func Test_Ctx_Cookie(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
|
@ -62,8 +62,6 @@ func New(config ...Config) fiber.Handler {
|
||||||
if err == nil && valid {
|
if err == nil && valid {
|
||||||
// Store in both Locals and Context
|
// Store in both Locals and Context
|
||||||
c.Locals(tokenKey, key)
|
c.Locals(tokenKey, key)
|
||||||
ctx := context.WithValue(c.Context(), tokenKey, key)
|
|
||||||
c.SetContext(ctx)
|
|
||||||
return cfg.SuccessHandler(c)
|
return cfg.SuccessHandler(c)
|
||||||
}
|
}
|
||||||
return cfg.ErrorHandler(c, err)
|
return cfg.ErrorHandler(c, err)
|
||||||
|
|
|
@ -544,8 +544,7 @@ func Test_TokenFromContext(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
// Verify that TokenFromContext works with context.Context
|
// Verify that TokenFromContext works with context.Context
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
ctx := c.Context()
|
token := TokenFromContext(c)
|
||||||
token := TokenFromContext(ctx)
|
|
||||||
return c.SendString(token)
|
return c.SendString(token)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -39,10 +39,6 @@ func New(config ...Config) fiber.Handler {
|
||||||
// Add the request ID to locals
|
// Add the request ID to locals
|
||||||
c.Locals(requestIDKey, rid)
|
c.Locals(requestIDKey, rid)
|
||||||
|
|
||||||
// Add the request ID to UserContext
|
|
||||||
ctx := context.WithValue(c.Context(), requestIDKey, rid)
|
|
||||||
c.SetContext(ctx)
|
|
||||||
|
|
||||||
// Continue stack
|
// Continue stack
|
||||||
return c.Next()
|
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 {
|
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
|
// Create a context with the specified timeout; any operation exceeding
|
||||||
// this deadline will be canceled automatically.
|
// this deadline will be canceled automatically.
|
||||||
timeoutContext, cancel := context.WithTimeout(ctx.Context(), timeout)
|
timeoutContext, cancel := context.WithTimeout(ctx, timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Replace the default Fiber context with our timeout-bound context.
|
|
||||||
ctx.SetContext(timeoutContext)
|
|
||||||
|
|
||||||
// Run the handler and check for relevant errors.
|
// Run the handler and check for relevant errors.
|
||||||
err := runHandler(ctx, h, tErrs)
|
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.
|
// Our middleware wraps a handler that sleeps for 10ms, well under the 50ms limit.
|
||||||
app.Get("/fast", New(func(c fiber.Ctx) error {
|
app.Get("/fast", New(func(c fiber.Ctx) error {
|
||||||
// Simulate some work
|
// 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 err
|
||||||
}
|
}
|
||||||
return c.SendString("OK")
|
return c.SendString("OK")
|
||||||
|
@ -60,7 +60,7 @@ func TestTimeout_Exceeded(t *testing.T) {
|
||||||
|
|
||||||
// This handler sleeps 200ms, exceeding the 100ms limit.
|
// This handler sleeps 200ms, exceeding the 100ms limit.
|
||||||
app.Get("/slow", New(func(c fiber.Ctx) error {
|
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 err
|
||||||
}
|
}
|
||||||
return c.SendString("Should never get here")
|
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 {
|
app.Get("/custom", New(func(c fiber.Ctx) error {
|
||||||
// Sleep might time out, or might return early. If the context is canceled,
|
// Sleep might time out, or might return early. If the context is canceled,
|
||||||
// we treat errCustomTimeout as a 'timeout-like' condition.
|
// 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 fmt.Errorf("wrapped: %w", err)
|
||||||
}
|
}
|
||||||
return c.SendString("Should never get here")
|
return c.SendString("Should never get here")
|
||||||
|
|
Loading…
Reference in New Issue