Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
3 tasks done
pjebs opened this issue Mar 10, 2025 · 7 comments · May be fixed by #3382
Open
3 tasks done

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

pjebs opened this issue Mar 10, 2025 · 7 comments · May be fixed by #3382

Comments

@pjebs
Copy link
Contributor

pjebs commented Mar 10, 2025

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:

  • I agree to follow Fiber's Code of Conduct.
  • I have searched for existing issues that describe my proposal before opening this one.
  • I understand that a proposal that does not meet these guidelines may be closed without explanation.
@gaby
Copy link
Member

gaby commented Mar 10, 2025

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

@gaby gaby added this to the v3 milestone Mar 10, 2025
@gaby
Copy link
Member

gaby commented Mar 10, 2025

@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 gaby removed this from the v3 milestone Mar 10, 2025
@pjebs
Copy link
Contributor Author

pjebs commented Mar 11, 2025

  • 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
Copy link
Contributor Author

pjebs commented Mar 19, 2025

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.

@gaby gaby linked a pull request Mar 31, 2025 that will close this issue
10 tasks
@pjebs
Copy link
Contributor Author

pjebs commented Apr 21, 2025

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

@gaby
Copy link
Member

gaby commented Apr 23, 2025

Relevant valyala/fasthttp#1998

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants