Skip to content

Commit c0daafa

Browse files
committed
feat: copy on immutable boundary
1 parent 19204f5 commit c0daafa

File tree

14 files changed

+56
-51
lines changed

14 files changed

+56
-51
lines changed

app.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -636,26 +636,28 @@ func NewWithCustomCtx(newCtxFunc func(app *App) CustomCtx, config ...Config) *Ap
636636
return app
637637
}
638638

639-
// SafeString returns s when it already owns its memory or `Immutable` is enabled.
640-
// Otherwise, a copy is allocated to ensure the caller can safely reuse the value.
641-
func (app *App) SafeString(s string) string {
642-
if len(s) == 0 || app.config.Immutable {
639+
// ImmutableString ensures the returned string doesn't reference request or response
640+
// memory when `Immutable` is enabled. If immutability is disabled, the input is
641+
// returned without modification.
642+
func (app *App) ImmutableString(s string) string {
643+
if !app.config.Immutable || len(s) == 0 {
643644
return s
644645
}
645-
b := app.getBytes(s)
646+
b := utils.UnsafeBytes(s)
646647
if len(b) > 0 && unsafe.StringData(s) == &b[0] {
647648
return utils.CopyString(s)
648649
}
649650
return s
650651
}
651652

652-
// SafeBytes returns b when it already owns its memory or `Immutable` is enabled.
653-
// Otherwise, a copy is allocated to ensure the caller can safely reuse the value.
654-
func (app *App) SafeBytes(b []byte) []byte {
655-
if len(b) == 0 || app.config.Immutable {
653+
// ImmutableBytes ensures the returned slice doesn't reference request or response
654+
// memory when `Immutable` is enabled. If immutability is disabled, the input is
655+
// returned without modification.
656+
func (app *App) ImmutableBytes(b []byte) []byte {
657+
if !app.config.Immutable || len(b) == 0 {
656658
return b
657659
}
658-
s := app.getString(b)
660+
s := utils.UnsafeString(b)
659661
if unsafe.StringData(s) == &b[0] {
660662
return utils.CopyBytes(b)
661663
}

app_test.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -982,37 +982,37 @@ func Test_App_Config(t *testing.T) {
982982
require.True(t, app.Config().StrictRouting)
983983
}
984984

985-
func Test_App_SafeString(t *testing.T) {
985+
func Test_App_ImmutableString(t *testing.T) {
986986
t.Parallel()
987987

988988
s := "fiber"
989989
app := New()
990-
copied := app.SafeString(s)
991-
if unsafe.StringData(copied) == unsafe.StringData(s) {
992-
t.Errorf("expected a copy when immutable is disabled")
990+
same := app.ImmutableString(s)
991+
if unsafe.StringData(same) != unsafe.StringData(s) {
992+
t.Errorf("expected original string when immutable is disabled")
993993
}
994994

995995
appImmutable := New(Config{Immutable: true})
996-
same := appImmutable.SafeString(s)
997-
if unsafe.StringData(same) != unsafe.StringData(s) {
998-
t.Errorf("expected original string when immutable is enabled")
996+
copied := appImmutable.ImmutableString(s)
997+
if unsafe.StringData(copied) == unsafe.StringData(s) {
998+
t.Errorf("expected a copy when immutable is enabled")
999999
}
10001000
}
10011001

1002-
func Test_App_SafeBytes(t *testing.T) {
1002+
func Test_App_ImmutableBytes(t *testing.T) {
10031003
t.Parallel()
10041004

10051005
b := []byte("fiber")
10061006
app := New()
1007-
copied := app.SafeBytes(b)
1008-
if &copied[0] == &b[0] {
1009-
t.Errorf("expected a copy when immutable is disabled")
1007+
same := app.ImmutableBytes(b)
1008+
if &same[0] != &b[0] {
1009+
t.Errorf("expected original slice when immutable is disabled")
10101010
}
10111011

10121012
appImmutable := New(Config{Immutable: true})
1013-
same := appImmutable.SafeBytes(b)
1014-
if &same[0] != &b[0] {
1015-
t.Errorf("expected original slice when immutable is enabled")
1013+
copied := appImmutable.ImmutableBytes(b)
1014+
if &copied[0] == &b[0] {
1015+
t.Errorf("expected a copy when immutable is enabled")
10161016
}
10171017
}
10181018

docs/api/app.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ import Reference from '@site/src/components/reference';
99

1010
## Helpers
1111

12-
### SafeString
12+
### ImmutableString
1313

14-
Returns `s` unless it still references request or response memory, in which case a safe copy is allocated. Within handlers, call via `c.App().SafeString(...)`. When [`Immutable`](./fiber.md#immutable) is enabled, context helpers already return owned values so this usually yields the original string.
14+
When [`Immutable`](./fiber.md#immutable) is enabled, returns a detached copy of `s` if it still references request or response memory. If immutability is disabled, `s` is returned unchanged.
1515

1616
```go title="Signature"
17-
func (app *App) SafeString(s string) string
17+
func (app *App) ImmutableString(s string) string
1818
```
1919

20-
### SafeBytes
20+
### ImmutableBytes
2121

22-
Returns `b` unless it shares underlying memory with the current request or response, in which case a safe copy is allocated. Within handlers, call via `c.App().SafeBytes(...)`. With [`Immutable`](./fiber.md#immutable) enabled, values are already copied and this method simply returns the input.
22+
When [`Immutable`](./fiber.md#immutable) is enabled, returns a detached copy of `b` if it still references request or response memory. If immutability is disabled, `b` is returned unchanged.
2323

2424
```go title="Signature"
25-
func (app *App) SafeBytes(b []byte) []byte
25+
func (app *App) ImmutableBytes(b []byte) []byte
2626
```
2727

2828
## Routing

docs/api/ctx.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ app.Get("/", func(c fiber.Ctx) error {
627627

628628
:::info
629629
Returned value is only valid within the handler. Do not store any references.
630-
Use [`App.SafeString`](./app.md#safestring) or [`App.SafeBytes`](./app.md#safebytes) to create copies when needed, or enable the [**`Immutable`**](./fiber.md#immutable) setting. [Read more...](../#zero-allocation)
630+
Use [`App.ImmutableString`](./app.md#immutablestring) or [`App.ImmutableBytes`](./app.md#immutablebytes) to create copies when needed, or enable the [**`Immutable`**](./fiber.md#immutable) setting. [Read more...](../#zero-allocation)
631631
:::
632632
### FormFile
633633

docs/intro.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ func handler(c fiber.Ctx) error {
5252
}
5353
```
5454

55-
Fiber provides `SafeString` and `SafeBytes` methods on the app that perform the above when needed.
55+
Fiber provides `ImmutableString` and `ImmutableBytes` methods on the app that perform the above when `Immutable` is enabled.
5656

5757
```go
5858
app.Get("/:foo", func(c fiber.Ctx) error {
5959
// Variable is now immutable
60-
result := c.App().SafeString(c.Params("foo"))
60+
result := c.App().ImmutableString(c.Params("foo"))
6161

6262
// ...
6363
})

docs/middleware/cache.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Import the middleware package that is part of the Fiber web framework
5050
import (
5151
"github.com/gofiber/fiber/v3"
5252
"github.com/gofiber/fiber/v3/middleware/cache"
53+
"github.com/gofiber/utils/v2"
5354
)
5455
```
5556

@@ -78,7 +79,7 @@ app.Use(cache.New(cache.Config{
7879
return time.Second * time.Duration(newCacheTime)
7980
},
8081
KeyGenerator: func(c fiber.Ctx) string {
81-
return c.App().SafeString(c.Path())
82+
return utils.CopyString(c.Path())
8283
},
8384
}))
8485

@@ -109,7 +110,7 @@ The `CacheInvalidator` function allows you to define custom conditions for cache
109110
| CacheHeader | `string` | CacheHeader is the header on the response header that indicates the cache status, with the possible return values "hit," "miss," or "unreachable." | `X-Cache` |
110111
| CacheControl | `bool` | CacheControl enables client-side caching if set to true. | `false` |
111112
| CacheInvalidator | `func(fiber.Ctx) bool` | CacheInvalidator defines a function that is executed before checking the cache entry. It can be used to invalidate the existing cache manually by returning true. | `nil` |
112-
| KeyGenerator | `func(fiber.Ctx) string` | Key allows you to generate custom keys. The HTTP method is appended automatically. | `func(c fiber.Ctx) string { return c.App().SafeString(c.Path()) }` |
113+
| KeyGenerator | `func(fiber.Ctx) string` | Key allows you to generate custom keys. The HTTP method is appended automatically. | `func(c fiber.Ctx) string { return utils.CopyString(c.Path()) }` |
113114
| ExpirationGenerator | `func(fiber.Ctx, *cache.Config) time.Duration` | ExpirationGenerator allows you to generate custom expiration keys based on the request. | `nil` |
114115
| Storage | `fiber.Storage` | Storage is used to store the state of the middleware. | In-memory store |
115116
| StoreResponseHeaders | `bool` | StoreResponseHeaders allows you to store additional headers generated by next middlewares & handler. | `false` |
@@ -126,7 +127,7 @@ var ConfigDefault = Config{
126127
CacheControl: false,
127128
CacheInvalidator: nil,
128129
KeyGenerator: func(c fiber.Ctx) string {
129-
return c.App().SafeString(c.Path())
130+
return utils.CopyString(c.Path())
130131
},
131132
ExpirationGenerator: nil,
132133
StoreResponseHeaders: false,

docs/whats_new.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ We have made several changes to the Fiber app, including:
7171
- **NewWithCustomCtx**: Initialize an app with a custom context in one step.
7272
- **State**: Provides a global state for the application, which can be used to store and retrieve data across the application. Check out the [State](./api/state) method for further details.
7373
- **NewErrorf**: Allows variadic parameters when creating formatted errors.
74-
- **SafeBytes / SafeString**: Conditional copy helpers that allocate only when a value still references request or response memory. If it already owns its data (e.g. with `Immutable` enabled), the value is returned as-is. Access via `c.App().SafeString` and `c.App().SafeBytes`.
74+
- **ImmutableBytes / ImmutableString**: Helpers that copy values only when `Immutable` is enabled and data still references request or response buffers. Access via `c.App().ImmutableString` and `c.App().ImmutableBytes`.
7575

7676
#### Custom Route Constraints
7777

middleware/cache/cache.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,10 @@ func New(config ...Config) fiber.Handler {
236236

237237
e = manager.acquire()
238238
// Cache response
239-
e.body = c.App().SafeBytes(c.Response().Body())
239+
e.body = utils.CopyBytes(c.Response().Body())
240240
e.status = c.Response().StatusCode()
241-
e.ctype = c.App().SafeBytes(c.Response().Header.ContentType())
242-
e.cencoding = c.App().SafeBytes(c.Response().Header.Peek(fiber.HeaderContentEncoding))
241+
e.ctype = utils.CopyBytes(c.Response().Header.ContentType())
242+
e.cencoding = utils.CopyBytes(c.Response().Header.Peek(fiber.HeaderContentEncoding))
243243

244244
ageVal := uint64(0)
245245
if b := c.Response().Header.Peek(fiber.HeaderAge); len(b) > 0 {
@@ -259,7 +259,7 @@ func New(config ...Config) fiber.Handler {
259259
// create real copy
260260
keyS := string(key)
261261
if _, ok := ignoreHeaders[keyS]; !ok {
262-
e.headers[keyS] = c.App().SafeBytes(value)
262+
e.headers[keyS] = utils.CopyBytes(value)
263263
}
264264
}
265265
}

middleware/cache/cache_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/gofiber/fiber/v3"
1717
"github.com/gofiber/fiber/v3/internal/storage/memory"
1818
"github.com/gofiber/fiber/v3/middleware/etag"
19+
"github.com/gofiber/utils/v2"
1920
"github.com/stretchr/testify/require"
2021
"github.com/valyala/fasthttp"
2122
)
@@ -563,7 +564,7 @@ func Test_CustomKey(t *testing.T) {
563564
var called bool
564565
app.Use(New(Config{KeyGenerator: func(c fiber.Ctx) string {
565566
called = true
566-
return c.App().SafeString(c.Path())
567+
return utils.CopyString(c.Path())
567568
}}))
568569

569570
app.Get("/", func(c fiber.Ctx) error {

middleware/cache/config.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"time"
55

66
"github.com/gofiber/fiber/v3"
7+
"github.com/gofiber/utils/v2"
78
)
89

910
// Config defines the config for middleware.
@@ -26,7 +27,7 @@ type Config struct {
2627
// Key allows you to generate custom keys, by default c.Path() is used
2728
//
2829
// Default: func(c fiber.Ctx) string {
29-
// return c.App().SafeString(c.Path())
30+
// return utils.CopyString(c.Path())
3031
// }
3132
KeyGenerator func(fiber.Ctx) string
3233

@@ -81,7 +82,7 @@ var ConfigDefault = Config{
8182
CacheControl: false,
8283
CacheInvalidator: nil,
8384
KeyGenerator: func(c fiber.Ctx) string {
84-
return c.App().SafeString(c.Path())
85+
return utils.CopyString(c.Path())
8586
},
8687
ExpirationGenerator: nil,
8788
StoreResponseHeaders: false,

0 commit comments

Comments
 (0)