Skip to content

Commit eeffa22

Browse files
committed
Merge remote-tracking branch 'origin/main' into multipart-nested
# Conflicts: # binder/mapping.go
2 parents 8c28b9a + d0e767f commit eeffa22

File tree

14 files changed

+301
-111
lines changed

14 files changed

+301
-111
lines changed

Diff for: .github/workflows/linter.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@ jobs:
3737
uses: golangci/golangci-lint-action@v6
3838
with:
3939
# NOTE: Keep this in sync with the version from .golangci.yml
40-
version: v1.62.0
40+
version: v1.62.2

Diff for: .github/workflows/markdown.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
uses: actions/checkout@v4
1616

1717
- name: Run markdownlint-cli2
18-
uses: DavidAnson/markdownlint-cli2-action@v18
18+
uses: DavidAnson/markdownlint-cli2-action@v19
1919
with:
2020
globs: |
2121
**/*.md

Diff for: Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ markdown:
3535
## lint: 🚨 Run lint checks
3636
.PHONY: lint
3737
lint:
38-
go run github.com/golangci/golangci-lint/cmd/[email protected].0 run ./...
38+
go run github.com/golangci/golangci-lint/cmd/[email protected].2 run ./...
3939

4040
## test: 🚦 Execute all tests
4141
.PHONY: test

Diff for: app.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,10 @@ func (app *App) handleTrustedProxy(ipAddress string) {
616616
// Note: It doesn't allow adding new methods, only customizing exist methods.
617617
func (app *App) NewCtxFunc(function func(app *App) CustomCtx) {
618618
app.newCtxFunc = function
619+
620+
if app.server != nil {
621+
app.server.Handler = app.customRequestHandler
622+
}
619623
}
620624

621625
// RegisterCustomConstraint allows to register custom constraint.
@@ -868,7 +872,11 @@ func (app *App) Config() Config {
868872
func (app *App) Handler() fasthttp.RequestHandler { //revive:disable-line:confusing-naming // Having both a Handler() (uppercase) and a handler() (lowercase) is fine. TODO: Use nolint:revive directive instead. See https://github.com/golangci/golangci-lint/issues/3476
869873
// prepare the server for the start
870874
app.startupProcess()
871-
return app.requestHandler
875+
876+
if app.newCtxFunc != nil {
877+
return app.customRequestHandler
878+
}
879+
return app.defaultRequestHandler
872880
}
873881

874882
// Stack returns the raw router stack.
@@ -1057,7 +1065,11 @@ func (app *App) init() *App {
10571065
}
10581066

10591067
// fasthttp server settings
1060-
app.server.Handler = app.requestHandler
1068+
if app.newCtxFunc != nil {
1069+
app.server.Handler = app.customRequestHandler
1070+
} else {
1071+
app.server.Handler = app.defaultRequestHandler
1072+
}
10611073
app.server.Name = app.config.ServerHeader
10621074
app.server.Concurrency = app.config.Concurrency
10631075
app.server.NoDefaultDate = app.config.DisableDefaultDate

Diff for: app_test.go

+29-10
Original file line numberDiff line numberDiff line change
@@ -581,32 +581,51 @@ func Test_App_Use_StrictRouting(t *testing.T) {
581581

582582
func Test_App_Add_Method_Test(t *testing.T) {
583583
t.Parallel()
584-
defer func() {
585-
if err := recover(); err != nil {
586-
require.Equal(t, "add: invalid http method JANE\n", fmt.Sprintf("%v", err))
587-
}
588-
}()
589584

590585
methods := append(DefaultMethods, "JOHN") //nolint:gocritic // We want a new slice here
591586
app := New(Config{
592587
RequestMethods: methods,
593588
})
594589

595-
app.Add([]string{"JOHN"}, "/doe", testEmptyHandler)
590+
app.Add([]string{"JOHN"}, "/john", testEmptyHandler)
596591

597-
resp, err := app.Test(httptest.NewRequest("JOHN", "/doe", nil))
592+
resp, err := app.Test(httptest.NewRequest("JOHN", "/john", nil))
598593
require.NoError(t, err, "app.Test(req)")
599594
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
600595

601-
resp, err = app.Test(httptest.NewRequest(MethodGet, "/doe", nil))
596+
resp, err = app.Test(httptest.NewRequest(MethodGet, "/john", nil))
602597
require.NoError(t, err, "app.Test(req)")
603598
require.Equal(t, StatusMethodNotAllowed, resp.StatusCode, "Status code")
604599

605-
resp, err = app.Test(httptest.NewRequest("UNKNOWN", "/doe", nil))
600+
resp, err = app.Test(httptest.NewRequest("UNKNOWN", "/john", nil))
606601
require.NoError(t, err, "app.Test(req)")
607602
require.Equal(t, StatusNotImplemented, resp.StatusCode, "Status code")
608603

609-
app.Add([]string{"JANE"}, "/doe", testEmptyHandler)
604+
// Add a new method
605+
require.Panics(t, func() {
606+
app.Add([]string{"JANE"}, "/jane", testEmptyHandler)
607+
})
608+
}
609+
610+
func Test_App_All_Method_Test(t *testing.T) {
611+
t.Parallel()
612+
613+
methods := append(DefaultMethods, "JOHN") //nolint:gocritic // We want a new slice here
614+
app := New(Config{
615+
RequestMethods: methods,
616+
})
617+
618+
// Add a new method with All
619+
app.All("/doe", testEmptyHandler)
620+
621+
resp, err := app.Test(httptest.NewRequest("JOHN", "/doe", nil))
622+
require.NoError(t, err, "app.Test(req)")
623+
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
624+
625+
// Add a new method
626+
require.Panics(t, func() {
627+
app.Add([]string{"JANE"}, "/jane", testEmptyHandler)
628+
})
610629
}
611630

612631
// go test -run Test_App_GETOnly

Diff for: binder/mapping.go

-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ func parseToMap(ptr any, data map[string][]string) error {
128128
newMap[k] = ""
129129
continue
130130
}
131-
132131
newMap[k] = v[len(v)-1]
133132
}
134133
default:

Diff for: ctx_interface_gen.go

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: ctx_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,35 @@ func Test_Ctx_CustomCtx(t *testing.T) {
127127
require.Equal(t, "prefix_v3", string(body))
128128
}
129129

130+
// go test -run Test_Ctx_CustomCtx
131+
func Test_Ctx_CustomCtx_and_Method(t *testing.T) {
132+
t.Parallel()
133+
134+
// Create app with custom request methods
135+
methods := append(DefaultMethods, "JOHN") //nolint:gocritic // We want a new slice here
136+
app := New(Config{
137+
RequestMethods: methods,
138+
})
139+
140+
// Create custom context
141+
app.NewCtxFunc(func(app *App) CustomCtx {
142+
return &customCtx{
143+
DefaultCtx: *NewDefaultCtx(app),
144+
}
145+
})
146+
147+
// Add route with custom method
148+
app.Add([]string{"JOHN"}, "/doe", testEmptyHandler)
149+
resp, err := app.Test(httptest.NewRequest("JOHN", "/doe", nil))
150+
require.NoError(t, err, "app.Test(req)")
151+
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
152+
153+
// Add a new method
154+
require.Panics(t, func() {
155+
app.Add([]string{"JANE"}, "/jane", testEmptyHandler)
156+
})
157+
}
158+
130159
// go test -run Test_Ctx_Accepts_EmptyAccept
131160
func Test_Ctx_Accepts_EmptyAccept(t *testing.T) {
132161
t.Parallel()

Diff for: docs/middleware/session.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
id: session
33
---
44

5-
# Session Middleware for [Fiber](https://github.com/gofiber/fiber)
5+
# Session
66

77
The `session` middleware provides session management for Fiber applications, utilizing the [Storage](https://github.com/gofiber/storage) package for multi-database support via a unified interface. By default, session data is stored in memory, but custom storage options are easily configurable (see examples below).
88

Diff for: middleware/idempotency/locker.go

+25-9
Original file line numberDiff line numberDiff line change
@@ -10,42 +10,58 @@ type Locker interface {
1010
Unlock(key string) error
1111
}
1212

13+
type countedLock struct {
14+
mu sync.Mutex
15+
locked int
16+
}
17+
1318
type MemoryLock struct {
14-
keys map[string]*sync.Mutex
19+
keys map[string]*countedLock
1520
mu sync.Mutex
1621
}
1722

1823
func (l *MemoryLock) Lock(key string) error {
1924
l.mu.Lock()
20-
mu, ok := l.keys[key]
25+
lock, ok := l.keys[key]
2126
if !ok {
22-
mu = new(sync.Mutex)
23-
l.keys[key] = mu
27+
lock = new(countedLock)
28+
l.keys[key] = lock
2429
}
30+
lock.locked++
2531
l.mu.Unlock()
2632

27-
mu.Lock()
33+
lock.mu.Lock()
2834

2935
return nil
3036
}
3137

3238
func (l *MemoryLock) Unlock(key string) error {
3339
l.mu.Lock()
34-
mu, ok := l.keys[key]
35-
l.mu.Unlock()
40+
lock, ok := l.keys[key]
3641
if !ok {
3742
// This happens if we try to unlock an unknown key
43+
l.mu.Unlock()
3844
return nil
3945
}
46+
l.mu.Unlock()
4047

41-
mu.Unlock()
48+
lock.mu.Unlock()
49+
50+
l.mu.Lock()
51+
lock.locked--
52+
if lock.locked <= 0 {
53+
// This happens if countedLock is used to Lock and Unlock the same number of times
54+
// So, we can delete the key to prevent memory leak
55+
delete(l.keys, key)
56+
}
57+
l.mu.Unlock()
4258

4359
return nil
4460
}
4561

4662
func NewMemoryLock() *MemoryLock {
4763
return &MemoryLock{
48-
keys: make(map[string]*sync.Mutex),
64+
keys: make(map[string]*countedLock),
4965
}
5066
}
5167

Diff for: middleware/idempotency/locker_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package idempotency_test
22

33
import (
4+
"strconv"
5+
"sync/atomic"
46
"testing"
57
"time"
68

@@ -59,3 +61,67 @@ func Test_MemoryLock(t *testing.T) {
5961
require.NoError(t, err)
6062
}
6163
}
64+
65+
func Benchmark_MemoryLock(b *testing.B) {
66+
keys := make([]string, b.N)
67+
for i := range keys {
68+
keys[i] = strconv.Itoa(i)
69+
}
70+
71+
lock := idempotency.NewMemoryLock()
72+
73+
b.ResetTimer()
74+
75+
for i := 0; i < b.N; i++ {
76+
key := keys[i]
77+
if err := lock.Lock(key); err != nil {
78+
b.Fatal(err)
79+
}
80+
if err := lock.Unlock(key); err != nil {
81+
b.Fatal(err)
82+
}
83+
}
84+
}
85+
86+
func Benchmark_MemoryLock_Parallel(b *testing.B) {
87+
// In order to prevent using repeated keys I pre-allocate keys
88+
keys := make([]string, 1_000_000)
89+
for i := range keys {
90+
keys[i] = strconv.Itoa(i)
91+
}
92+
93+
b.Run("UniqueKeys", func(b *testing.B) {
94+
lock := idempotency.NewMemoryLock()
95+
var keyI atomic.Int32
96+
b.RunParallel(func(p *testing.PB) {
97+
for p.Next() {
98+
i := int(keyI.Add(1)) % len(keys)
99+
key := keys[i]
100+
if err := lock.Lock(key); err != nil {
101+
b.Fatal(err)
102+
}
103+
if err := lock.Unlock(key); err != nil {
104+
b.Fatal(err)
105+
}
106+
}
107+
})
108+
})
109+
110+
b.Run("RepeatedKeys", func(b *testing.B) {
111+
lock := idempotency.NewMemoryLock()
112+
var keyI atomic.Int32
113+
b.RunParallel(func(p *testing.PB) {
114+
for p.Next() {
115+
// Division by 3 ensures that index will be repreated exactly 3 times
116+
i := int(keyI.Add(1)) / 3 % len(keys)
117+
key := keys[i]
118+
if err := lock.Lock(key); err != nil {
119+
b.Fatal(err)
120+
}
121+
if err := lock.Unlock(key); err != nil {
122+
b.Fatal(err)
123+
}
124+
}
125+
})
126+
})
127+
}

Diff for: path.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -620,10 +620,16 @@ func GetTrimmedParam(param string) string {
620620

621621
// RemoveEscapeChar remove escape characters
622622
func RemoveEscapeChar(word string) string {
623-
if strings.IndexByte(word, escapeChar) != -1 {
624-
return strings.ReplaceAll(word, string(escapeChar), "")
623+
b := []byte(word)
624+
dst := 0
625+
for src := 0; src < len(b); src++ {
626+
if b[src] == '\\' {
627+
continue
628+
}
629+
b[dst] = b[src]
630+
dst++
625631
}
626-
return word
632+
return string(b[:dst])
627633
}
628634

629635
func getParamConstraintType(constraintPart string) TypeConstraint {

0 commit comments

Comments
 (0)