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

✨ feature: add initial support for hooks #1777

Merged
merged 30 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b0933d1
Add initial support for hooks.
efectn Feb 14, 2022
b43788d
release ctx, mutex.
efectn Feb 17, 2022
76a2e45
Add unit tests.
efectn Feb 18, 2022
c61eac9
add comment lines.
efectn Feb 18, 2022
124671b
update
efectn Feb 18, 2022
7292511
update
efectn Feb 18, 2022
649bb6c
Merge branch 'gofiber:master' into add-hooks-support
efectn Feb 19, 2022
2faecaa
remove unnecessary code.
efectn Feb 19, 2022
2e18ea0
Merge branch 'add-hooks-support' of https://github.com/efectn/fiber i…
efectn Feb 19, 2022
429ee79
fix race condition.
efectn Feb 19, 2022
8a04452
fix gosec.
efectn Feb 20, 2022
936a260
skip error handling for onshutdown and onresponse.
efectn Feb 20, 2022
495e722
update
efectn Feb 20, 2022
2d8dce2
separate hooks from app.go
efectn Feb 21, 2022
ca72c2c
make hooks field private, hook struct public and Hooks() func.
efectn Feb 23, 2022
7dd783b
remove onreq and onres because of they can be done by middlewares.
efectn Feb 25, 2022
5b6073a
OnGroupName method.
efectn Feb 25, 2022
a9605c3
Update hooks.go
efectn Feb 26, 2022
57de317
handle errors for name and groupname
efectn Feb 28, 2022
401e4c6
Merge branch 'add-hooks-support' of https://github.com/efectn/fiber i…
efectn Feb 28, 2022
bf99b34
fix tests.
efectn Feb 28, 2022
02fa494
Update app.go
hi019 Mar 2, 2022
1e4138e
use struct fields instead of map
efectn Mar 2, 2022
8efce6f
Merge branch 'add-hooks-support' of https://github.com/efectn/fiber i…
efectn Mar 2, 2022
4bb277e
add multi-handler.
efectn Mar 2, 2022
fecaa73
add onGroup, make prefix field public on Group struct.
efectn Mar 2, 2022
3dbf49b
Update hooks.go
efectn Mar 5, 2022
0223ed1
add newhooks method.
efectn Mar 7, 2022
8d9f0dc
✨ feature: add initial support for hooks
ReneWerner87 Mar 8, 2022
34592bc
remove ctx from hooks.
efectn Mar 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ type App struct {

// Mounted and main apps
appList map[string]*App

// Hooks
hooks Hooks
}

// Config is a struct holding the server settings.
Expand Down Expand Up @@ -466,6 +469,13 @@ func New(config ...Config) *App {
getString: utils.UnsafeString,
appList: make(map[string]*App),
}

// Define hooks
app.hooks = Hooks{
efectn marked this conversation as resolved.
Show resolved Hide resolved
app: app,
hookList: make(map[string][]HookHandler),
}

// Override config if provided
if len(config) > 0 {
app.config = config[0]
Expand Down Expand Up @@ -575,7 +585,12 @@ func (app *App) Name(name string) Router {
} else {
latestRoute.route.Name = name
}

if err := app.hooks.executeOnNameHooks(*latestRoute.route); err != nil {
panic(err)
}
latestRoute.mu.Unlock()

return app
}

Expand Down Expand Up @@ -859,6 +874,8 @@ func (app *App) HandlersCount() uint32 {
//
// Shutdown does not close keepalive connections so its recommended to set ReadTimeout to something else than 0.
func (app *App) Shutdown() error {
defer app.hooks.executeOnShutdownHooks()

app.mutex.Lock()
defer app.mutex.Unlock()
if app.server == nil {
Expand All @@ -872,6 +889,11 @@ func (app *App) Server() *fasthttp.Server {
return app.server
}

// Hooks returns the hook struct to register hooks.
func (app *App) Hooks() *Hooks {
return &app.hooks
}

// Test is used for internal debugging by passing a *http.Request.
// Timeout is optional and defaults to 1s, -1 will disable it completely.
func (app *App) Test(req *http.Request, msTimeout ...int) (resp *http.Response, err error) {
Expand Down Expand Up @@ -1038,6 +1060,10 @@ func (app *App) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) {

// startupProcess Is the method which executes all the necessary processes just before the start of the server.
func (app *App) startupProcess() *App {
if err := app.hooks.executeOnListenHooks(); err != nil {
panic(err)
}

app.mutex.Lock()
app.buildTree()
app.mutex.Unlock()
Expand Down
4 changes: 4 additions & 0 deletions app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ var testEmptyHandler = func(c *Ctx) error {
return nil
}

var testSimpleHandler = func(c *Ctx) error {
return c.SendString("simple")
}

func testStatus200(t *testing.T, app *App, url string, method string) {
t.Helper()

Expand Down
4 changes: 4 additions & 0 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ func (grp *Group) Name(name string) Router {

latestGroup = *grp

if err := grp.app.hooks.executeOnGroupNameHooks(latestGroup); err != nil {
panic(err)
}

return grp
}

Expand Down
118 changes: 118 additions & 0 deletions hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package fiber

import (
"github.com/valyala/fasthttp"
)

// Handler defines a function to create hooks for Fiber.
type HookHandler = func(*Ctx, Map) error

type Hooks struct {
app *App
hookList map[string][]HookHandler
}

// OnRoute is a hook to execute user functions on each route registeration.
// Also you can get route properties by "route" key of map.
func (h *Hooks) OnRoute(handler ...HookHandler) {
h.app.mutex.Lock()
h.hookList["onRoute"] = append(h.hookList["onRoute"], handler...)
h.app.mutex.Unlock()
}

// OnName is a hook to execute user functions on each route naming.
// Also you can get route properties by "route" key of map.
//
// WARN: OnName only works with naming routes, not groups.
func (h *Hooks) OnName(handler ...HookHandler) {
h.app.mutex.Lock()
h.hookList["onName"] = append(h.hookList["onName"], handler...)
h.app.mutex.Unlock()
}

// OnGroupName is a hook to execute user functions on each group naming.
// Also you can get group properties by "group" key of map.
//
// WARN: OnGroupName only works with naming groups, not routes.
func (h *Hooks) OnGroupName(handler ...HookHandler) {
h.app.mutex.Lock()
h.hookList["onGroupName"] = append(h.hookList["onGroupName"], handler...)
h.app.mutex.Unlock()
}

// OnListen is a hook to execute user functions on Listen, ListenTLS, Listener.
func (h *Hooks) OnListen(handler ...HookHandler) {
h.app.mutex.Lock()
h.hookList["onListen"] = append(h.hookList["onListen"], handler...)
h.app.mutex.Unlock()
}

// OnShutdown is a hook to execute user functions after Shutdown.
func (h *Hooks) OnShutdown(handler ...HookHandler) {
h.app.mutex.Lock()
h.hookList["onShutdown"] = append(h.hookList["onShutdown"], handler...)
h.app.mutex.Unlock()
}

func (h *Hooks) executeOnRouteHooks(route Route) error {
for _, v := range h.hookList["onRoute"] {
ctx := h.app.AcquireCtx(&fasthttp.RequestCtx{})
defer h.app.ReleaseCtx(ctx)

if err := v(ctx, Map{"route": route}); err != nil {
return err
}
}

return nil
}

func (h *Hooks) executeOnNameHooks(route Route) error {
for _, v := range h.hookList["onName"] {
ctx := h.app.AcquireCtx(&fasthttp.RequestCtx{})
defer h.app.ReleaseCtx(ctx)

if err := v(ctx, Map{"route": route}); err != nil {
return err
}
}

return nil
}

func (h *Hooks) executeOnGroupNameHooks(group Group) error {
for _, v := range h.hookList["onGroupName"] {
ctx := h.app.AcquireCtx(&fasthttp.RequestCtx{})
defer h.app.ReleaseCtx(ctx)

if err := v(ctx, Map{"group": group}); err != nil {
return err
}
}

return nil
}

func (h *Hooks) executeOnListenHooks() error {
for _, v := range h.hookList["onListen"] {
ctx := h.app.AcquireCtx(&fasthttp.RequestCtx{})
defer h.app.ReleaseCtx(ctx)

if err := v(ctx, Map{}); err != nil {
return err
}
}

return nil
}

func (h *Hooks) executeOnShutdownHooks() {
if len(h.hookList["onShutdown"]) > 0 {
for _, v := range h.hookList["onShutdown"] {
ctx := h.app.AcquireCtx(&fasthttp.RequestCtx{})
defer h.app.ReleaseCtx(ctx)

_ = v(ctx, Map{})
}
}
}
140 changes: 140 additions & 0 deletions hooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package fiber

import (
"errors"
"fmt"
"testing"
"time"

"github.com/gofiber/fiber/v2/internal/bytebufferpool"
"github.com/gofiber/fiber/v2/utils"
)

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

app.Hooks().OnRoute(func(c *Ctx, m Map) error {
utils.AssertEqual(t, "", m["route"].(Route).Name)

return nil
})

app.Get("/", testSimpleHandler).Name("x")

subApp := New()
subApp.Get("/test", testSimpleHandler)

app.Mount("/sub", subApp)
}

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

buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf)

app.Hooks().OnName(func(c *Ctx, m Map) error {
buf.WriteString(m["route"].(Route).Name)

return nil
})

app.Get("/", testSimpleHandler).Name("index")

subApp := New()
subApp.Get("/test", testSimpleHandler)
subApp.Get("/test2", testSimpleHandler)

app.Mount("/sub", subApp)

utils.AssertEqual(t, "index", buf.String())
}

func Test_Hook_OnName_Error(t *testing.T) {
app := New()
defer func() {
if err := recover(); err != nil {
utils.AssertEqual(t, "unknown error", fmt.Sprintf("%v", err))
}
}()

app.Hooks().OnName(func(c *Ctx, m Map) error {
return errors.New("unknown error")
})

app.Get("/", testSimpleHandler).Name("index")
}

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

buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf)

app.Hooks().OnGroupName(func(c *Ctx, m Map) error {
buf.WriteString(m["group"].(Group).name)

return nil
})

grp := app.Group("/x").Name("x.")
grp.Get("/test", testSimpleHandler)
grp.Get("/test2", testSimpleHandler)

utils.AssertEqual(t, "x.", buf.String())
}

func Test_Hook_OnGroupName_Error(t *testing.T) {
app := New()
defer func() {
if err := recover(); err != nil {
utils.AssertEqual(t, "unknown error", fmt.Sprintf("%v", err))
}
}()

app.Hooks().OnGroupName(func(c *Ctx, m Map) error {
return errors.New("unknown error")
})

grp := app.Group("/x").Name("x.")
grp.Get("/test", testSimpleHandler)
}

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

buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf)

app.Hooks().OnShutdown(func(c *Ctx, m Map) error {
buf.WriteString("shutdowning")

return nil
})

utils.AssertEqual(t, nil, app.Shutdown())
utils.AssertEqual(t, "shutdowning", buf.String())
}

func Test_Hook_OnListen(t *testing.T) {
app := New(Config{
DisableStartupMessage: true,
})

buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf)

app.Hooks().OnListen(func(c *Ctx, m Map) error {
buf.WriteString("ready")

return nil
})

go func() {
time.Sleep(1000 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
}()
utils.AssertEqual(t, nil, app.Listen(":9000"))

utils.AssertEqual(t, "ready", buf.String())
}
4 changes: 4 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ func (app *App) handler(rctx *fasthttp.RequestCtx) {
if match && app.config.ETag {
setETag(c, false)
}

// Release Ctx
app.ReleaseCtx(c)
}
Expand Down Expand Up @@ -437,6 +438,9 @@ func (app *App) addRoute(method string, route *Route) {

latestRoute.mu.Lock()
latestRoute.route = route
if err := app.hooks.executeOnRouteHooks(*route); err != nil {
panic(err)
}
latestRoute.mu.Unlock()
}

Expand Down