mirror of https://github.com/gofiber/fiber.git
🐛 bug: improve mounting behavior (#2120)
* 🐛 bug: fix mounting doesn't work if when to declare it before routes * 🐛 bug: fix mounting doesn't work if when to declare it before routes * 🐛 bug: fix mounting doesn't work if when to declare it before routes * 🐛 bug: fix mounting doesn't work if when to declare it before routes * 🐛 bug: fix mounting doesn't work if when to declare it before routes * add onMount hooks, mountPath like express.js, better behavior for onName, onRoute, onGroup, onGroupName hooks on mounted apps * add comment * use once * fix views when both app and sub-app have view engine, better behavior for mount path * fix tests * fix tests * make some tasks * make some taskspull/2173/head
parent
5b1885a44c
commit
156b81c768
|
@ -0,0 +1 @@
|
||||||
|
<h1>I'm Bruh</h1>
|
56
app.go
56
app.go
|
@ -18,7 +18,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -106,8 +105,6 @@ type App struct {
|
||||||
getBytes func(s string) (b []byte)
|
getBytes func(s string) (b []byte)
|
||||||
// Converts byte slice to a string
|
// Converts byte slice to a string
|
||||||
getString func(b []byte) string
|
getString func(b []byte) string
|
||||||
// Mounted and main apps
|
|
||||||
appList map[string]*App
|
|
||||||
// Hooks
|
// Hooks
|
||||||
hooks *Hooks
|
hooks *Hooks
|
||||||
// Latest route & group
|
// Latest route & group
|
||||||
|
@ -115,6 +112,8 @@ type App struct {
|
||||||
latestGroup *Group
|
latestGroup *Group
|
||||||
// TLS handler
|
// TLS handler
|
||||||
tlsHandler *TLSHandler
|
tlsHandler *TLSHandler
|
||||||
|
// Mount fields
|
||||||
|
mountFields *mountFields
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is a struct holding the server settings.
|
// Config is a struct holding the server settings.
|
||||||
|
@ -485,7 +484,6 @@ func New(config ...Config) *App {
|
||||||
config: Config{},
|
config: Config{},
|
||||||
getBytes: utils.UnsafeBytes,
|
getBytes: utils.UnsafeBytes,
|
||||||
getString: utils.UnsafeString,
|
getString: utils.UnsafeString,
|
||||||
appList: make(map[string]*App),
|
|
||||||
latestRoute: &Route{},
|
latestRoute: &Route{},
|
||||||
latestGroup: &Group{},
|
latestGroup: &Group{},
|
||||||
}
|
}
|
||||||
|
@ -493,6 +491,9 @@ func New(config ...Config) *App {
|
||||||
// Define hooks
|
// Define hooks
|
||||||
app.hooks = newHooks(app)
|
app.hooks = newHooks(app)
|
||||||
|
|
||||||
|
// Define mountFields
|
||||||
|
app.mountFields = newMountFields(app)
|
||||||
|
|
||||||
// Override config if provided
|
// Override config if provided
|
||||||
if len(config) > 0 {
|
if len(config) > 0 {
|
||||||
app.config = config[0]
|
app.config = config[0]
|
||||||
|
@ -549,9 +550,6 @@ func New(config ...Config) *App {
|
||||||
// Override colors
|
// Override colors
|
||||||
app.config.ColorScheme = defaultColors(app.config.ColorScheme)
|
app.config.ColorScheme = defaultColors(app.config.ColorScheme)
|
||||||
|
|
||||||
// Init appList
|
|
||||||
app.appList[""] = app
|
|
||||||
|
|
||||||
// Init app
|
// Init app
|
||||||
app.init()
|
app.init()
|
||||||
|
|
||||||
|
@ -582,36 +580,6 @@ func (app *App) SetTLSHandler(tlsHandler *TLSHandler) {
|
||||||
app.mutex.Unlock()
|
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.AddUint32(&app.handlersCount, fiber.handlersCount)
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name Assign name to specific route.
|
// Name Assign name to specific route.
|
||||||
func (app *App) Name(name string) Router {
|
func (app *App) Name(name string) Router {
|
||||||
app.mutex.Lock()
|
app.mutex.Lock()
|
||||||
|
@ -991,7 +959,7 @@ func (app *App) ErrorHandler(ctx *Ctx, err error) error {
|
||||||
mountedPrefixParts int
|
mountedPrefixParts int
|
||||||
)
|
)
|
||||||
|
|
||||||
for prefix, subApp := range app.appList {
|
for prefix, subApp := range app.mountFields.appList {
|
||||||
if prefix != "" && strings.HasPrefix(ctx.path, prefix) {
|
if prefix != "" && strings.HasPrefix(ctx.path, prefix) {
|
||||||
parts := len(strings.Split(prefix, "/"))
|
parts := len(strings.Split(prefix, "/"))
|
||||||
if mountedPrefixParts <= parts {
|
if mountedPrefixParts <= parts {
|
||||||
|
@ -1041,7 +1009,17 @@ func (app *App) startupProcess() *App {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.mutex.Lock()
|
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.buildTree()
|
||||||
app.mutex.Unlock()
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
161
app_test.go
161
app_test.go
|
@ -246,44 +246,6 @@ func Test_App_ErrorHandler_RouteStack(t *testing.T) {
|
||||||
utils.AssertEqual(t, "1: USE error", string(body))
|
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) {
|
func Test_App_Nested_Params(t *testing.T) {
|
||||||
app := New()
|
app := New()
|
||||||
|
|
||||||
|
@ -307,22 +269,6 @@ func Test_App_Nested_Params(t *testing.T) {
|
||||||
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
|
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) {
|
func Test_App_Use_Params(t *testing.T) {
|
||||||
app := New()
|
app := New()
|
||||||
|
|
||||||
|
@ -1046,23 +992,6 @@ func Test_App_Group_Invalid(t *testing.T) {
|
||||||
New().Group("/").Use(1)
|
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) {
|
func Test_App_Group(t *testing.T) {
|
||||||
dummyHandler := testEmptyHandler
|
dummyHandler := testEmptyHandler
|
||||||
|
|
||||||
|
@ -1538,96 +1467,6 @@ func Test_App_DisablePreParseMultipartForm(t *testing.T) {
|
||||||
utils.AssertEqual(t, testString, string(body))
|
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) {
|
func Test_App_Test_no_timeout_infinitely(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
c := make(chan int)
|
c := make(chan int)
|
||||||
|
|
12
ctx.go
12
ctx.go
|
@ -1384,11 +1384,13 @@ func (c *Ctx) Render(name string, bind interface{}, layouts ...string) error {
|
||||||
buf := bytebufferpool.Get()
|
buf := bytebufferpool.Get()
|
||||||
defer bytebufferpool.Put(buf)
|
defer bytebufferpool.Put(buf)
|
||||||
|
|
||||||
// Pass-locals-to-views & bind
|
// Pass-locals-to-views, bind, appListKeys
|
||||||
c.renderExtensions(bind)
|
c.renderExtensions(bind)
|
||||||
|
|
||||||
rendered := false
|
var rendered bool
|
||||||
for prefix, app := range c.app.appList {
|
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 prefix == "" || strings.Contains(c.OriginalURL(), prefix) {
|
||||||
if len(layouts) == 0 && app.config.ViewsLayout != "" {
|
if len(layouts) == 0 && app.config.ViewsLayout != "" {
|
||||||
layouts = []string{
|
layouts = []string{
|
||||||
|
@ -1454,6 +1456,10 @@ func (c *Ctx) renderExtensions(bind interface{}) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(c.app.mountFields.appListKeys) == 0 {
|
||||||
|
c.app.generateAppListKeys()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route returns the matched Route struct.
|
// Route returns the matched Route struct.
|
||||||
|
|
59
ctx_test.go
59
ctx_test.go
|
@ -32,7 +32,6 @@ import (
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2/internal/bytebufferpool"
|
"github.com/gofiber/fiber/v2/internal/bytebufferpool"
|
||||||
"github.com/gofiber/fiber/v2/internal/storage/memory"
|
"github.com/gofiber/fiber/v2/internal/storage/memory"
|
||||||
"github.com/gofiber/fiber/v2/internal/template/html"
|
|
||||||
"github.com/gofiber/fiber/v2/utils"
|
"github.com/gofiber/fiber/v2/utils"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
)
|
)
|
||||||
|
@ -2792,58 +2791,6 @@ func Test_Ctx_Render(t *testing.T) {
|
||||||
utils.AssertEqual(t, false, err == nil)
|
utils.AssertEqual(t, false, err == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// go test -run Test_Ctx_Render_Mount
|
|
||||||
func Test_Ctx_Render_Mount(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
sub := New(Config{
|
|
||||||
Views: html.New("./.github/testdata/template", ".gohtml"),
|
|
||||||
})
|
|
||||||
|
|
||||||
sub.Get("/:name", func(ctx *Ctx) error {
|
|
||||||
return ctx.Render("hello_world", Map{
|
|
||||||
"Name": ctx.Params("name"),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
app := New()
|
|
||||||
app.Mount("/hello", sub)
|
|
||||||
|
|
||||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/hello/a", nil))
|
|
||||||
utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
|
|
||||||
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
utils.AssertEqual(t, nil, err)
|
|
||||||
utils.AssertEqual(t, "<h1>Hello a!</h1>", string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Ctx_Render_MountGroup(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
micro := New(Config{
|
|
||||||
Views: html.New("./.github/testdata/template", ".gohtml"),
|
|
||||||
})
|
|
||||||
|
|
||||||
micro.Get("/doe", func(c *Ctx) error {
|
|
||||||
return c.Render("hello_world", Map{
|
|
||||||
"Name": "doe",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
utils.AssertEqual(t, nil, err)
|
|
||||||
utils.AssertEqual(t, "<h1>Hello doe!</h1>", string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Ctx_RenderWithoutLocals(t *testing.T) {
|
func Test_Ctx_RenderWithoutLocals(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New(Config{
|
app := New(Config{
|
||||||
|
@ -3149,6 +3096,7 @@ func Test_Ctx_RestartRoutingWithChangedPathAndCatchAll(t *testing.T) {
|
||||||
|
|
||||||
type testTemplateEngine struct {
|
type testTemplateEngine struct {
|
||||||
templates *template.Template
|
templates *template.Template
|
||||||
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testTemplateEngine) Render(w io.Writer, name string, bind interface{}, layout ...string) error {
|
func (t *testTemplateEngine) Render(w io.Writer, name string, bind interface{}, layout ...string) error {
|
||||||
|
@ -3160,7 +3108,10 @@ func (t *testTemplateEngine) Render(w io.Writer, name string, bind interface{},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testTemplateEngine) Load() error {
|
func (t *testTemplateEngine) Load() error {
|
||||||
t.templates = template.Must(template.ParseGlob("./.github/testdata/*.tmpl"))
|
if t.path == "" {
|
||||||
|
t.path = "testdata"
|
||||||
|
}
|
||||||
|
t.templates = template.Must(template.ParseGlob("./.github/" + t.path + "/*.tmpl"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
30
group.go
30
group.go
|
@ -8,7 +8,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Group struct
|
// Group struct
|
||||||
|
@ -19,35 +18,6 @@ type Group struct {
|
||||||
Prefix string
|
Prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (grp *Group) Mount(prefix string, fiber *App) Router {
|
|
||||||
stack := fiber.Stack()
|
|
||||||
groupPath := getGroupPath(grp.Prefix, prefix)
|
|
||||||
groupPath = strings.TrimRight(groupPath, "/")
|
|
||||||
if groupPath == "" {
|
|
||||||
groupPath = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
for m := range stack {
|
|
||||||
for r := range stack[m] {
|
|
||||||
route := grp.app.copyRoute(stack[m][r])
|
|
||||||
grp.app.addRoute(route.Method, grp.app.addPrefixToRoute(groupPath, route))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support for configs of mounted-apps and sub-mounted-apps
|
|
||||||
for mountedPrefixes, subApp := range fiber.appList {
|
|
||||||
grp.app.appList[groupPath+mountedPrefixes] = subApp
|
|
||||||
subApp.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.AddUint32(&grp.app.handlersCount, fiber.handlersCount)
|
|
||||||
|
|
||||||
return grp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name Assign name to specific route.
|
// Name Assign name to specific route.
|
||||||
func (grp *Group) Name(name string) Router {
|
func (grp *Group) Name(name string) Router {
|
||||||
grp.app.mutex.Lock()
|
grp.app.mutex.Lock()
|
||||||
|
|
44
hooks.go
44
hooks.go
|
@ -8,6 +8,7 @@ type OnGroupNameHandler = OnGroupHandler
|
||||||
type OnListenHandler = func() error
|
type OnListenHandler = func() error
|
||||||
type OnShutdownHandler = OnListenHandler
|
type OnShutdownHandler = OnListenHandler
|
||||||
type OnForkHandler = func(int) error
|
type OnForkHandler = func(int) error
|
||||||
|
type OnMountHandler = func(*App) error
|
||||||
|
|
||||||
// Hooks is a struct to use it with App.
|
// Hooks is a struct to use it with App.
|
||||||
type Hooks struct {
|
type Hooks struct {
|
||||||
|
@ -22,6 +23,7 @@ type Hooks struct {
|
||||||
onListen []OnListenHandler
|
onListen []OnListenHandler
|
||||||
onShutdown []OnShutdownHandler
|
onShutdown []OnShutdownHandler
|
||||||
onFork []OnForkHandler
|
onFork []OnForkHandler
|
||||||
|
onMount []OnMountHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHooks(app *App) *Hooks {
|
func newHooks(app *App) *Hooks {
|
||||||
|
@ -34,6 +36,7 @@ func newHooks(app *App) *Hooks {
|
||||||
onListen: make([]OnListenHandler, 0),
|
onListen: make([]OnListenHandler, 0),
|
||||||
onShutdown: make([]OnShutdownHandler, 0),
|
onShutdown: make([]OnShutdownHandler, 0),
|
||||||
onFork: make([]OnForkHandler, 0),
|
onFork: make([]OnForkHandler, 0),
|
||||||
|
onMount: make([]OnMountHandler, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +97,22 @@ func (h *Hooks) OnFork(handler ...OnForkHandler) {
|
||||||
h.app.mutex.Unlock()
|
h.app.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnMount is a hook to execute user function after mounting process.
|
||||||
|
// The mount event is fired when sub-app is mounted on a parent app. The parent app is passed as a parameter.
|
||||||
|
// It works for app and group mounting.
|
||||||
|
func (h *Hooks) OnMount(handler ...OnMountHandler) {
|
||||||
|
h.app.mutex.Lock()
|
||||||
|
h.onMount = append(h.onMount, handler...)
|
||||||
|
h.app.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Hooks) executeOnRouteHooks(route Route) error {
|
func (h *Hooks) executeOnRouteHooks(route Route) error {
|
||||||
|
// Check mounting
|
||||||
|
if h.app.mountFields.mountPath != "" {
|
||||||
|
route.path = h.app.mountFields.mountPath + route.path
|
||||||
|
route.Path = route.path
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range h.onRoute {
|
for _, v := range h.onRoute {
|
||||||
if err := v(route); err != nil {
|
if err := v(route); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -105,6 +123,12 @@ func (h *Hooks) executeOnRouteHooks(route Route) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hooks) executeOnNameHooks(route Route) error {
|
func (h *Hooks) executeOnNameHooks(route Route) error {
|
||||||
|
// Check mounting
|
||||||
|
if h.app.mountFields.mountPath != "" {
|
||||||
|
route.path = h.app.mountFields.mountPath + route.path
|
||||||
|
route.Path = route.path
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range h.onName {
|
for _, v := range h.onName {
|
||||||
if err := v(route); err != nil {
|
if err := v(route); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -115,6 +139,11 @@ func (h *Hooks) executeOnNameHooks(route Route) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hooks) executeOnGroupHooks(group Group) error {
|
func (h *Hooks) executeOnGroupHooks(group Group) error {
|
||||||
|
// Check mounting
|
||||||
|
if h.app.mountFields.mountPath != "" {
|
||||||
|
group.Prefix = h.app.mountFields.mountPath + group.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range h.onGroup {
|
for _, v := range h.onGroup {
|
||||||
if err := v(group); err != nil {
|
if err := v(group); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -125,6 +154,11 @@ func (h *Hooks) executeOnGroupHooks(group Group) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hooks) executeOnGroupNameHooks(group Group) error {
|
func (h *Hooks) executeOnGroupNameHooks(group Group) error {
|
||||||
|
// Check mounting
|
||||||
|
if h.app.mountFields.mountPath != "" {
|
||||||
|
group.Prefix = h.app.mountFields.mountPath + group.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range h.onGroupName {
|
for _, v := range h.onGroupName {
|
||||||
if err := v(group); err != nil {
|
if err := v(group); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -155,3 +189,13 @@ func (h *Hooks) executeOnForkHooks(pid int) {
|
||||||
_ = v(pid)
|
_ = v(pid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hooks) executeOnMountHooks(app *App) error {
|
||||||
|
for _, v := range h.onMount {
|
||||||
|
if err := v(app); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -33,6 +33,29 @@ func Test_Hook_OnRoute(t *testing.T) {
|
||||||
app.Mount("/sub", subApp)
|
app.Mount("/sub", subApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Hook_OnRoute_Mount(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
app := New()
|
||||||
|
subApp := New()
|
||||||
|
app.Mount("/sub", subApp)
|
||||||
|
|
||||||
|
subApp.Hooks().OnRoute(func(r Route) error {
|
||||||
|
utils.AssertEqual(t, "/sub/test", r.Path)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Hooks().OnRoute(func(r Route) error {
|
||||||
|
utils.AssertEqual(t, "/", r.Path)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/", testSimpleHandler).Name("x")
|
||||||
|
subApp.Get("/test", testSimpleHandler)
|
||||||
|
}
|
||||||
|
|
||||||
func Test_Hook_OnName(t *testing.T) {
|
func Test_Hook_OnName(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -96,6 +119,24 @@ func Test_Hook_OnGroup(t *testing.T) {
|
||||||
utils.AssertEqual(t, "/x/x/a", buf.String())
|
utils.AssertEqual(t, "/x/x/a", buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Hook_OnGroup_Mount(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
app := New()
|
||||||
|
micro := New()
|
||||||
|
micro.Mount("/john", app)
|
||||||
|
|
||||||
|
app.Hooks().OnGroup(func(g Group) error {
|
||||||
|
utils.AssertEqual(t, "/john/v1", g.Prefix)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
v1 := app.Group("/v1")
|
||||||
|
v1.Get("/doe", func(c *Ctx) error {
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func Test_Hook_OnGroupName(t *testing.T) {
|
func Test_Hook_OnGroupName(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -200,3 +241,21 @@ func Test_Hook_OnHook(t *testing.T) {
|
||||||
|
|
||||||
utils.AssertEqual(t, nil, app.prefork(NetworkTCP4, ":3000", nil))
|
utils.AssertEqual(t, nil, app.prefork(NetworkTCP4, ":3000", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Hook_OnMount(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
app := New()
|
||||||
|
app.Get("/", testSimpleHandler).Name("x")
|
||||||
|
|
||||||
|
subApp := New()
|
||||||
|
subApp.Get("/test", testSimpleHandler)
|
||||||
|
|
||||||
|
subApp.Hooks().OnMount(func(parent *App) error {
|
||||||
|
utils.AssertEqual(t, parent.mountFields.mountPath, "")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Mount("/sub", subApp)
|
||||||
|
}
|
||||||
|
|
|
@ -183,15 +183,6 @@ func (e *Engine) Load() error {
|
||||||
|
|
||||||
// Render will execute the template name along with the given values.
|
// Render will execute the template name along with the given values.
|
||||||
func (e *Engine) Render(out io.Writer, template string, binding interface{}, layout ...string) error {
|
func (e *Engine) Render(out io.Writer, template string, binding interface{}, layout ...string) error {
|
||||||
if !e.loaded || e.reload {
|
|
||||||
if e.reload {
|
|
||||||
e.loaded = false
|
|
||||||
}
|
|
||||||
if err := e.Load(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpl := e.Templates.Lookup(template)
|
tmpl := e.Templates.Lookup(template)
|
||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
return fmt.Errorf("render: template %s does not exist", template)
|
return fmt.Errorf("render: template %s does not exist", template)
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
|
||||||
|
// 🤖 Github Repository: https://github.com/gofiber/fiber
|
||||||
|
// 📌 API Documentation: https://docs.gofiber.io
|
||||||
|
|
||||||
|
package fiber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Put fields related to mounting.
|
||||||
|
type mountFields struct {
|
||||||
|
// Mounted and main apps
|
||||||
|
appList map[string]*App
|
||||||
|
// Ordered keys of apps (sorted by key length for Render)
|
||||||
|
appListKeys []string
|
||||||
|
// check added routes of sub-apps
|
||||||
|
subAppsRoutesAdded sync.Once
|
||||||
|
// Prefix of app if it was mounted
|
||||||
|
mountPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create empty mountFields instance
|
||||||
|
func newMountFields(app *App) *mountFields {
|
||||||
|
return &mountFields{
|
||||||
|
appList: map[string]*App{"": app},
|
||||||
|
appListKeys: make([]string, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
prefix = strings.TrimRight(prefix, "/")
|
||||||
|
if prefix == "" {
|
||||||
|
prefix = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support for configs of mounted-apps and sub-mounted-apps
|
||||||
|
for mountedPrefixes, subApp := range fiber.mountFields.appList {
|
||||||
|
subApp.mountFields.mountPath = prefix + mountedPrefixes
|
||||||
|
app.mountFields.appList[prefix+mountedPrefixes] = subApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute onMount hooks
|
||||||
|
if err := fiber.hooks.executeOnMountHooks(app); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (grp *Group) Mount(prefix string, fiber *App) Router {
|
||||||
|
groupPath := getGroupPath(grp.Prefix, prefix)
|
||||||
|
groupPath = strings.TrimRight(groupPath, "/")
|
||||||
|
if groupPath == "" {
|
||||||
|
groupPath = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support for configs of mounted-apps and sub-mounted-apps
|
||||||
|
for mountedPrefixes, subApp := range fiber.mountFields.appList {
|
||||||
|
subApp.mountFields.mountPath = groupPath + mountedPrefixes
|
||||||
|
grp.app.mountFields.appList[groupPath+mountedPrefixes] = subApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute onMount hooks
|
||||||
|
if err := fiber.hooks.executeOnMountHooks(grp.app); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return grp
|
||||||
|
}
|
||||||
|
|
||||||
|
// The MountPath property contains one or more path patterns on which a sub-app was mounted.
|
||||||
|
func (app *App) MountPath() string {
|
||||||
|
return app.mountFields.mountPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateAppListKeys generates app list keys for Render, should work after appendSubAppLists
|
||||||
|
func (app *App) generateAppListKeys() {
|
||||||
|
for key := range app.mountFields.appList {
|
||||||
|
app.mountFields.appListKeys = append(app.mountFields.appListKeys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(app.mountFields.appListKeys, func(i, j int) bool {
|
||||||
|
return len(app.mountFields.appListKeys[i]) < len(app.mountFields.appListKeys[j])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendSubAppLists supports nested for sub apps
|
||||||
|
func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) {
|
||||||
|
for prefix, subApp := range appList {
|
||||||
|
// skip real app
|
||||||
|
if prefix == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parent) > 0 {
|
||||||
|
prefix = parent[0] + prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := app.mountFields.appList[prefix]; !ok {
|
||||||
|
app.mountFields.appList[prefix] = subApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first element of appList is always the app itself. If there are no other sub apps, we should skip appending nested apps.
|
||||||
|
if len(subApp.mountFields.appList) > 1 {
|
||||||
|
app.appendSubAppLists(subApp.mountFields.appList, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addSubAppsRoutes adds routes of sub apps nestedly when to start the server
|
||||||
|
func (app *App) addSubAppsRoutes(appList map[string]*App, parent ...string) {
|
||||||
|
for prefix, subApp := range appList {
|
||||||
|
// skip real app
|
||||||
|
if prefix == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parent) > 0 {
|
||||||
|
prefix = parent[0] + prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// add routes
|
||||||
|
stack := subApp.stack
|
||||||
|
for m := range stack {
|
||||||
|
for r := range stack[m] {
|
||||||
|
route := app.copyRoute(stack[m][r])
|
||||||
|
app.addRoute(route.Method, app.addPrefixToRoute(prefix, route), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddUint32(&app.handlersCount, subApp.handlersCount)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,351 @@
|
||||||
|
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
|
||||||
|
// 🤖 Github Repository: https://github.com/gofiber/fiber
|
||||||
|
// 📌 API Documentation: https://docs.gofiber.io
|
||||||
|
|
||||||
|
package fiber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2/internal/template/html"
|
||||||
|
"github.com/gofiber/fiber/v2/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// go test -run Test_App_Mount_Nested
|
||||||
|
func Test_App_Mount_Nested(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
one := New()
|
||||||
|
two := New()
|
||||||
|
three := New()
|
||||||
|
|
||||||
|
two.Mount("/three", three)
|
||||||
|
app.Mount("/one", one)
|
||||||
|
one.Mount("/two", two)
|
||||||
|
|
||||||
|
one.Get("/doe", func(c *Ctx) error {
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
two.Get("/nested", func(c *Ctx) error {
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
three.Get("/test", func(c *Ctx) error {
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
resp, err := app.Test(httptest.NewRequest(MethodGet, "/one/doe", nil))
|
||||||
|
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
||||||
|
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
|
||||||
|
|
||||||
|
resp, err = app.Test(httptest.NewRequest(MethodGet, "/one/two/nested", nil))
|
||||||
|
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
||||||
|
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
|
||||||
|
|
||||||
|
resp, err = app.Test(httptest.NewRequest(MethodGet, "/one/two/three/test", nil))
|
||||||
|
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
||||||
|
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
|
||||||
|
|
||||||
|
utils.AssertEqual(t, uint32(6), app.handlersCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// go test -run Test_App_MountPath
|
||||||
|
func Test_App_MountPath(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
one := New()
|
||||||
|
two := New()
|
||||||
|
three := New()
|
||||||
|
|
||||||
|
two.Mount("/three", three)
|
||||||
|
one.Mount("/two", two)
|
||||||
|
app.Mount("/one", one)
|
||||||
|
|
||||||
|
utils.AssertEqual(t, "/one", one.MountPath())
|
||||||
|
utils.AssertEqual(t, "/one/two", two.MountPath())
|
||||||
|
utils.AssertEqual(t, "/one/two/three", three.MountPath())
|
||||||
|
utils.AssertEqual(t, "", app.MountPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// go test -run Test_Ctx_Render_Mount
|
||||||
|
func Test_Ctx_Render_Mount(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
sub := New(Config{
|
||||||
|
Views: html.New("./.github/testdata/template", ".gohtml"),
|
||||||
|
})
|
||||||
|
|
||||||
|
sub.Get("/:name", func(ctx *Ctx) error {
|
||||||
|
return ctx.Render("hello_world", Map{
|
||||||
|
"Name": ctx.Params("name"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app := New()
|
||||||
|
app.Mount("/hello", sub)
|
||||||
|
|
||||||
|
resp, err := app.Test(httptest.NewRequest(MethodGet, "/hello/a", nil))
|
||||||
|
utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
|
||||||
|
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
utils.AssertEqual(t, nil, err)
|
||||||
|
utils.AssertEqual(t, "<h1>Hello a!</h1>", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// go test -run Test_Ctx_Render_Mount_ParentOrSubHasViews
|
||||||
|
func Test_Ctx_Render_Mount_ParentOrSubHasViews(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
engine := &testTemplateEngine{}
|
||||||
|
err := engine.Load()
|
||||||
|
utils.AssertEqual(t, nil, err)
|
||||||
|
|
||||||
|
engine2 := &testTemplateEngine{path: "testdata2"}
|
||||||
|
err = engine2.Load()
|
||||||
|
utils.AssertEqual(t, nil, err)
|
||||||
|
|
||||||
|
sub := New(Config{
|
||||||
|
Views: html.New("./.github/testdata/template", ".gohtml"),
|
||||||
|
})
|
||||||
|
|
||||||
|
sub2 := New(Config{
|
||||||
|
Views: engine2,
|
||||||
|
})
|
||||||
|
|
||||||
|
app := New(Config{
|
||||||
|
Views: engine,
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/test", func(c *Ctx) error {
|
||||||
|
return c.Render("index.tmpl", Map{
|
||||||
|
"Title": "Hello, World!",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
sub.Get("/world/:name", func(c *Ctx) error {
|
||||||
|
return c.Render("hello_world", Map{
|
||||||
|
"Name": c.Params("name"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
sub2.Get("/moment", func(c *Ctx) error {
|
||||||
|
return c.Render("bruh.tmpl", Map{})
|
||||||
|
})
|
||||||
|
|
||||||
|
sub.Mount("/bruh", sub2)
|
||||||
|
app.Mount("/hello", sub)
|
||||||
|
|
||||||
|
resp, err := app.Test(httptest.NewRequest(MethodGet, "/hello/world/a", nil))
|
||||||
|
utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
|
||||||
|
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
utils.AssertEqual(t, nil, err)
|
||||||
|
utils.AssertEqual(t, "<h1>Hello a!</h1>", string(body))
|
||||||
|
|
||||||
|
resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil))
|
||||||
|
utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
|
||||||
|
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(resp.Body)
|
||||||
|
utils.AssertEqual(t, nil, err)
|
||||||
|
utils.AssertEqual(t, "<h1>Hello, World!</h1>", string(body))
|
||||||
|
|
||||||
|
resp, err = app.Test(httptest.NewRequest(MethodGet, "/hello/bruh/moment", nil))
|
||||||
|
utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
|
||||||
|
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(resp.Body)
|
||||||
|
utils.AssertEqual(t, nil, err)
|
||||||
|
utils.AssertEqual(t, "<h1>I'm Bruh</h1>", string(body))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Ctx_Render_MountGroup(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
micro := New(Config{
|
||||||
|
Views: html.New("./.github/testdata/template", ".gohtml"),
|
||||||
|
})
|
||||||
|
|
||||||
|
micro.Get("/doe", func(c *Ctx) error {
|
||||||
|
return c.Render("hello_world", Map{
|
||||||
|
"Name": "doe",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
utils.AssertEqual(t, nil, err)
|
||||||
|
utils.AssertEqual(t, "<h1>Hello doe!</h1>", string(body))
|
||||||
|
}
|
13
router.go
13
router.go
|
@ -423,7 +423,13 @@ func (app *App) registerStatic(prefix, root string, config ...Static) Router {
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) addRoute(method string, route *Route) {
|
func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
|
||||||
|
// Check mounted routes
|
||||||
|
var mounted bool
|
||||||
|
if len(isMounted) > 0 {
|
||||||
|
mounted = isMounted[0]
|
||||||
|
}
|
||||||
|
|
||||||
// Get unique HTTP method identifier
|
// Get unique HTTP method identifier
|
||||||
m := methodInt(method)
|
m := methodInt(method)
|
||||||
|
|
||||||
|
@ -441,6 +447,8 @@ func (app *App) addRoute(method string, route *Route) {
|
||||||
app.routesRefreshed = true
|
app.routesRefreshed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execute onRoute hooks & change latestRoute if not adding mounted route
|
||||||
|
if !mounted {
|
||||||
app.mutex.Lock()
|
app.mutex.Lock()
|
||||||
app.latestRoute = route
|
app.latestRoute = route
|
||||||
if err := app.hooks.executeOnRouteHooks(*route); err != nil {
|
if err := app.hooks.executeOnRouteHooks(*route); err != nil {
|
||||||
|
@ -448,12 +456,14 @@ func (app *App) addRoute(method string, route *Route) {
|
||||||
}
|
}
|
||||||
app.mutex.Unlock()
|
app.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// buildTree build the prefix tree from the previously registered routes
|
// buildTree build the prefix tree from the previously registered routes
|
||||||
func (app *App) buildTree() *App {
|
func (app *App) buildTree() *App {
|
||||||
if !app.routesRefreshed {
|
if !app.routesRefreshed {
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop all the methods and stacks and create the prefix tree
|
// loop all the methods and stacks and create the prefix tree
|
||||||
for m := range intMethod {
|
for m := range intMethod {
|
||||||
tsMap := make(map[string][]*Route)
|
tsMap := make(map[string][]*Route)
|
||||||
|
@ -467,6 +477,7 @@ func (app *App) buildTree() *App {
|
||||||
}
|
}
|
||||||
app.treeStack[m] = tsMap
|
app.treeStack[m] = tsMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop the methods and tree stacks and add global stack and sort everything
|
// loop the methods and tree stacks and add global stack and sort everything
|
||||||
for m := range intMethod {
|
for m := range intMethod {
|
||||||
tsMap := app.treeStack[m]
|
tsMap := app.treeStack[m]
|
||||||
|
|
Loading…
Reference in New Issue