Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
52d06a3
🐛 bug: balance store session releases on terminal branches
gaby Feb 7, 2026
1d5889f
🐛 bug: clear session middleware locals before pooling
gaby Feb 7, 2026
ea9385c
🐛 bug: honor GetByID context in session destroy
gaby Feb 7, 2026
fe63faf
🐛 bug: avoid returning init error after body write
gaby Feb 7, 2026
bb28e61
🐛 bug: make init-error cleanup assertion deterministic
gaby Feb 23, 2026
8f92fa7
Merge branch 'main' into update-session-middleware-to-fail-gracefully
gaby Feb 24, 2026
36c284f
Refactor session middleware context handling
gaby Feb 24, 2026
a5953ea
Fix session middleware return statement
gaby Feb 24, 2026
c3be3ba
Initial plan
Copilot Feb 24, 2026
a7fdf2c
🐛 bug: validate session IDs, use StoreInContext for cleanup, remove p…
Copilot Feb 24, 2026
93fdf46
Merge pull request #4107 from gofiber/copilot/sub-pr-4059
gaby Feb 24, 2026
097e06f
Merge branch 'main' into update-session-middleware-to-fail-gracefully
gaby Feb 24, 2026
c972b14
Merge branch 'main' into update-session-middleware-to-fail-gracefully
gaby Mar 21, 2026
0fbad94
🐛 bug: use context helpers for session lookup and IDs
Copilot May 9, 2026
a0e4a26
🐛 bug: prefer extracted session ids over cached request values
Copilot May 9, 2026
19d91e3
🐛 bug: keep session id caching scoped to the active request
Copilot May 9, 2026
f857a52
Merge branch 'main' into update-session-middleware-to-fail-gracefully
Copilot May 9, 2026
eb43a5c
🔒 security: replace panic with graceful config adjustment in session …
Claude May 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,8 @@ func Test_Client_Cookie_With_Server(t *testing.T) {

handler := func(c fiber.Ctx) error {
return c.SendString(
c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3") + c.Cookies("k4"))
c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3") + c.Cookies("k4"),
)
}

wrapAgent := func(c *Client) {
Expand All @@ -1464,7 +1465,8 @@ func Test_Client_Cookie_With_Server(t *testing.T) {
func Test_Client_CookieJar(t *testing.T) {
handler := func(c fiber.Ctx) error {
return c.SendString(
c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3"))
c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3"),
)
}

jar := AcquireCookieJar()
Expand All @@ -1491,7 +1493,8 @@ func Test_Client_CookieJar_Response(t *testing.T) {
Value: "v4",
})
return c.SendString(
c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3"))
c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3"),
)
}

jar := AcquireCookieJar()
Expand All @@ -1518,7 +1521,8 @@ func Test_Client_CookieJar_Response(t *testing.T) {
Expires: time.Now().Add(1 * time.Nanosecond),
})
return c.SendString(
c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3"))
c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3"),
)
}

jar := AcquireCookieJar()
Expand All @@ -1544,7 +1548,8 @@ func Test_Client_CookieJar_Response(t *testing.T) {
Value: "v2",
})
return c.SendString(
c.Cookies("k1") + c.Cookies("k2"))
c.Cookies("k1") + c.Cookies("k2"),
)
}

jar := AcquireCookieJar()
Expand Down
21 changes: 14 additions & 7 deletions client/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,8 @@ func Test_Request_Cookie_With_Server(t *testing.T) {
t.Parallel()
handler := func(c fiber.Ctx) error {
return c.SendString(
c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3") + c.Cookies("k4"))
c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3") + c.Cookies("k4"),
)
}

wrapAgent := func(req *Request) {
Expand Down Expand Up @@ -1187,7 +1188,8 @@ func Test_Request_Body_With_Server(t *testing.T) {

t.Run("json body", func(t *testing.T) {
t.Parallel()
testRequest(t,
testRequest(
t,
func(c fiber.Ctx) error {
require.Equal(t, "application/json", string(c.Request().Header.ContentType()))
return c.SendString(string(c.Request().Body()))
Expand All @@ -1203,7 +1205,8 @@ func Test_Request_Body_With_Server(t *testing.T) {

t.Run("xml body", func(t *testing.T) {
t.Parallel()
testRequest(t,
testRequest(
t,
func(c fiber.Ctx) error {
require.Equal(t, "application/xml", string(c.Request().Header.ContentType()))
return c.SendString(string(c.Request().Body()))
Expand All @@ -1222,7 +1225,8 @@ func Test_Request_Body_With_Server(t *testing.T) {

t.Run("cbor body", func(t *testing.T) {
t.Parallel()
testRequest(t,
testRequest(
t,
func(c fiber.Ctx) error {
require.Equal(t, "application/cbor", string(c.Request().Header.ContentType()))
return c.SendString(string(c.Request().Body()))
Expand Down Expand Up @@ -1382,7 +1386,8 @@ func Test_Request_Body_With_Server(t *testing.T) {

t.Run("raw body", func(t *testing.T) {
t.Parallel()
testRequest(t,
testRequest(
t,
func(c fiber.Ctx) error {
return c.SendString(string(c.Request().Body()))
},
Expand Down Expand Up @@ -1458,7 +1463,8 @@ func Test_Request_Error_Body_With_Server(t *testing.T) {
t.Parallel()
t.Run("json error", func(t *testing.T) {
t.Parallel()
testRequestFail(t,
testRequestFail(
t,
func(c fiber.Ctx) error {
return c.SendString("")
},
Expand All @@ -1471,7 +1477,8 @@ func Test_Request_Error_Body_With_Server(t *testing.T) {

t.Run("xml error", func(t *testing.T) {
t.Parallel()
testRequestFail(t,
testRequestFail(
t,
func(c fiber.Ctx) error {
return c.SendString("")
},
Expand Down
18 changes: 12 additions & 6 deletions ctx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1593,7 +1593,8 @@ func Test_Ctx_Cookie_DefaultPath(t *testing.T) {
}

c.Res().Cookie(ck)
require.Equal(t,
require.Equal(
t,
"p=v; path=/; SameSite=Lax",
c.Res().Get(HeaderSetCookie),
)
Expand All @@ -1612,7 +1613,8 @@ func Test_Ctx_Cookie_MaxAgeOnly(t *testing.T) {
}
c.Res().Cookie(ck)

require.Equal(t,
require.Equal(
t,
"ttl=v; max-age=3600; path=/; SameSite=Lax",
c.Res().Get(HeaderSetCookie),
)
Expand All @@ -1633,7 +1635,8 @@ func Test_Ctx_Cookie_StrictPartitioned(t *testing.T) {
}
c.Res().Cookie(ck)

require.Equal(t,
require.Equal(
t,
"sp=v; path=/; secure; SameSite=Strict; Partitioned",
c.Res().Get(HeaderSetCookie),
)
Expand Down Expand Up @@ -2122,7 +2125,8 @@ func Test_Ctx_AutoFormat_Struct(t *testing.T) {
err := c.AutoFormat(data)
require.NoError(t, err)
require.Equal(t, MIMEApplicationJSONCharsetUTF8, c.GetRespHeader(HeaderContentType)) //nolint:testifylint // this is comparing content-type headers, not JSON content
require.JSONEq(t,
require.JSONEq(
t,
`{"Sender":"Carol","Recipients":["Alice","Bob"],"Urgency":3}`,
string(c.Response().Body()),
)
Expand Down Expand Up @@ -2153,7 +2157,8 @@ func Test_Ctx_AutoFormat_Struct(t *testing.T) {
err = c.AutoFormat(data)
require.NoError(t, err)
require.Equal(t, MIMEApplicationXMLCharsetUTF8, c.GetRespHeader(HeaderContentType))
require.Equal(t,
require.Equal(
t,
`<Message sender="Carol" urgency="3"><Recipients>Alice</Recipients><Recipients>Bob</Recipients></Message>`,
string(c.Response().Body()),
)
Expand Down Expand Up @@ -8725,7 +8730,8 @@ func Test_Ctx_OverrideParam(t *testing.T) {
t.Run("plus_wildcard_params", func(t *testing.T) {
t.Parallel()
app := New()
app.Get("/files+/+",
app.Get(
"/files+/+",
func(c Ctx) error {
c.OverrideParam("+", "changed")
c.OverrideParam("+2", "changed2")
Expand Down
30 changes: 20 additions & 10 deletions domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,8 @@ func Test_Domain_MultipleHandlers(t *testing.T) {

app := New()

app.Domain("api.example.com").Get("/test",
app.Domain("api.example.com").Get(
"/test",
func(c Ctx) error {
c.Set("X-First", "true")
return c.Next()
Expand Down Expand Up @@ -1203,7 +1204,8 @@ func Test_Domain_HandlerTypes(t *testing.T) {
t.Run("express next-err returns-err", func(t *testing.T) {
t.Parallel()
app := New()
app.Domain("api.example.com").Get("/test",
app.Domain("api.example.com").Get(
"/test",
func(_ Req, res Res, next func() error) error {
res.Set("X-MW", "yes")
return next()
Expand All @@ -1226,7 +1228,8 @@ func Test_Domain_HandlerTypes(t *testing.T) {
t.Run("express with next error", func(t *testing.T) {
t.Parallel()
app := New()
app.Domain("api.example.com").Get("/test",
app.Domain("api.example.com").Get(
"/test",
func(_ Req, res Res, next func() error) {
res.Set("X-MW", "yes")
_ = next() //nolint:errcheck // test
Expand All @@ -1246,7 +1249,8 @@ func Test_Domain_HandlerTypes(t *testing.T) {
t.Run("express with noarg next error", func(t *testing.T) {
t.Parallel()
app := New()
app.Domain("api.example.com").Get("/test",
app.Domain("api.example.com").Get(
"/test",
func(_ Req, res Res, next func()) error {
res.Set("X-MW", "yes")
next()
Expand All @@ -1267,7 +1271,8 @@ func Test_Domain_HandlerTypes(t *testing.T) {
t.Run("express with noarg next", func(t *testing.T) {
t.Parallel()
app := New()
app.Domain("api.example.com").Get("/test",
app.Domain("api.example.com").Get(
"/test",
func(_ Req, res Res, next func()) {
res.Set("X-MW", "yes")
next()
Expand All @@ -1287,7 +1292,8 @@ func Test_Domain_HandlerTypes(t *testing.T) {
t.Run("express with error next", func(t *testing.T) {
t.Parallel()
app := New()
app.Domain("api.example.com").Get("/test",
app.Domain("api.example.com").Get(
"/test",
func(_ Req, res Res, next func(error)) {
res.Set("X-MW", "yes")
next(nil)
Expand All @@ -1307,7 +1313,8 @@ func Test_Domain_HandlerTypes(t *testing.T) {
t.Run("express with error next error", func(t *testing.T) {
t.Parallel()
app := New()
app.Domain("api.example.com").Get("/test",
app.Domain("api.example.com").Get(
"/test",
func(_ Req, res Res, next func(error)) error {
res.Set("X-MW", "yes")
next(nil)
Expand All @@ -1328,7 +1335,8 @@ func Test_Domain_HandlerTypes(t *testing.T) {
t.Run("express errnext-err returns-err", func(t *testing.T) {
t.Parallel()
app := New()
app.Domain("api.example.com").Get("/test",
app.Domain("api.example.com").Get(
"/test",
func(_ Req, res Res, next func(error) error) {
res.Set("X-MW", "yes")
_ = next(nil) //nolint:errcheck // test
Expand All @@ -1348,7 +1356,8 @@ func Test_Domain_HandlerTypes(t *testing.T) {
t.Run("express errnext-err returns-err err", func(t *testing.T) {
t.Parallel()
app := New()
app.Domain("api.example.com").Get("/test",
app.Domain("api.example.com").Get(
"/test",
func(_ Req, res Res, next func(error) error) error {
res.Set("X-MW", "yes")
return next(nil)
Expand Down Expand Up @@ -1424,7 +1433,8 @@ func Test_Domain_HandlerTypes(t *testing.T) {
t.Run("non-matching domain skips all handler types", func(t *testing.T) {
t.Parallel()
app := New()
app.Domain("api.example.com").Get("/test",
app.Domain("api.example.com").Get(
"/test",
func(c Ctx) error {
c.Set("X-Handler", "ran")
return c.SendString("should-not-run")
Expand Down
3 changes: 2 additions & 1 deletion helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ func (app *App) quoteRawString(raw string) string {
bb.B = append(bb.B, '\\', 'r')
case c < 0x20 || c == 0x7f:
// percent-encode control and DEL
bb.B = append(bb.B,
bb.B = append(
bb.B,
'%',
hex[c>>4],
hex[c&0x0f],
Expand Down
3 changes: 2 additions & 1 deletion helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,8 @@ func Test_Utils_UniqueRouteStack(t *testing.T) {
route1,
route2,
route3,
}))
}),
)
}

func Test_Utils_getGroupPath(t *testing.T) {
Expand Down
9 changes: 6 additions & 3 deletions middleware/compress/compress.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,22 @@ func New(config ...Config) fiber.Handler {
switch cfg.Level {
case LevelDefault:
// LevelDefault
compressor = fasthttp.CompressHandlerBrotliLevel(fctx,
compressor = fasthttp.CompressHandlerBrotliLevel(
fctx,
fasthttp.CompressBrotliDefaultCompression,
fasthttp.CompressDefaultCompression,
)
case LevelBestSpeed:
// LevelBestSpeed
compressor = fasthttp.CompressHandlerBrotliLevel(fctx,
compressor = fasthttp.CompressHandlerBrotliLevel(
fctx,
fasthttp.CompressBrotliBestSpeed,
fasthttp.CompressBestSpeed,
)
case LevelBestCompression:
// LevelBestCompression
compressor = fasthttp.CompressHandlerBrotliLevel(fctx,
compressor = fasthttp.CompressHandlerBrotliLevel(
fctx,
fasthttp.CompressBrotliBestCompression,
fasthttp.CompressBestCompression,
)
Expand Down
3 changes: 2 additions & 1 deletion middleware/envvar/envvar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ func Test_EnvVarHandler(t *testing.T) {
Vars map[string]string `json:"vars"`
}{
Vars: map[string]string{"testKey": "testVal"},
})
},
)
require.NoError(t, err)

app := fiber.New()
Expand Down
3 changes: 2 additions & 1 deletion middleware/logger/default_logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg *Config) error {
if data.ChainErr != nil {
formatErr = colors.Red + " | " + data.ChainErr.Error() + colors.Reset
}
fmt.Fprintf(buf,
fmt.Fprintf(
buf,
"%s |%s %3d %s| %13v | %15s |%s %-7s %s| %-"+data.ErrPaddingStr+"s %s\n",
data.Timestamp.Load().(string), //nolint:forcetypeassert,errcheck // Timestamp is always a string
statusColor(c.Response().StatusCode(), &colors), c.Response().StatusCode(), colors.Reset,
Expand Down
4 changes: 3 additions & 1 deletion middleware/session/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,10 @@ func configDefault(config ...Config) Config {
}

// Ensure AbsoluteTimeout is greater than or equal to IdleTimeout.
// Silently adjust to IdleTimeout to prevent application crashes.
if cfg.AbsoluteTimeout > 0 && cfg.AbsoluteTimeout < cfg.IdleTimeout {
panic("[session] AbsoluteTimeout must be greater than or equal to IdleTimeout")
log.Warnf("session: AbsoluteTimeout (%v) adjusted to match IdleTimeout (%v)", cfg.AbsoluteTimeout, cfg.IdleTimeout)
cfg.AbsoluteTimeout = cfg.IdleTimeout
}

// Check if we have a zero-value Extractor
Expand Down
12 changes: 7 additions & 5 deletions middleware/session/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ func TestDefaultErrorHandler(t *testing.T) {
}

func TestAbsoluteTimeoutValidation(t *testing.T) {
require.PanicsWithValue(t, "[session] AbsoluteTimeout must be greater than or equal to IdleTimeout", func() {
configDefault(Config{
IdleTimeout: 30 * time.Minute,
AbsoluteTimeout: 15 * time.Minute, // Less than IdleTimeout
})
// When AbsoluteTimeout is less than IdleTimeout, it should be adjusted to match IdleTimeout
// to prevent application crashes during middleware initialization.
cfg := configDefault(Config{
IdleTimeout: 30 * time.Minute,
AbsoluteTimeout: 15 * time.Minute, // Less than IdleTimeout
})
require.Equal(t, 30*time.Minute, cfg.IdleTimeout)
require.Equal(t, 30*time.Minute, cfg.AbsoluteTimeout, "AbsoluteTimeout should be adjusted to match IdleTimeout")
}
5 changes: 3 additions & 2 deletions middleware/session/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ func acquireData() *data {
d.Reset()
return d
}
// Handle unexpected type in the pool
panic("unexpected type in data pool")
d := new(data)
d.Data = make(map[any]any)
return d
}

// releaseData resets the data object and returns it to the pool.
Expand Down
Loading
Loading