mount.go 6.7 KB

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