Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 improve mounting behavior #2120

Merged
merged 13 commits into from Oct 25, 2022
1 change: 1 addition & 0 deletions .github/testdata2/bruh.tmpl
@@ -0,0 +1 @@
<h1>I'm Bruh</h1>
56 changes: 17 additions & 39 deletions app.go
Expand Up @@ -19,7 +19,6 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

"encoding/json"
Expand Down Expand Up @@ -107,15 +106,15 @@ type App struct {
getBytes func(s string) (b []byte)
// Converts byte slice to a string
getString func(b []byte) string
// Mounted and main apps
appList map[string]*App
// Hooks
hooks *Hooks
// Latest route & group
latestRoute *Route
latestGroup *Group
// TLS handler
tlsHandler *TLSHandler
// Mount fields
mountFields *mountFields
}

// Config is a struct holding the server settings.
Expand Down Expand Up @@ -481,14 +480,16 @@ func New(config ...Config) *App {
config: Config{},
getBytes: utils.UnsafeBytes,
getString: utils.UnsafeString,
appList: make(map[string]*App),
latestRoute: &Route{},
latestGroup: &Group{},
}

// Define hooks
app.hooks = newHooks(app)

// Define mountFields
app.mountFields = newMountFields(app)

// Override config if provided
if len(config) > 0 {
app.config = config[0]
Expand Down Expand Up @@ -545,9 +546,6 @@ func New(config ...Config) *App {
// Override colors
app.config.ColorScheme = defaultColors(app.config.ColorScheme)

// Init appList
app.appList[""] = app

// Init app
app.init()

Expand Down Expand Up @@ -578,36 +576,6 @@ func (app *App) SetTLSHandler(tlsHandler *TLSHandler) {
app.mutex.Unlock()
}

// 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, fiber *App) Router {
stack := fiber.Stack()
prefix = strings.TrimRight(prefix, "/")
if prefix == "" {
prefix = "/"
}

for m := range stack {
for r := range stack[m] {
route := app.copyRoute(stack[m][r])
app.addRoute(route.Method, app.addPrefixToRoute(prefix, route))
}
}

// Support for configs of mounted-apps and sub-mounted-apps
for mountedPrefixes, subApp := range fiber.appList {
app.appList[prefix+mountedPrefixes] = subApp
subApp.init()
efectn marked this conversation as resolved.
Show resolved Hide resolved
}

atomic.AddUint32(&app.handlersCount, fiber.handlersCount)

return app
}

// Name Assign name to specific route.
func (app *App) Name(name string) Router {
app.mutex.Lock()
Expand Down Expand Up @@ -974,7 +942,7 @@ func (app *App) ErrorHandler(ctx *Ctx, err error) error {
mountedPrefixParts int
)

for prefix, subApp := range app.appList {
for prefix, subApp := range app.mountFields.appList {
if prefix != "" && strings.HasPrefix(ctx.path, prefix) {
parts := len(strings.Split(prefix, "/"))
if mountedPrefixParts <= parts {
Expand Down Expand Up @@ -1024,7 +992,17 @@ func (app *App) startupProcess() *App {
}

app.mutex.Lock()
defer app.mutex.Unlock()

// add routes of sub-apps
app.mountFields.subAppsRoutesAdded.Do(func() {
app.appendSubAppLists(app.mountFields.appList)
app.addSubAppsRoutes(app.mountFields.appList)
app.generateAppListKeys()
})

// build route tree stack
app.buildTree()
app.mutex.Unlock()

return app
}
161 changes: 0 additions & 161 deletions app_test.go
Expand Up @@ -246,44 +246,6 @@ func Test_App_ErrorHandler_RouteStack(t *testing.T) {
utils.AssertEqual(t, "1: USE error", string(body))
}

func Test_App_ErrorHandler_GroupMount(t *testing.T) {
micro := New(Config{
ErrorHandler: func(c *Ctx, err error) error {
utils.AssertEqual(t, "0: GET error", err.Error())
return c.Status(500).SendString("1: custom error")
},
})
micro.Get("/doe", func(c *Ctx) error {
return errors.New("0: GET error")
})

app := New()
v1 := app.Group("/v1")
v1.Mount("/john", micro)

resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil))
testErrorResponse(t, err, resp, "1: custom error")
}

func Test_App_ErrorHandler_GroupMountRootLevel(t *testing.T) {
micro := New(Config{
ErrorHandler: func(c *Ctx, err error) error {
utils.AssertEqual(t, "0: GET error", err.Error())
return c.Status(500).SendString("1: custom error")
},
})
micro.Get("/john/doe", func(c *Ctx) error {
return errors.New("0: GET error")
})

app := New()
v1 := app.Group("/v1")
v1.Mount("/", micro)

resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil))
testErrorResponse(t, err, resp, "1: custom error")
}

func Test_App_Nested_Params(t *testing.T) {
app := New()

Expand All @@ -307,22 +269,6 @@ func Test_App_Nested_Params(t *testing.T) {
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
}

// go test -run Test_App_Mount
func Test_App_Mount(t *testing.T) {
micro := New()
micro.Get("/doe", func(c *Ctx) error {
return c.SendStatus(StatusOK)
})

app := New()
app.Mount("/john", micro)

resp, err := app.Test(httptest.NewRequest(MethodGet, "/john/doe", nil))
utils.AssertEqual(t, nil, err, "app.Test(req)")
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
utils.AssertEqual(t, uint32(2), app.handlersCount)
}

func Test_App_Use_Params(t *testing.T) {
app := New()

Expand Down Expand Up @@ -1026,23 +972,6 @@ func Test_App_Group_Invalid(t *testing.T) {
New().Group("/").Use(1)
}

// go test -run Test_App_Group_Mount
func Test_App_Group_Mount(t *testing.T) {
micro := New()
micro.Get("/doe", func(c *Ctx) error {
return c.SendStatus(StatusOK)
})

app := New()
v1 := app.Group("/v1")
v1.Mount("/john", micro)

resp, err := app.Test(httptest.NewRequest(MethodGet, "/v1/john/doe", nil))
utils.AssertEqual(t, nil, err, "app.Test(req)")
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
utils.AssertEqual(t, uint32(2), app.handlersCount)
}

func Test_App_Group(t *testing.T) {
dummyHandler := testEmptyHandler

Expand Down Expand Up @@ -1512,96 +1441,6 @@ func Test_App_DisablePreParseMultipartForm(t *testing.T) {
utils.AssertEqual(t, testString, string(body))
}

func Test_App_UseMountedErrorHandler(t *testing.T) {
app := New()

fiber := New(Config{
ErrorHandler: func(ctx *Ctx, err error) error {
return ctx.Status(500).SendString("hi, i'm a custom error")
},
})
fiber.Get("/", func(c *Ctx) error {
return errors.New("something happened")
})

app.Mount("/api", fiber)

resp, err := app.Test(httptest.NewRequest(MethodGet, "/api", nil))
testErrorResponse(t, err, resp, "hi, i'm a custom error")
}

func Test_App_UseMountedErrorHandlerRootLevel(t *testing.T) {
app := New()

fiber := New(Config{
ErrorHandler: func(ctx *Ctx, err error) error {
return ctx.Status(500).SendString("hi, i'm a custom error")
},
})
fiber.Get("/api", func(c *Ctx) error {
return errors.New("something happened")
})

app.Mount("/", fiber)

resp, err := app.Test(httptest.NewRequest(MethodGet, "/api", nil))
testErrorResponse(t, err, resp, "hi, i'm a custom error")
}

func Test_App_UseMountedErrorHandlerForBestPrefixMatch(t *testing.T) {
app := New()

tsf := func(ctx *Ctx, err error) error {
return ctx.Status(200).SendString("hi, i'm a custom sub sub fiber error")
}
tripleSubFiber := New(Config{
ErrorHandler: tsf,
})
tripleSubFiber.Get("/", func(c *Ctx) error {
return errors.New("something happened")
})

sf := func(ctx *Ctx, err error) error {
return ctx.Status(200).SendString("hi, i'm a custom sub fiber error")
}
subfiber := New(Config{
ErrorHandler: sf,
})
subfiber.Get("/", func(c *Ctx) error {
return errors.New("something happened")
})
subfiber.Mount("/third", tripleSubFiber)

f := func(ctx *Ctx, err error) error {
return ctx.Status(200).SendString("hi, i'm a custom error")
}
fiber := New(Config{
ErrorHandler: f,
})
fiber.Get("/", func(c *Ctx) error {
return errors.New("something happened")
})
fiber.Mount("/sub", subfiber)

app.Mount("/api", fiber)

resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/sub", nil))
utils.AssertEqual(t, nil, err, "/api/sub req")
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")

b, err := ioutil.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err, "iotuil.ReadAll()")
utils.AssertEqual(t, "hi, i'm a custom sub fiber error", string(b), "Response body")

resp2, err := app.Test(httptest.NewRequest(MethodGet, "/api/sub/third", nil))
utils.AssertEqual(t, nil, err, "/api/sub/third req")
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")

b, err = ioutil.ReadAll(resp2.Body)
utils.AssertEqual(t, nil, err, "iotuil.ReadAll()")
utils.AssertEqual(t, "hi, i'm a custom sub sub fiber error", string(b), "Third fiber Response body")
}

func Test_App_Test_no_timeout_infinitely(t *testing.T) {
var err error
c := make(chan int)
Expand Down
12 changes: 9 additions & 3 deletions ctx.go
Expand Up @@ -1333,11 +1333,13 @@ func (c *Ctx) Render(name string, bind interface{}, layouts ...string) error {
buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf)

// Pass-locals-to-views & bind
// Pass-locals-to-views, bind, appListKeys
c.renderExtensions(bind)

rendered := false
for prefix, app := range c.app.appList {
var rendered bool
for i := len(c.app.mountFields.appListKeys) - 1; i >= 0; i-- {
prefix := c.app.mountFields.appListKeys[i]
app := c.app.mountFields.appList[prefix]
if prefix == "" || strings.Contains(c.OriginalURL(), prefix) {
if len(layouts) == 0 && app.config.ViewsLayout != "" {
layouts = []string{
Expand Down Expand Up @@ -1403,6 +1405,10 @@ func (c *Ctx) renderExtensions(bind interface{}) {
})
}
}

if len(c.app.mountFields.appListKeys) == 0 {
c.app.generateAppListKeys()
}
}

// Route returns the matched Route struct.
Expand Down