fiber icon indicating copy to clipboard operation
fiber copied to clipboard

📝 [Proposal]: Make fiber.Ctx implement context.Context

Open pjebs opened this issue 10 months ago • 4 comments

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 avatar Mar 10 '25 03:03 pjebs

@pjebs I kind of agree with this. We will discuss it internally.

gaby avatar Mar 10 '25 04:03 gaby

@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.

gaby avatar Mar 10 '25 23:03 gaby

  • 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) <============

pjebs avatar Mar 11 '25 00:03 pjebs

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 avatar Mar 19 '25 01:03 pjebs

@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

gaby avatar Apr 21 '25 12:04 gaby

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).

pjebs avatar Apr 21 '25 14:04 pjebs

Relevant https://github.com/valyala/fasthttp/issues/1998

gaby avatar Apr 23 '25 10:04 gaby

@gaby @pjebs I'd like to have thin in v3, what can we do to resolve and finalize the PR?

sixcolors avatar May 15 '25 14:05 sixcolors

@sixcolors I left a review on the PR, waiting for @pjebs

gaby avatar May 16 '25 13:05 gaby

@gaby @sixcolors should be complete

pjebs avatar May 24 '25 06:05 pjebs