Skip to content

Commit 94e30d7

Browse files
authored
🐛 Fix routing with mount and static (#3454)
* fix routing with mount and static [Bug]: Static server in sub app does not work #3104 #3104 [Bug]: When mounting a subapp with mount, the static route is inaccessible. #3442 #3442
1 parent fccff19 commit 94e30d7

File tree

5 files changed

+97
-67
lines changed

5 files changed

+97
-67
lines changed

app.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ type App struct {
9393
treeStack []map[string][]*Route
9494
// contains the information if the route stack has been changed to build the optimized tree
9595
routesRefreshed bool
96-
// Amount of registered routes
97-
routesCount uint32
9896
// Amount of registered handlers
9997
handlersCount uint32
10098
// Ctx pool

mount.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ func (app *App) processSubAppsRoutes() {
174174
}
175175
}
176176
var handlersCount uint32
177-
var routePos uint32
178177
// Iterate over the stack of the parent app
179178
for m := range app.stack {
180179
// Iterate over each route in the stack
@@ -183,9 +182,6 @@ func (app *App) processSubAppsRoutes() {
183182
route := app.stack[m][i]
184183
// Check if the route has a mounted app
185184
if !route.mount {
186-
routePos++
187-
// If not, update the route's position and continue
188-
route.pos = routePos
189185
if !route.use || (route.use && m == 0) {
190186
handlersCount += uint32(len(route.Handlers))
191187
}
@@ -214,11 +210,7 @@ func (app *App) processSubAppsRoutes() {
214210
copy(newStack[i+len(subRoutes):], app.stack[m][i+1:])
215211
app.stack[m] = newStack
216212

217-
// Decrease the parent app's route count to account for the mounted app's original route
218-
atomic.AddUint32(&app.routesCount, ^uint32(0))
219213
i--
220-
// Increase the parent app's route count to account for the sub-app's routes
221-
atomic.AddUint32(&app.routesCount, uint32(len(subRoutes)))
222214

223215
// Mark the parent app's routes as refreshed
224216
app.routesRefreshed = true

mount_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ func Test_App_Mount_Nested(t *testing.T) {
8989
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
9090

9191
utils.AssertEqual(t, uint32(6), app.handlersCount)
92-
utils.AssertEqual(t, uint32(6), app.routesCount)
9392
}
9493

9594
// go test -run Test_App_Mount_Express_Behavior
@@ -139,7 +138,6 @@ func Test_App_Mount_Express_Behavior(t *testing.T) {
139138
testEndpoint(app, "/unknown", ErrNotFound.Message, StatusNotFound)
140139

141140
utils.AssertEqual(t, uint32(17), app.handlersCount)
142-
utils.AssertEqual(t, uint32(16+9), app.routesCount)
143141
}
144142

145143
// go test -run Test_App_Mount_RoutePositions
@@ -195,19 +193,15 @@ func Test_App_Mount_RoutePositions(t *testing.T) {
195193

196194
utils.AssertEqual(t, true, routeStackGET[1].use)
197195
utils.AssertEqual(t, "/", routeStackGET[1].path)
198-
utils.AssertEqual(t, true, routeStackGET[0].pos < routeStackGET[1].pos, "wrong position of route 0")
199196

200197
utils.AssertEqual(t, false, routeStackGET[2].use)
201198
utils.AssertEqual(t, "/bar", routeStackGET[2].path)
202-
utils.AssertEqual(t, true, routeStackGET[1].pos < routeStackGET[2].pos, "wrong position of route 1")
203199

204200
utils.AssertEqual(t, true, routeStackGET[3].use)
205201
utils.AssertEqual(t, "/", routeStackGET[3].path)
206-
utils.AssertEqual(t, true, routeStackGET[2].pos < routeStackGET[3].pos, "wrong position of route 2")
207202

208203
utils.AssertEqual(t, false, routeStackGET[4].use)
209204
utils.AssertEqual(t, "/subapp2/world", routeStackGET[4].path)
210-
utils.AssertEqual(t, true, routeStackGET[3].pos < routeStackGET[4].pos, "wrong position of route 3")
211205

212206
utils.AssertEqual(t, 5, len(routeStackGET))
213207
}

router.go

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package fiber
77
import (
88
"fmt"
99
"html"
10-
"sort"
1110
"strconv"
1211
"strings"
1312
"sync/atomic"
@@ -47,9 +46,8 @@ type Router interface {
4746

4847
// Route is a struct that holds all metadata for each registered handler.
4948
type Route struct {
50-
// ### important: always keep in sync with the copy method "app.copyRoute" ###
49+
// ### important: always keep in sync with the copy method "app.copyRoute" and all creations of Route struct ###
5150
// Data for routing
52-
pos uint32 // Position in stack -> important for the sort of the matched routes
5351
use bool // USE matches path prefixes
5452
mount bool // Indicated a mounted app on a specific route
5553
star bool // Path equals '*'
@@ -215,9 +213,6 @@ func (*App) copyRoute(route *Route) *Route {
215213
path: route.path,
216214
routeParser: route.routeParser,
217215

218-
// misc
219-
pos: route.pos,
220-
221216
// Public data
222217
Path: route.Path,
223218
Params: route.Params,
@@ -298,11 +293,11 @@ func (app *App) register(method, pathRaw string, group *Group, handlers ...Handl
298293
for _, m := range app.config.RequestMethods {
299294
// Create a route copy to avoid duplicates during compression
300295
r := route
301-
app.addRoute(m, &r, isMount)
296+
app.addRoute(m, &r)
302297
}
303298
} else {
304299
// Add route to stack
305-
app.addRoute(method, &route, isMount)
300+
app.addRoute(method, &route)
306301
}
307302
}
308303

@@ -428,12 +423,20 @@ func (app *App) registerStatic(prefix, root string, config ...Static) {
428423
// Create route metadata without pointer
429424
route := Route{
430425
// Router booleans
431-
use: true,
432-
root: isRoot,
426+
use: true,
427+
mount: false,
428+
star: isStar,
429+
root: isRoot,
430+
431+
// Path data
433432
path: prefix,
433+
434+
// Group data
435+
group: nil,
436+
434437
// Public data
435-
Method: MethodGet,
436438
Path: prefix,
439+
Method: MethodGet,
437440
Handlers: []Handler{handler},
438441
}
439442
// Increment global handler count
@@ -444,13 +447,7 @@ func (app *App) registerStatic(prefix, root string, config ...Static) {
444447
app.addRoute(MethodHead, &route)
445448
}
446449

447-
func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
448-
// Check mounted routes
449-
var mounted bool
450-
if len(isMounted) > 0 {
451-
mounted = isMounted[0]
452-
}
453-
450+
func (app *App) addRoute(method string, route *Route) {
454451
// Get unique HTTP method identifier
455452
m := app.methodInt(method)
456453

@@ -460,16 +457,14 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
460457
preRoute := app.stack[m][l-1]
461458
preRoute.Handlers = append(preRoute.Handlers, route.Handlers...)
462459
} else {
463-
// Increment global route position
464-
route.pos = atomic.AddUint32(&app.routesCount, 1)
465460
route.Method = method
466461
// Add route to the stack
467462
app.stack[m] = append(app.stack[m], route)
468463
app.routesRefreshed = true
469464
}
470465

471466
// Execute onRoute hooks & change latestRoute if not adding mounted route
472-
if !mounted {
467+
if !route.mount {
473468
app.mutex.Lock()
474469
app.latestRoute = route
475470
if err := app.hooks.executeOnRouteHooks(*route); err != nil {
@@ -481,38 +476,59 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
481476

482477
// buildTree build the prefix tree from the previously registered routes
483478
func (app *App) buildTree() *App {
479+
// If routes haven't been refreshed, nothing to do
484480
if !app.routesRefreshed {
485481
return app
486482
}
487483

488-
// loop all the methods and stacks and create the prefix tree
489-
for m := range app.config.RequestMethods {
490-
tsMap := make(map[string][]*Route)
491-
for _, route := range app.stack[m] {
492-
treePath := ""
484+
// 1) First loop: determine all possible 3-char prefixes ("treePaths") for each method
485+
for method := range app.config.RequestMethods {
486+
prefixSet := map[string]struct{}{
487+
"": {},
488+
}
489+
for _, route := range app.stack[method] {
493490
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
494-
treePath = route.routeParser.segs[0].Const[:3]
491+
prefix := route.routeParser.segs[0].Const[:3]
492+
prefixSet[prefix] = struct{}{}
495493
}
496-
// create tree stack
497-
tsMap[treePath] = append(tsMap[treePath], route)
498494
}
499-
app.treeStack[m] = tsMap
500-
}
495+
tsMap := make(map[string][]*Route, len(prefixSet))
496+
for prefix := range prefixSet {
497+
tsMap[prefix] = nil
498+
}
499+
app.treeStack[method] = tsMap
500+
}
501+
502+
// 2) Second loop: for each method and each discovered treePath, assign matching routes
503+
for method := range app.config.RequestMethods {
504+
// get the map of buckets for this method
505+
tsMap := app.treeStack[method]
506+
507+
// for every treePath key (including the empty one)
508+
for treePath := range tsMap {
509+
// iterate all routes of this method
510+
for _, route := range app.stack[method] {
511+
// compute this route's own prefix ("" or first 3 chars)
512+
routePath := ""
513+
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
514+
routePath = route.routeParser.segs[0].Const[:3]
515+
}
501516

502-
// loop the methods and tree stacks and add global stack and sort everything
503-
for m := range app.config.RequestMethods {
504-
tsMap := app.treeStack[m]
505-
for treePart := range tsMap {
506-
if treePart != "" {
507-
// merge global tree routes in current tree stack
508-
tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[""]...))
517+
// if it's a global route, assign to every bucket
518+
if routePath == "" {
519+
tsMap[treePath] = append(tsMap[treePath], route)
520+
// otherwise only assign if this route's prefix matches the current bucket's key
521+
} else if routePath == treePath {
522+
tsMap[treePath] = append(tsMap[treePath], route)
523+
}
509524
}
510-
// sort tree slices with the positions
511-
slc := tsMap[treePart]
512-
sort.Slice(slc, func(i, j int) bool { return slc[i].pos < slc[j].pos })
525+
526+
// after collecting, dedupe the bucket if it's not the global one
527+
tsMap[treePath] = uniqueRouteStack(tsMap[treePath])
513528
}
514529
}
515-
app.routesRefreshed = false
516530

531+
// reset the flag and return
532+
app.routesRefreshed = false
517533
return app
518534
}

router_test.go

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import (
2020
"github.com/valyala/fasthttp"
2121
)
2222

23-
var routesFixture routeJSON
23+
var (
24+
routesFixture routeJSON
25+
cssDir = "./.github/testdata/fs/css"
26+
)
2427

2528
func init() {
2629
dat, err := os.ReadFile("./.github/testdata/testRoutes.json")
@@ -354,9 +357,8 @@ func Test_Router_Handler_Catch_Error(t *testing.T) {
354357
func Test_Route_Static_Root(t *testing.T) {
355358
t.Parallel()
356359

357-
dir := "./.github/testdata/fs/css"
358360
app := New()
359-
app.Static("/", dir, Static{
361+
app.Static("/", cssDir, Static{
360362
Browse: true,
361363
})
362364

@@ -373,7 +375,7 @@ func Test_Route_Static_Root(t *testing.T) {
373375
utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color"))
374376

375377
app = New()
376-
app.Static("/", dir)
378+
app.Static("/", cssDir)
377379

378380
resp, err = app.Test(httptest.NewRequest(MethodGet, "/", nil))
379381
utils.AssertEqual(t, nil, err, "app.Test(req)")
@@ -391,9 +393,8 @@ func Test_Route_Static_Root(t *testing.T) {
391393
func Test_Route_Static_HasPrefix(t *testing.T) {
392394
t.Parallel()
393395

394-
dir := "./.github/testdata/fs/css"
395396
app := New()
396-
app.Static("/static", dir, Static{
397+
app.Static("/static", cssDir, Static{
397398
Browse: true,
398399
})
399400

@@ -414,7 +415,7 @@ func Test_Route_Static_HasPrefix(t *testing.T) {
414415
utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color"))
415416

416417
app = New()
417-
app.Static("/static/", dir, Static{
418+
app.Static("/static/", cssDir, Static{
418419
Browse: true,
419420
})
420421

@@ -435,7 +436,7 @@ func Test_Route_Static_HasPrefix(t *testing.T) {
435436
utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color"))
436437

437438
app = New()
438-
app.Static("/static", dir)
439+
app.Static("/static", cssDir)
439440

440441
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static", nil))
441442
utils.AssertEqual(t, nil, err, "app.Test(req)")
@@ -454,7 +455,7 @@ func Test_Route_Static_HasPrefix(t *testing.T) {
454455
utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color"))
455456

456457
app = New()
457-
app.Static("/static/", dir)
458+
app.Static("/static/", cssDir)
458459

459460
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static", nil))
460461
utils.AssertEqual(t, nil, err, "app.Test(req)")
@@ -474,6 +475,8 @@ func Test_Route_Static_HasPrefix(t *testing.T) {
474475
}
475476

476477
func Test_Router_NotFound(t *testing.T) {
478+
t.Parallel()
479+
477480
app := New()
478481
app.Use(func(c *Ctx) error {
479482
return c.Next()
@@ -491,6 +494,8 @@ func Test_Router_NotFound(t *testing.T) {
491494
}
492495

493496
func Test_Router_NotFound_HTML_Inject(t *testing.T) {
497+
t.Parallel()
498+
494499
app := New()
495500
app.Use(func(c *Ctx) error {
496501
return c.Next()
@@ -507,6 +512,31 @@ func Test_Router_NotFound_HTML_Inject(t *testing.T) {
507512
utils.AssertEqual(t, "Cannot DELETE /does/not/exist&lt;script&gt;alert(&#39;foo&#39;);&lt;/script&gt;", string(c.Response.Body()))
508513
}
509514

515+
func Test_Router_Mount_n_Static(t *testing.T) {
516+
t.Parallel()
517+
518+
app := New()
519+
520+
app.Static("/static", cssDir, Static{Browse: true})
521+
app.Get("/", func(c *Ctx) error {
522+
return c.SendString("Home")
523+
})
524+
525+
subApp := New()
526+
app.Mount("/mount", subApp)
527+
subApp.Get("/test", func(c *Ctx) error {
528+
return c.SendString("Hello from /test")
529+
})
530+
531+
app.Use(func(c *Ctx) error {
532+
return c.Status(StatusNotFound).SendString("Not Found")
533+
})
534+
535+
resp, err := app.Test(httptest.NewRequest(MethodGet, "/static/style.css", nil))
536+
utils.AssertEqual(t, nil, err, "app.Test(req)")
537+
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
538+
}
539+
510540
//////////////////////////////////////////////
511541
///////////////// BENCHMARKS /////////////////
512542
//////////////////////////////////////////////

0 commit comments

Comments
 (0)