📝 [Proposal]: Make fiber.Ctx implement context.Context
Feature Proposal Description
Having too many different types of contexts is confusing - especially for new people transitioning from other languages to Go. If it's going to be simplified, now is the time to do it.
Why not just make fiber.Ctx implement context.Context?
Currently, fiber.Ctx.Context() context.Context and fiber.Ctx.SetContext(ctx context.Context) do nothing but store a user's custom context.Context into fasthttp's uservalues.
Originally the signature of fasthttp's uservalues only accepted strings. I changed it to anything: https://github.com/valyala/fasthttp/pull/1387/files but the purpose wasn't to dump a custom context.Context into it at the expense of added confusion of another type of context.
If the user wants to pass custom values around from middleware to middleware etc, they can directly store it in fasthttp's request's user values OR even better, we make fiber.Ctx implement context.Context and allow them to store it in there.
type context.Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any)any
}
type Ctx interface {
Context() context.Context <============
SetContext(ctx context.Context) <============
Accepts(offers ...string) string
AcceptsCharsets(offers ...string) string
AcceptsEncodings(offers ...string) string
AcceptsLanguages(offers ...string) string
App() *App
Append(field string, values ...string)
Attachment(filename ...string)
BaseURL() string
BodyRaw() []byte
Body() []byte
ClearCookie(key ...string)
RequestCtx() *fasthttp.RequestCtx
Cookie(cookie *Cookie)
Cookies(key string, defaultValue ...string) string
Download(file string, filename ...string) error
Request() *fasthttp.Request
Response() *fasthttp.Response
Format(handlers ...ResFmt) error
AutoFormat(body any) error
FormFile(key string) (*multipart.FileHeader, error)
FormValue(key string, defaultValue ...string) string
Fresh() bool
Get(key string, defaultValue ...string) string
GetRespHeader(key string, defaultValue ...string) string
GetRespHeaders() map[string][]string
GetReqHeaders() map[string][]string
Host() string
Hostname() string
Port() string
IP() string
IPs() []string
Is(extension string) bool
JSON(data any, ctype ...string) error
CBOR(data any, ctype ...string) error
JSONP(data any, callback ...string) error
XML(data any) error
Links(link ...string)
Locals(key any, value ...any) any
Location(path string)
Method(override ...string) string
MultipartForm() (*multipart.Form, error)
ClientHelloInfo() *tls.ClientHelloInfo
Next() error
RestartRouting() error
OriginalURL() string
Params(key string, defaultValue ...string) string
Path(override ...string) string
Scheme() string
Protocol() string
Query(key string, defaultValue ...string) string
Queries() map[string]string
Range(size int) (Range, error)
Redirect() *Redirect
ViewBind(vars Map) error
GetRouteURL(routeName string, params Map) (string, error)
Render(name string, bind Map, layouts ...string) error
Route() *Route
SaveFile(fileheader *multipart.FileHeader, path string) error
SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error
Secure() bool
Send(body []byte) error
SendFile(file string, config ...SendFile) error
SendStatus(status int) error
SendString(body string) error
SendStream(stream io.Reader, size ...int) error
SendStreamWriter(streamWriter func(*bufio.Writer)) error
Set(key, val string)
Subdomains(offset ...int) []string
Stale() bool
Status(status int) Ctx
String() string
Type(extension string, charset ...string) Ctx
Vary(fields ...string)
Write(p []byte) (int, error)
Writef(f string, a ...any) (int, error)
WriteString(s string) (int, error)
XHR() bool
IsProxyTrusted() bool
IsFromLocal() bool
Bind() *Bind
Reset(fctx *fasthttp.RequestCtx)
Drop() error
}
Alignment with Express API
N/A
HTTP RFC Standards Compliance
N/A
API Stability
Not backward compatible with v2
Feature Examples
N/A
Checklist:
- [x] I agree to follow Fiber's Code of Conduct.
- [x] I have searched for existing issues that describe my proposal before opening this one.
- [x] I understand that a proposal that does not meet these guidelines may be closed without explanation.
@pjebs I kind of agree with this. We will discuss it internally.
@pjebs Although it is tempting to unify them, implementing the full context.Context interface on fiber.Ctx has subtle pitfalls:
- It would misrepresent or sabotage the built-in concurrency & lifecycles of Fiber’s context. Each fiber.Ctx only lives during the duration of the handler.
- It would likely break your code if you rely on “real” context cancellation across goroutines. This is because after the handler the fiber.Ctx is reset and store back in the pool for re-use.
- It introduces overhead that goes against Fiber’s zero-allocation, high-performance design. This is because we would have to create the context and channel for each
fiber.Ctx.
- It would misrepresent or sabotage the built-in concurrency & lifecycles of Fiber’s context. Each fiber.Ctx only lives during the duration of the handler.
That's how any context should be in a Request-Response cycle.
- It would likely break your code if you rely on “real” context cancellation across goroutines. This is because after the handler the fiber.Ctx is reset and store back in the pool for re-use.
Currently that is already documented: https://docs.gofiber.io/#zero-allocation and already applies anyway with the current Ctx.
Because fiber is optimized for high-performance, values returned from fiber.Ctx are not immutable by default and will be re-used across requests. As a rule of thumb, you must only use context values within the handler, and you must not keep any references. As soon as you return from the handler, any values you have obtained from the context will be re-used in future requests and will change below your feet. Here is an example:
Currently if someone starts a goroutine that is expected to live longer than the request-response cycle, they are already expected to abide by the rules of using Fiber.
If they want to use a goroutine that outlives the Fiber request-response cycle, they should create a context.Context and pass that to the goroutine.
- It introduces overhead that goes against Fiber’s zero-allocation, high-performance design. This is because we would have to create the context and channel for each
fiber.Ctx.
Yes this is true. But it can be created only once and never closed. It can be reused and documented what the rules are of using it.
Most likely, people will use it to store values anyway. What is the purpose of anyway:
type fiber.Ctx interface {
Context() context.Context <============
SetContext(ctx context.Context) <============
type context.Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any)any
}
Here is an example of how to simply implement Deadline, Done and Err: https://www.youtube.com/watch?v=8M90t0KvEDY and https://www.youtube.com/watch?v=LSzR0VEraWw by @campoy who use to be in the Go Team: https://github.com/campoy/justforfunc/blob/master/10-contextimpl/context.go
Value can simply place values directly in to fasthttp's uservalues like Locals does right now.
@pjebs The fasthttp context already implements the functions from #3382
See:
- Deadline() https://github.com/valyala/fasthttp/blob/master/server.go#L2825
- Done() https://github.com/valyala/fasthttp/blob/master/server.go#L2835
- Err() https://github.com/valyala/fasthttp/blob/master/server.go#L2848
- Value() https://github.com/valyala/fasthttp/blob/master/server.go#L2863
Yes, but the aim is for fiber.Ctx to implement context.Context. They recognised the importance of making fasthttp.RequestCtx implement it. They just do a nop implementation for Deadline, Done and Err. (Technically if the server shuts down, then they close the channel as a special case, but not if the client closes the connection which is what net/http does and is the ideal case - they believe it will effect performance).
Relevant https://github.com/valyala/fasthttp/issues/1998
@gaby @pjebs I'd like to have thin in v3, what can we do to resolve and finalize the PR?
@sixcolors I left a review on the PR, waiting for @pjebs
@gaby @sixcolors should be complete