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