-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
mount.go
231 lines (196 loc) · 7.03 KB
/
mount.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
package fiber
import (
"sort"
"sync"
"sync/atomic"
"github.com/gofiber/utils/v2"
)
// Put fields related to mounting.
type mountFields struct {
// Mounted and main apps
appList map[string]*App
// Prefix of app if it was mounted
mountPath string
// Ordered keys of apps (sorted by key length for Render)
appListKeys []string
// check added routes of sub-apps
subAppsRoutesAdded sync.Once
// check mounted sub-apps
subAppsProcessed sync.Once
}
// Create empty mountFields instance
func newMountFields(app *App) *mountFields {
return &mountFields{
appList: map[string]*App{"": app},
appListKeys: make([]string, 0),
}
}
// Mount attaches another app instance as a sub-router along a routing path.
// It's very useful to split up a large API as many independent routers and
// compose them as a single service using Mount. The fiber's error handler and
// any of the fiber's sub apps are added to the application's error handlers
// to be invoked on errors that happen within the prefix route.
func (app *App) mount(prefix string, subApp *App) Router {
prefix = utils.TrimRight(prefix, '/')
if prefix == "" {
prefix = "/"
}
// Support for configs of mounted-apps and sub-mounted-apps
for mountedPrefixes, subApp := range subApp.mountFields.appList {
path := getGroupPath(prefix, mountedPrefixes)
subApp.mountFields.mountPath = path
app.mountFields.appList[path] = subApp
}
// register mounted group
mountGroup := &Group{Prefix: prefix, app: subApp}
app.register([]string{methodUse}, prefix, mountGroup, nil)
// Execute onMount hooks
if err := subApp.hooks.executeOnMountHooks(app); err != nil {
panic(err)
}
return app
}
// Mount attaches another app instance as a sub-router along a routing path.
// It's very useful to split up a large API as many independent routers and
// compose them as a single service using Mount.
func (grp *Group) mount(prefix string, subApp *App) Router {
groupPath := getGroupPath(grp.Prefix, prefix)
groupPath = utils.TrimRight(groupPath, '/')
if groupPath == "" {
groupPath = "/"
}
// Support for configs of mounted-apps and sub-mounted-apps
for mountedPrefixes, subApp := range subApp.mountFields.appList {
path := getGroupPath(groupPath, mountedPrefixes)
subApp.mountFields.mountPath = path
grp.app.mountFields.appList[path] = subApp
}
// register mounted group
mountGroup := &Group{Prefix: groupPath, app: subApp}
grp.app.register([]string{methodUse}, groupPath, mountGroup, nil)
// Execute onMount hooks
if err := subApp.hooks.executeOnMountHooks(grp.app); err != nil {
panic(err)
}
return grp
}
// The MountPath property contains one or more path patterns on which a sub-app was mounted.
func (app *App) MountPath() string {
return app.mountFields.mountPath
}
// hasMountedApps Checks if there are any mounted apps in the current application.
func (app *App) hasMountedApps() bool {
return len(app.mountFields.appList) > 1
}
// mountStartupProcess Handles the startup process of mounted apps by appending sub-app routes, generating app list keys, and processing sub-app routes.
func (app *App) mountStartupProcess() {
if app.hasMountedApps() {
// add routes of sub-apps
app.mountFields.subAppsProcessed.Do(func() {
app.appendSubAppLists(app.mountFields.appList)
app.generateAppListKeys()
})
// adds the routes of the sub-apps to the current application.
app.mountFields.subAppsRoutesAdded.Do(func() {
app.processSubAppsRoutes()
})
}
}
// generateAppListKeys generates app list keys for Render, should work after appendSubAppLists
func (app *App) generateAppListKeys() {
for key := range app.mountFields.appList {
app.mountFields.appListKeys = append(app.mountFields.appListKeys, key)
}
sort.Slice(app.mountFields.appListKeys, func(i, j int) bool {
return len(app.mountFields.appListKeys[i]) < len(app.mountFields.appListKeys[j])
})
}
// appendSubAppLists supports nested for sub apps
func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) {
// Optimize: Cache parent prefix
parentPrefix := ""
if len(parent) > 0 {
parentPrefix = parent[0]
}
for prefix, subApp := range appList {
// skip real app
if prefix == "" {
continue
}
if parentPrefix != "" {
prefix = getGroupPath(parentPrefix, prefix)
}
if _, ok := app.mountFields.appList[prefix]; !ok {
app.mountFields.appList[prefix] = subApp
}
// The first element of appList is always the app itself. If there are no other sub apps, we should skip appending nested apps.
if len(subApp.mountFields.appList) > 1 {
app.appendSubAppLists(subApp.mountFields.appList, prefix)
}
}
}
// processSubAppsRoutes adds routes of sub-apps recursively when the server is started
func (app *App) processSubAppsRoutes() {
for prefix, subApp := range app.mountFields.appList {
// skip real app
if prefix == "" {
continue
}
// process the inner routes
if subApp.hasMountedApps() {
subApp.mountFields.subAppsRoutesAdded.Do(func() {
subApp.processSubAppsRoutes()
})
}
}
var handlersCount uint32
var routePos uint32
// Iterate over the stack of the parent app
for m := range app.stack {
// Iterate over each route in the stack
stackLen := len(app.stack[m])
for i := 0; i < stackLen; i++ {
route := app.stack[m][i]
// Check if the route has a mounted app
if !route.mount {
routePos++
// If not, update the route's position and continue
route.pos = routePos
if !route.use || (route.use && m == 0) {
handlersCount += uint32(len(route.Handlers)) //nolint:gosec // Not a concern
}
continue
}
// Create a slice to hold the sub-app's routes
subRoutes := make([]*Route, len(route.group.app.stack[m]))
// Iterate over the sub-app's routes
for j, subAppRoute := range route.group.app.stack[m] {
// Clone the sub-app's route
subAppRouteClone := app.copyRoute(subAppRoute)
// Add the parent route's path as a prefix to the sub-app's route
app.addPrefixToRoute(route.path, subAppRouteClone)
// Add the cloned sub-app's route to the slice of sub-app routes
subRoutes[j] = subAppRouteClone
}
// Insert the sub-app's routes into the parent app's stack
newStack := make([]*Route, len(app.stack[m])+len(subRoutes)-1)
copy(newStack[:i], app.stack[m][:i])
copy(newStack[i:i+len(subRoutes)], subRoutes)
copy(newStack[i+len(subRoutes):], app.stack[m][i+1:])
app.stack[m] = newStack
// Decrease the parent app's route count to account for the mounted app's original route
atomic.AddUint32(&app.routesCount, ^uint32(0))
i--
// Increase the parent app's route count to account for the sub-app's routes
atomic.AddUint32(&app.routesCount, uint32(len(subRoutes))) //nolint:gosec // Not a concern
// Mark the parent app's routes as refreshed
app.routesRefreshed = true
// update stackLen after appending subRoutes to app.stack[m]
stackLen = len(app.stack[m])
}
}
atomic.StoreUint32(&app.handlersCount, handlersCount)
}