Skip to content

Commit f21b54c

Browse files
authored
Merge branch 'main' into 2025-08-11-00-12-44
2 parents f0a5b25 + 77667e5 commit f21b54c

File tree

6 files changed

+87
-57
lines changed

6 files changed

+87
-57
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
h1 {
2+
color: black;
3+
}

ctx_test.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ import (
2929
"time"
3030

3131
"github.com/fxamacker/cbor/v2"
32-
"github.com/gofiber/fiber/v3/internal/storage/memory"
3332
"github.com/gofiber/utils/v2"
3433
"github.com/shamaton/msgpack/v2"
3534
"github.com/stretchr/testify/require"
3635
"github.com/valyala/bytebufferpool"
3736
"github.com/valyala/fasthttp"
37+
38+
"github.com/gofiber/fiber/v3/internal/storage/memory"
3839
)
3940

4041
const epsilon = 0.001
@@ -5413,23 +5414,24 @@ func Test_Ctx_SendStreamWriter_Interrupted(t *testing.T) {
54135414
t.Parallel()
54145415
app := New()
54155416
var flushed atomic.Int32
5417+
var flushErrLine atomic.Int32
54165418
app.Get("/", func(c Ctx) error {
54175419
return c.SendStreamWriter(func(w *bufio.Writer) {
54185420
for lineNum := 1; lineNum <= 5; lineNum++ {
54195421
fmt.Fprintf(w, "Line %d\n", lineNum)
54205422

54215423
if err := w.Flush(); err != nil {
5422-
if lineNum <= 3 {
5423-
t.Errorf("unexpected error: %s", err)
5424-
}
5424+
flushErrLine.Store(int32(lineNum)) //nolint:gosec // this is a test
54255425
return
54265426
}
54275427

54285428
if lineNum <= 3 {
54295429
flushed.Add(1)
54305430
}
54315431

5432-
time.Sleep(500 * time.Millisecond)
5432+
if lineNum == 3 {
5433+
time.Sleep(500 * time.Millisecond)
5434+
}
54335435
}
54345436
})
54355437
})
@@ -5439,7 +5441,7 @@ func Test_Ctx_SendStreamWriter_Interrupted(t *testing.T) {
54395441
// allow enough time for three lines to flush before
54405442
// the test connection is closed but stop before the
54415443
// fourth line is sent
5442-
Timeout: 1400 * time.Millisecond,
5444+
Timeout: 200 * time.Millisecond,
54435445
FailOnTimeout: false,
54445446
}
54455447
resp, err := app.Test(req, testConfig)
@@ -5453,6 +5455,10 @@ func Test_Ctx_SendStreamWriter_Interrupted(t *testing.T) {
54535455

54545456
// ensure the first three lines were successfully flushed
54555457
require.Equal(t, int32(3), flushed.Load())
5458+
5459+
// verify no flush errors occurred before the fourth line
5460+
v := flushErrLine.Load()
5461+
require.True(t, v == 0 || v >= 4, "unexpected flush error on line %d", v)
54565462
}
54575463

54585464
// go test -run Test_Ctx_Set

docs/middleware/timeout.md

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ id: timeout
44

55
# Timeout
66

7-
There exist two distinct implementations of timeout middleware [Fiber](https://github.com/gofiber/fiber).
7+
The timeout middleware limits how long a handler can take to complete. When the
8+
deadline is exceeded, Fiber responds with `408 Request Timeout`. The middleware
9+
uses `context.WithTimeout` and makes the context available through
10+
`c.Context()` so that your code can listen for cancellation.
811

9-
## New
10-
11-
As a `fiber.Handler` wrapper, it creates a context with `context.WithTimeout` which is then used with `c.Context()`.
12-
13-
If the context passed executions (eg. DB ops, Http calls) takes longer than the given duration to return, the timeout error is set and forwarded to the centralized `ErrorHandler`.
14-
15-
It does not cancel long running executions. Underlying executions must handle timeout by using `context.Context` parameter.
12+
:::caution
13+
`timeout.New` wraps your final handler and can't be added with `app.Use` or
14+
used in a middleware chain. Register it per route and avoid calling
15+
`c.Next()` inside the wrapped handler—doing so will panic.
16+
:::
1617

1718
## Signatures
1819

@@ -22,45 +23,58 @@ func New(handler fiber.Handler, config ...timeout.Config) fiber.Handler
2223

2324
## Examples
2425

25-
Import the middleware package that is part of the Fiber web framework
26+
### Basic example
27+
28+
The following program times out any request that takes longer than two seconds.
29+
The handler simulates work with `sleepWithContext`, which stops when the
30+
request's context is canceled:
2631

2732
```go
33+
package main
34+
2835
import (
36+
"context"
37+
"fmt"
38+
"log"
39+
"time"
40+
2941
"github.com/gofiber/fiber/v3"
3042
"github.com/gofiber/fiber/v3/middleware/timeout"
3143
)
32-
```
3344

34-
After you initiate your Fiber app, you can use the following possibilities:
45+
func sleepWithContext(ctx context.Context, d time.Duration) error {
46+
select {
47+
case <-time.After(d):
48+
return nil
49+
case <-ctx.Done():
50+
return ctx.Err()
51+
}
52+
}
3553

36-
```go
3754
func main() {
3855
app := fiber.New()
39-
h := func(c fiber.Ctx) error {
40-
sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms")
41-
if err := sleepWithContext(c.Context(), sleepTime); err != nil {
56+
57+
handler := func(c fiber.Ctx) error {
58+
delay, _ := time.ParseDuration(c.Params("delay") + "ms")
59+
if err := sleepWithContext(c.Context(), delay); err != nil {
4260
return fmt.Errorf("%w: execution error", err)
4361
}
44-
return nil
62+
return c.SendString("finished")
4563
}
4664

47-
app.Get("/foo/:sleepTime", timeout.New(h, timeout.Config{Timeout: 2 * time.Second}))
65+
app.Get("/sleep/:delay", timeout.New(handler, timeout.Config{
66+
Timeout: 2 * time.Second,
67+
}))
68+
4869
log.Fatal(app.Listen(":3000"))
4970
}
71+
```
5072

51-
func sleepWithContext(ctx context.Context, d time.Duration) error {
52-
timer := time.NewTimer(d)
73+
Test it with curl:
5374

54-
select {
55-
case <-ctx.Done():
56-
if !timer.Stop() {
57-
<-timer.C
58-
}
59-
return context.DeadlineExceeded
60-
case <-timer.C:
61-
}
62-
return nil
63-
}
75+
```bash
76+
curl -i http://localhost:3000/sleep/1000 # finishes within the timeout
77+
curl -i http://localhost:3000/sleep/3000 # returns 408 Request Timeout
6478
```
6579

6680
## Config
@@ -72,19 +86,7 @@ func sleepWithContext(ctx context.Context, d time.Duration) error {
7286
| OnTimeout | `fiber.Handler` | Handler executed when a timeout occurs. Defaults to returning `fiber.ErrRequestTimeout`. | `nil` |
7387
| Errors | `[]error` | Custom errors treated as timeout errors. | `nil` |
7488

75-
Test http 200 with curl:
76-
77-
```bash
78-
curl --location -I --request GET 'http://localhost:3000/foo/1000'
79-
```
80-
81-
Test http 408 with curl:
82-
83-
```bash
84-
curl --location -I --request GET 'http://localhost:3000/foo/3000'
85-
```
86-
87-
Use with custom error:
89+
### Use with custom error
8890

8991
```go
9092
var ErrFooTimeOut = errors.New("foo context canceled")
@@ -117,7 +119,7 @@ func sleepWithContextWithCustomError(ctx context.Context, d time.Duration) error
117119
}
118120
```
119121

120-
Sample usage with a DB call:
122+
### Sample usage with a database call
121123

122124
```go
123125
func main() {

docs/whats_new.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,6 @@ app.Get("/login/:id<ulid>", func(c fiber.Ctx) error {
110110
- **ListenTLSWithCertificate**: Use `app.Listen()` with `tls.Config`.
111111
- **ListenMutualTLS**: Use `app.Listen()` with `tls.Config`.
112112
- **ListenMutualTLSWithCertificate**: Use `app.Listen()` with `tls.Config`.
113-
- **Context()**: Removed. `Ctx` now directly implements `context.Context`, so you can pass `c` anywhere a `context.Context` is required.
114-
- **SetContext()**: Removed. Attach additional context information using `Locals` or middleware if needed.
115113

116114
### Method Changes
117115

@@ -453,7 +451,7 @@ testConfig := fiber.TestConfig{
453451

454452
- Cookie now allows Partitioned cookies for [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips) support. CHIPS (Cookies Having Independent Partitioned State) is a feature that improves privacy by allowing cookies to be partitioned by top-level site, mitigating cross-site tracking.
455453
- Cookie automatic security enforcement: When setting a cookie with `SameSite=None`, Fiber automatically sets `Secure=true` as required by RFC 6265bis and modern browsers (Chrome, Firefox, Safari). This ensures compliance with the "None" SameSite policy. See [Mozilla docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#none) and [Chrome docs](https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure) for details.
456-
- Context now implements [context.Context](https://pkg.go.dev/context#Context).
454+
- `Ctx` now implements the [context.Context](https://pkg.go.dev/context#Context) interface, replacing the former `UserContext` helpers.
457455

458456
### New Methods
459457

@@ -490,6 +488,8 @@ testConfig := fiber.TestConfig{
490488
- **RedirectToRoute**: Use `c.Redirect().Route()` instead.
491489
- **RedirectBack**: Use `c.Redirect().Back()` instead.
492490
- **ReqHeaderParser**: Use `c.Bind().Header()` instead.
491+
- **UserContext**: Removed. `Ctx` itself now satisfies `context.Context`; pass `c` directly where a `context.Context` is required.
492+
- **SetUserContext**: Removed. Use `context.WithValue` on `c` or `c.Locals` to store additional request-scoped values.
493493

494494
### Changed Methods
495495

@@ -500,9 +500,7 @@ testConfig := fiber.TestConfig{
500500
- **Attachment and Download**: Non-ASCII filenames now use `filename*` as
501501
specified by [RFC 6266](https://www.rfc-editor.org/rfc/rfc6266) and
502502
[RFC 8187](https://www.rfc-editor.org/rfc/rfc8187).
503-
- **Context**: Renamed to `RequestCtx` to correspond with the FastHTTP Request Context.
504-
- **UserContext**: Renamed to `Context`, which returns a `context.Context` object.
505-
- **SetUserContext**: Renamed to `SetContext`.
503+
- **Context()**: Renamed to `RequestCtx()` to access the underlying `fasthttp.RequestCtx`.
506504

507505
### SendStreamWriter
508506

@@ -940,10 +938,10 @@ func main() {
940938
```sh
941939
$ go run . -v
942940

943-
_______ __
941+
_______ __
944942
/ ____(_) /_ ___ _____
945943
/ /_ / / __ \/ _ \/ ___/
946-
/ __/ / / /_/ / __/ /
944+
/ __/ / / /_/ / __/ /
947945
/_/ /_/_.___/\___/_/ v3.0.0
948946
--------------------------------------------------
949947
INFO Server started on: http://127.0.0.1:3000 (bound on host 0.0.0.0 and port 3000)

middleware/static/static.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import (
2323
// It returns an error if the path attempts to traverse directories.
2424
func sanitizePath(p []byte, filesystem fs.FS) ([]byte, error) {
2525
var s string
26+
27+
hasTrailingSlash := len(p) > 0 && p[len(p)-1] == '/'
28+
2629
if bytes.IndexByte(p, '\\') >= 0 {
2730
b := make([]byte, len(p))
2831
copy(b, p)
@@ -66,6 +69,10 @@ func sanitizePath(p []byte, filesystem fs.FS) ([]byte, error) {
6669
s = "/" + s
6770
}
6871

72+
if hasTrailingSlash && len(s) > 1 && s[len(s)-1] != '/' {
73+
s += "/"
74+
}
75+
6976
return utils.UnsafeBytes(s), nil
7077
}
7178

middleware/static/static_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,20 @@ func Test_Static_FS_Browse(t *testing.T) {
651651
require.NoError(t, err, "app.Test(req)")
652652
require.Contains(t, string(body), "color")
653653

654+
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/dirfs/test", nil))
655+
require.NoError(t, err, "app.Test(req)")
656+
require.Equal(t, 200, resp.StatusCode, "Status code")
657+
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
658+
659+
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/dirfs/test/style2.css", nil))
660+
require.NoError(t, err, "app.Test(req)")
661+
require.Equal(t, 200, resp.StatusCode, "Status code")
662+
require.Equal(t, fiber.MIMETextCSSCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
663+
664+
body, err = io.ReadAll(resp.Body)
665+
require.NoError(t, err, "app.Test(req)")
666+
require.Contains(t, string(body), "color")
667+
654668
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/embed", nil))
655669
require.NoError(t, err, "app.Test(req)")
656670
require.Equal(t, 200, resp.StatusCode, "Status code")

0 commit comments

Comments
 (0)