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  }