mount.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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. "strings"
  8. "sync"
  9. "sync/atomic"
  10. )
  11. // Put fields related to mounting.
  12. type mountFields struct {
  13. // Mounted and main apps
  14. appList map[string]*App
  15. // Ordered keys of apps (sorted by key length for Render)
  16. appListKeys []string
  17. // check added routes of sub-apps
  18. subAppsRoutesAdded sync.Once
  19. // check mounted sub-apps
  20. subAppsProcessed sync.Once
  21. // Prefix of app if it was mounted
  22. mountPath string
  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 = strings.TrimRight(prefix, "/")
  38. if prefix == "" {
  39. prefix = "/"
  40. }
  41. // Support for configs of mounted-apps and sub-mounted-apps
  42. for mountedPrefixes, subApp := range subApp.mountFields.appList {
  43. path := getGroupPath(prefix, mountedPrefixes)
  44. subApp.mountFields.mountPath = path
  45. app.mountFields.appList[path] = subApp
  46. }
  47. // register mounted group
  48. mountGroup := &Group{Prefix: prefix, app: subApp}
  49. app.register(methodUse, prefix, mountGroup)
  50. // Execute onMount hooks
  51. if err := subApp.hooks.executeOnMountHooks(app); err != nil {
  52. panic(err)
  53. }
  54. return app
  55. }
  56. // Mount attaches another app instance as a sub-router along a routing path.
  57. // It's very useful to split up a large API as many independent routers and
  58. // compose them as a single service using Mount.
  59. func (grp *Group) Mount(prefix string, subApp *App) Router {
  60. groupPath := getGroupPath(grp.Prefix, prefix)
  61. groupPath = strings.TrimRight(groupPath, "/")
  62. if groupPath == "" {
  63. groupPath = "/"
  64. }
  65. // Support for configs of mounted-apps and sub-mounted-apps
  66. for mountedPrefixes, subApp := range subApp.mountFields.appList {
  67. path := getGroupPath(groupPath, mountedPrefixes)
  68. subApp.mountFields.mountPath = path
  69. grp.app.mountFields.appList[path] = subApp
  70. }
  71. // register mounted group
  72. mountGroup := &Group{Prefix: groupPath, app: subApp}
  73. grp.app.register(methodUse, groupPath, mountGroup)
  74. // Execute onMount hooks
  75. if err := subApp.hooks.executeOnMountHooks(grp.app); err != nil {
  76. panic(err)
  77. }
  78. return grp
  79. }
  80. // The MountPath property contains one or more path patterns on which a sub-app was mounted.
  81. func (app *App) MountPath() string {
  82. return app.mountFields.mountPath
  83. }
  84. // hasMountedApps Checks if there are any mounted apps in the current application.
  85. func (app *App) hasMountedApps() bool {
  86. return len(app.mountFields.appList) > 1
  87. }
  88. // mountStartupProcess Handles the startup process of mounted apps by appending sub-app routes, generating app list keys, and processing sub-app routes.
  89. func (app *App) mountStartupProcess() {
  90. if app.hasMountedApps() {
  91. // add routes of sub-apps
  92. app.mountFields.subAppsProcessed.Do(func() {
  93. app.appendSubAppLists(app.mountFields.appList)
  94. app.generateAppListKeys()
  95. })
  96. // adds the routes of the sub-apps to the current application.
  97. app.mountFields.subAppsRoutesAdded.Do(func() {
  98. app.processSubAppsRoutes()
  99. })
  100. }
  101. }
  102. // generateAppListKeys generates app list keys for Render, should work after appendSubAppLists
  103. func (app *App) generateAppListKeys() {
  104. for key := range app.mountFields.appList {
  105. app.mountFields.appListKeys = append(app.mountFields.appListKeys, key)
  106. }
  107. sort.Slice(app.mountFields.appListKeys, func(i, j int) bool {
  108. return len(app.mountFields.appListKeys[i]) < len(app.mountFields.appListKeys[j])
  109. })
  110. }
  111. // appendSubAppLists supports nested for sub apps
  112. func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) {
  113. // Optimize: Cache parent prefix
  114. parentPrefix := ""
  115. if len(parent) > 0 {
  116. parentPrefix = parent[0]
  117. }
  118. for prefix, subApp := range appList {
  119. // skip real app
  120. if prefix == "" {
  121. continue
  122. }
  123. if parentPrefix != "" {
  124. prefix = getGroupPath(parentPrefix, prefix)
  125. }
  126. if _, ok := app.mountFields.appList[prefix]; !ok {
  127. app.mountFields.appList[prefix] = subApp
  128. }
  129. // The first element of appList is always the app itself. If there are no other sub apps, we should skip appending nested apps.
  130. if len(subApp.mountFields.appList) > 1 {
  131. app.appendSubAppLists(subApp.mountFields.appList, prefix)
  132. }
  133. }
  134. }
  135. // processSubAppsRoutes adds routes of sub-apps recursively when the server is started
  136. func (app *App) processSubAppsRoutes() {
  137. for prefix, subApp := range app.mountFields.appList {
  138. // skip real app
  139. if prefix == "" {
  140. continue
  141. }
  142. // process the inner routes
  143. if subApp.hasMountedApps() {
  144. subApp.mountFields.subAppsRoutesAdded.Do(func() {
  145. subApp.processSubAppsRoutes()
  146. })
  147. }
  148. }
  149. var handlersCount uint32
  150. var routePos uint32
  151. // Iterate over the stack of the parent app
  152. for m := range app.stack {
  153. // Iterate over each route in the stack
  154. stackLen := len(app.stack[m])
  155. for i := 0; i < stackLen; i++ {
  156. route := app.stack[m][i]
  157. // Check if the route has a mounted app
  158. if !route.mount {
  159. routePos++
  160. // If not, update the route's position and continue
  161. route.pos = routePos
  162. if !route.use || (route.use && m == 0) {
  163. handlersCount += uint32(len(route.Handlers))
  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. // Decrease the parent app's route count to account for the mounted app's original route
  185. atomic.AddUint32(&app.routesCount, ^uint32(0))
  186. i--
  187. // Increase the parent app's route count to account for the sub-app's routes
  188. atomic.AddUint32(&app.routesCount, uint32(len(subRoutes)))
  189. // Mark the parent app's routes as refreshed
  190. app.routesRefreshed = true
  191. // update stackLen after appending subRoutes to app.stack[m]
  192. stackLen = len(app.stack[m])
  193. }
  194. }
  195. atomic.StoreUint32(&app.handlersCount, handlersCount)
  196. }