Skip to content

Commit 97530f7

Browse files
committed
🔥 feat: add msgpack cbor xml helpers to SharedState
1 parent cf37114 commit 97530f7

6 files changed

Lines changed: 1118 additions & 1 deletion

File tree

app.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ type App struct {
9090
mountFields *mountFields
9191
// state management
9292
state *State
93+
// shared state management (prefork-safe, storage-backed)
94+
sharedState *SharedState
9395
// Route stack divided by HTTP methods
9496
stack [][]*Route
9597
// customConstraints is a list of external constraints
@@ -279,6 +281,19 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
279281
// Default: nil
280282
AppName string `json:"app_name"`
281283

284+
// SharedStorage configures storage-backed shared state that can be used
285+
// safely across prefork workers and processes.
286+
//
287+
// Default: nil
288+
SharedStorage Storage `json:"-"`
289+
290+
// SharedStatePrefix customizes the namespace prefix for keys written to
291+
// SharedStorage. If empty, Fiber derives a prefixed namespace using
292+
// AppName (when set) or an internal default.
293+
//
294+
// Default: ""
295+
SharedStatePrefix string `json:"shared_state_prefix"`
296+
282297
// StreamRequestBody enables request body streaming,
283298
// and calls the handler sooner when given body is
284299
// larger than the current limit.
@@ -638,6 +653,26 @@ func New(config ...Config) *App {
638653
if app.config.XMLDecoder == nil {
639654
app.config.XMLDecoder = xml.Unmarshal
640655
}
656+
657+
sharedStatePrefix := app.config.SharedStatePrefix
658+
if sharedStatePrefix == "" {
659+
sharedStatePrefix = defaultSharedStatePrefix
660+
if app.config.AppName != "" {
661+
sharedStatePrefix += app.config.AppName + "-"
662+
}
663+
}
664+
app.sharedState = newSharedState(
665+
app.config.SharedStorage,
666+
sharedStatePrefix,
667+
app.config.JSONEncoder,
668+
app.config.JSONDecoder,
669+
app.config.MsgPackEncoder,
670+
app.config.MsgPackDecoder,
671+
app.config.CBOREncoder,
672+
app.config.CBORDecoder,
673+
app.config.XMLEncoder,
674+
app.config.XMLDecoder,
675+
)
641676
if len(app.config.RequestMethods) == 0 {
642677
app.config.RequestMethods = DefaultMethods
643678
}
@@ -1172,11 +1207,18 @@ func (app *App) Hooks() *Hooks {
11721207
return app.hooks
11731208
}
11741209

1175-
// State returns the state struct to store global data in order to share it between handlers.
1210+
// State returns the in-process state struct to store global data between handlers.
1211+
// State is process-local and is not shared across prefork workers.
11761212
func (app *App) State() *State {
11771213
return app.state
11781214
}
11791215

1216+
// SharedState returns storage-backed shared state.
1217+
// SharedState is prefork-safe when Config.SharedStorage is configured.
1218+
func (app *App) SharedState() *SharedState {
1219+
return app.sharedState
1220+
}
1221+
11801222
var ErrTestGotEmptyResponse = errors.New("test: got empty response")
11811223

11821224
// TestConfig is a struct holding Test settings

docs/api/app.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,18 @@ func main() {
8080
}
8181
```
8282

83+
### State / SharedState
84+
85+
`State()` returns in-process state (local to the current process).
86+
`SharedState()` returns storage-backed state intended for prefork/multi-process sharing.
87+
88+
```go title="Signature"
89+
func (app *App) State() *State
90+
func (app *App) SharedState() *SharedState
91+
```
92+
93+
See [State Management](./state.md) for usage and examples.
94+
8395
### MountPath
8496

8597
The `MountPath` property contains one or more path patterns on which a sub-app was mounted.

docs/api/state.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,122 @@ State management provides a global key–value store for application dependencie
1010
When prefork is enabled, each worker process has an independent state store, meaning state is not shared between them.
1111
:::
1212

13+
## SharedState (Prefork-safe)
14+
15+
For data that must be shared across prefork workers or multiple app processes, use `app.SharedState()` backed by `fiber.Storage`.
16+
17+
Configure storage in `fiber.Config`:
18+
19+
```go
20+
app := fiber.New(fiber.Config{
21+
AppName: "billing-api",
22+
SharedStorage: redisStorage, // any implementation of fiber.Storage
23+
SharedStatePrefix: "billing-shared-", // optional
24+
})
25+
```
26+
27+
If `SharedStatePrefix` is empty, Fiber derives a default namespace and includes `AppName` (when set) to reduce collisions between apps/services.
28+
29+
:::warning Memory storage caveat
30+
`SharedState` is only cross-worker / cross-process when the configured `SharedStorage` backend is shared.
31+
32+
If you use an in-memory backend (for example memory storage), data remains process-local. In prefork mode, each worker process has its own independent in-memory store.
33+
:::
34+
35+
### SharedState Methods
36+
37+
```go title="Signature"
38+
func (app *App) SharedState() *SharedState
39+
40+
func (s *SharedState) Set(key string, val []byte, ttl time.Duration) error
41+
func (s *SharedState) SetWithContext(ctx context.Context, key string, val []byte, ttl time.Duration) error
42+
43+
func (s *SharedState) Get(key string) (val []byte, found bool, err error)
44+
func (s *SharedState) GetWithContext(ctx context.Context, key string) (val []byte, found bool, err error)
45+
46+
func (s *SharedState) SetJSON(key string, v any, ttl time.Duration) error
47+
func (s *SharedState) SetJSONWithContext(ctx context.Context, key string, v any, ttl time.Duration) error
48+
49+
func (s *SharedState) GetJSON(key string, out any) (raw []byte, found bool, err error)
50+
func (s *SharedState) GetJSONWithContext(ctx context.Context, key string, out any) (raw []byte, found bool, err error)
51+
52+
func (s *SharedState) SetMsgPack(key string, v any, ttl time.Duration) error
53+
func (s *SharedState) SetMsgPackWithContext(ctx context.Context, key string, v any, ttl time.Duration) error
54+
55+
func (s *SharedState) GetMsgPack(key string, out any) (raw []byte, found bool, err error)
56+
func (s *SharedState) GetMsgPackWithContext(ctx context.Context, key string, out any) (raw []byte, found bool, err error)
57+
58+
func (s *SharedState) SetCBOR(key string, v any, ttl time.Duration) error
59+
func (s *SharedState) SetCBORWithContext(ctx context.Context, key string, v any, ttl time.Duration) error
60+
61+
func (s *SharedState) GetCBOR(key string, out any) (raw []byte, found bool, err error)
62+
func (s *SharedState) GetCBORWithContext(ctx context.Context, key string, out any) (raw []byte, found bool, err error)
63+
64+
func (s *SharedState) SetXML(key string, v any, ttl time.Duration) error
65+
func (s *SharedState) SetXMLWithContext(ctx context.Context, key string, v any, ttl time.Duration) error
66+
67+
func (s *SharedState) GetXML(key string, out any) (raw []byte, found bool, err error)
68+
func (s *SharedState) GetXMLWithContext(ctx context.Context, key string, out any) (raw []byte, found bool, err error)
69+
70+
func (s *SharedState) Delete(key string) error
71+
func (s *SharedState) DeleteWithContext(ctx context.Context, key string) error
72+
73+
func (s *SharedState) Has(key string) (bool, error)
74+
func (s *SharedState) HasWithContext(ctx context.Context, key string) (bool, error)
75+
```
76+
77+
### SharedState Example
78+
79+
```go
80+
type SessionSnapshot struct {
81+
UserID string `json:"user_id"`
82+
UpdatedAt time.Time `json:"updated_at"`
83+
}
84+
85+
app.Post("/sessions/:id", func(c fiber.Ctx) error {
86+
key := "session:" + c.Params("id")
87+
value := SessionSnapshot{
88+
UserID: c.Params("id"),
89+
UpdatedAt: time.Now().UTC(),
90+
}
91+
92+
if err := app.SharedState().SetJSON(key, value, 30*time.Minute); err != nil {
93+
return err
94+
}
95+
96+
return c.SendStatus(fiber.StatusAccepted)
97+
})
98+
99+
app.Get("/sessions/:id", func(c fiber.Ctx) error {
100+
key := "session:" + c.Params("id")
101+
var snapshot SessionSnapshot
102+
103+
_, found, err := app.SharedState().GetJSON(key, &snapshot)
104+
if err != nil {
105+
return err
106+
}
107+
if !found {
108+
return c.SendStatus(fiber.StatusNotFound)
109+
}
110+
111+
return c.JSON(snapshot)
112+
})
113+
```
114+
115+
### SharedState with Context (timeouts/cancellation)
116+
117+
```go
118+
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
119+
defer cancel()
120+
121+
err := app.SharedState().SetJSONWithContext(ctx, "job:42", fiber.Map{
122+
"status": "queued",
123+
}, 2*time.Minute)
124+
if err != nil {
125+
// timeout, cancellation, storage error, or JSON serialization error
126+
}
127+
```
128+
13129
## State Type
14130

15131
`State` is a key–value store built on top of `sync.Map` to ensure safe concurrent access. It allows storage and retrieval of dependencies and configurations in a Fiber application as well as thread–safe access to runtime data.

docs/whats_new.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ We have made several changes to the Fiber app, including:
8484
- **RegisterCustomConstraint**: Allows for the registration of custom constraints.
8585
- **NewWithCustomCtx**: Initialize an app with a custom context in one step.
8686
- **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.
87+
- **SharedState**: Introduces storage-backed app state for prefork-safe/multi-process coordination via `Config.SharedStorage`, with optional `Config.SharedStatePrefix` namespacing and JSON/context-aware helpers (`SetJSON`, `GetJSON`, `Has`, `Delete`, and `WithContext` variants).
8788
- **NewErrorf**: Allows variadic parameters when creating formatted errors.
8889
- **GetBytes / GetString**: Helpers that detach values only when `Immutable` is enabled and the data still references request or response buffers. Access via `c.App().GetString` and `c.App().GetBytes`.
8990
- **ReloadViews**: Lets you re-run the configured view engine's `Load()` logic at runtime, including guard rails for missing or nil view engines so development hot-reload hooks can refresh templates safely.

0 commit comments

Comments
 (0)