Skip to content

Commit 845a7f8

Browse files
authored
🧹 chore: Improve Performance of Fiber Router (#3261)
* Initial improvements * Update test * Improve RemoveEscapeChar performance * Fix lint issues * Re-add comments * Add dedicated request handlers * Fix lint issues * Add test case for app.All with custom method * Add test for custom Ctx and Request Methods * Simplify test logic * Simplify test
1 parent 775e0a7 commit 845a7f8

File tree

10 files changed

+209
-102
lines changed

10 files changed

+209
-102
lines changed

.github/workflows/linter.yml

Lines changed: 1 addition & 1 deletion
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

Makefile

Lines changed: 1 addition & 1 deletion
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

app.go

Lines changed: 14 additions & 2 deletions
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

app_test.go

Lines changed: 29 additions & 10 deletions
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

binder/mapping.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ func parseToStruct(aliasTag string, out any, data map[string][]string) error {
107107
func parseToMap(ptr any, data map[string][]string) error {
108108
elem := reflect.TypeOf(ptr).Elem()
109109

110-
//nolint:exhaustive // it's not necessary to check all types
111-
switch elem.Kind() {
110+
switch elem.Kind() { //nolint:exhaustive // it's not necessary to check all types
112111
case reflect.Slice:
113112
newMap, ok := ptr.(map[string][]string)
114113
if !ok {
@@ -129,7 +128,6 @@ func parseToMap(ptr any, data map[string][]string) error {
129128
newMap[k] = ""
130129
continue
131130
}
132-
133131
newMap[k] = v[len(v)-1]
134132
}
135133
}

ctx_interface_gen.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ctx_test.go

Lines changed: 29 additions & 0 deletions
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()

path.go

Lines changed: 9 additions & 3 deletions
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)