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 26 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
69 changes: 50 additions & 19 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,13 @@ 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
}

// Config is a struct holding the server settings.
Expand Down Expand Up @@ -423,14 +427,6 @@ const (
DefaultCompressedFileSuffix = ".fiber.gz"
)

// Variables for Name & GetRoute
var latestRoute struct {
route *Route
mu sync.Mutex
}

var latestGroup Group

// DefaultErrorHandler that process return errors from handlers
var DefaultErrorHandler = func(c *Ctx, err error) error {
code := StatusInternalServerError
Expand Down Expand Up @@ -461,11 +457,25 @@ func New(config ...Config) *App {
},
},
// Create config
config: Config{},
getBytes: utils.UnsafeBytes,
getString: utils.UnsafeString,
appList: make(map[string]*App),
config: Config{},
getBytes: utils.UnsafeBytes,
getString: utils.UnsafeString,
appList: make(map[string]*App),
latestRoute: &Route{},
latestGroup: &Group{},
}

// Define hooks
app.hooks = Hooks{
efectn marked this conversation as resolved.
Show resolved Hide resolved
app: app,
onRoute: make([]OnRouteHandler, 0),
onGroup: make([]OnGroupHandler, 0),
onGroupName: make([]OnGroupNameHandler, 0),
onName: make([]OnNameHandler, 0),
onListen: make([]OnListenHandler, 0),
onShutdown: make([]OnShutdownHandler, 0),
}

// Override config if provided
if len(config) > 0 {
app.config = config[0]
Expand Down Expand Up @@ -569,13 +579,18 @@ func (app *App) Mount(prefix string, fiber *App) Router {

// Assign name to specific route.
func (app *App) Name(name string) Router {
latestRoute.mu.Lock()
if strings.HasPrefix(latestRoute.route.path, latestGroup.prefix) {
latestRoute.route.Name = latestGroup.name + name
app.mutex.Lock()
if strings.HasPrefix(app.latestRoute.path, app.latestGroup.Prefix) {
app.latestRoute.Name = app.latestGroup.name + name
} else {
latestRoute.route.Name = name
app.latestRoute.Name = name
}

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

return app
}

Expand Down Expand Up @@ -702,7 +717,12 @@ func (app *App) Group(prefix string, handlers ...Handler) Router {
if len(handlers) > 0 {
app.register(methodUse, prefix, handlers...)
}
return &Group{prefix: prefix, app: app}
grp := &Group{Prefix: prefix, app: app}
if err := app.hooks.executeOnGroupHooks(*grp); err != nil {
panic(err)
}

return grp
}

// Route is used to define routes with a common prefix inside the common function.
Expand Down Expand Up @@ -859,6 +879,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 +894,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 +1065,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
31 changes: 19 additions & 12 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ import (

// Group struct
type Group struct {
app *App
prefix string
name string
app *App
name 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 := getGroupPath(grp.Prefix, prefix)

for m := range stack {
for r := range stack[m] {
Expand All @@ -46,13 +47,19 @@ func (grp *Group) Mount(prefix string, fiber *App) Router {

// Assign name to specific route.
func (grp *Group) Name(name string) Router {
if strings.HasPrefix(grp.prefix, latestGroup.prefix) {
grp.name = latestGroup.name + name
grp.app.mutex.Lock()
if strings.HasPrefix(grp.Prefix, grp.app.latestGroup.Prefix) {
grp.name = grp.app.latestGroup.name + name
} else {
grp.name = name
}

latestGroup = *grp
grp.app.latestGroup = grp

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

return grp
}
Expand Down Expand Up @@ -84,14 +91,14 @@ func (grp *Group) Use(args ...interface{}) Router {
panic(fmt.Sprintf("use: invalid handler %v\n", reflect.TypeOf(arg)))
}
}
grp.app.register(methodUse, getGroupPath(grp.prefix, prefix), handlers...)
grp.app.register(methodUse, getGroupPath(grp.Prefix, prefix), handlers...)
return grp
}

// Get registers a route for GET methods that requests a representation
// of the specified resource. Requests using GET should only retrieve data.
func (grp *Group) Get(path string, handlers ...Handler) Router {
path = getGroupPath(grp.prefix, path)
path = getGroupPath(grp.Prefix, path)
return grp.app.Add(MethodHead, path, handlers...).Add(MethodGet, path, handlers...)
}

Expand Down Expand Up @@ -144,12 +151,12 @@ func (grp *Group) Patch(path string, handlers ...Handler) Router {

// Add allows you to specify a HTTP method to register a route
func (grp *Group) Add(method, path string, handlers ...Handler) Router {
return grp.app.register(method, getGroupPath(grp.prefix, path), handlers...)
return grp.app.register(method, getGroupPath(grp.Prefix, path), handlers...)
}

// Static will create a file server serving static files
func (grp *Group) Static(prefix, root string, config ...Static) Router {
return grp.app.registerStatic(getGroupPath(grp.prefix, prefix), root, config...)
return grp.app.registerStatic(getGroupPath(grp.Prefix, prefix), root, config...)
}

// All will register the handler on all HTTP methods
Expand All @@ -164,7 +171,7 @@ func (grp *Group) All(path string, handlers ...Handler) Router {
// api := app.Group("/api")
// api.Get("/users", handler)
func (grp *Group) Group(prefix string, handlers ...Handler) Router {
prefix = getGroupPath(grp.prefix, prefix)
prefix = getGroupPath(grp.Prefix, prefix)
if len(handlers) > 0 {
_ = grp.app.register(methodUse, prefix, handlers...)
}
Expand Down
150 changes: 150 additions & 0 deletions hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package fiber

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

// Handlers define a function to create hooks for Fiber.
type OnRouteHandler = func(*Ctx, Route) error
type OnNameHandler = OnRouteHandler
type OnGroupHandler = func(*Ctx, Group) error
type OnGroupNameHandler = OnGroupHandler
type OnListenHandler = Handler
type OnShutdownHandler = Handler

type Hooks struct {
// Embed app
app *App

// Hooks
onRoute []OnRouteHandler
onName []OnNameHandler
onGroup []OnGroupHandler
onGroupName []OnGroupNameHandler
onListen []OnListenHandler
onShutdown []OnShutdownHandler
}

// OnRoute is a hook to execute user functions on each route registeration.
// Also you can get route properties by route parameter.
func (h *Hooks) OnRoute(handler ...OnRouteHandler) {
h.app.mutex.Lock()
h.onRoute = append(h.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 parameter.
//
// WARN: OnName only works with naming routes, not groups.
func (h *Hooks) OnName(handler ...OnNameHandler) {
h.app.mutex.Lock()
h.onName = append(h.onName, handler...)
h.app.mutex.Unlock()
}

// OnGroup is a hook to execute user functions on each group registeration.
// Also you can get route properties by group parameter.
func (h *Hooks) OnGroup(handler ...OnGroupHandler) {
h.app.mutex.Lock()
h.onGroup = append(h.onGroup, 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 parameter.
//
// WARN: OnGroupName only works with naming groups, not routes.
func (h *Hooks) OnGroupName(handler ...OnGroupNameHandler) {
h.app.mutex.Lock()
h.onGroupName = append(h.onGroupName, handler...)
h.app.mutex.Unlock()
}

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

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

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

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

return nil
}

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

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

return nil
}

func (h *Hooks) executeOnGroupHooks(group Group) error {
for _, v := range h.onGroup {
ctx := h.app.AcquireCtx(&fasthttp.RequestCtx{})
defer h.app.ReleaseCtx(ctx)

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

return nil
}

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

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

return nil
}

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

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

return nil
}

func (h *Hooks) executeOnShutdownHooks() {
for _, v := range h.onShutdown {
ctx := h.app.AcquireCtx(&fasthttp.RequestCtx{})
defer h.app.ReleaseCtx(ctx)

_ = v(ctx)
}
}
Loading