Skip to content

Commit

Permalink
✨ feature: add initial support for hooks (#1777)
Browse files Browse the repository at this point in the history
* Add initial support for hooks.

* release ctx, mutex.

* Add unit tests.

* add comment lines.

* update

* update

* remove unnecessary code.

* fix race condition.

* fix gosec.

* skip error handling for onshutdown and onresponse.

* update

* separate hooks from app.go

* make hooks field private, hook struct public and Hooks() func.

* remove onreq and onres because of they can be done by middlewares.

* OnGroupName method.

* Update hooks.go

Co-authored-by: hi019 <65871571+hi019@users.noreply.github.com>

* handle errors for name and groupname

* fix tests.

* Update app.go

* use struct fields instead of map

* add multi-handler.

* add onGroup, make prefix field public on Group struct.

* Update hooks.go

* add newhooks method.

* ✨ feature: add initial support for hooks

* remove ctx from hooks.

Co-authored-by: hi019 <65871571+hi019@users.noreply.github.com>
Co-authored-by: wernerr <rene@gofiber.io>
  • Loading branch information
3 people authored Mar 10, 2022
1 parent 166e55e commit bd20e90
Show file tree
Hide file tree
Showing 5 changed files with 389 additions and 34 deletions.
63 changes: 44 additions & 19 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,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 @@ -424,14 +428,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 @@ -462,11 +458,17 @@ 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 = newHooks(app)

// Override config if provided
if len(config) > 0 {
app.config = config[0]
Expand Down Expand Up @@ -570,13 +572,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
}
latestRoute.mu.Unlock()

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

return app
}

Expand Down Expand Up @@ -703,7 +710,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 @@ -919,6 +931,10 @@ 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 {
if app.hooks != nil {
defer app.hooks.executeOnShutdownHooks()
}

app.mutex.Lock()
defer app.mutex.Unlock()
if app.server == nil {
Expand All @@ -932,6 +948,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 @@ -1098,6 +1119,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
141 changes: 141 additions & 0 deletions hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package fiber

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

type hooks struct {
// Embed app
app *App

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

func newHooks(app *App) *hooks {
return &hooks{
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),
}
}

// 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 group 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 {
if err := v(route); err != nil {
return err
}
}

return nil
}

func (h *hooks) executeOnNameHooks(route Route) error {

for _, v := range h.onName {
if err := v(route); err != nil {
return err
}
}

return nil
}

func (h *hooks) executeOnGroupHooks(group Group) error {
for _, v := range h.onGroup {
if err := v(group); err != nil {
return err
}
}

return nil
}

func (h *hooks) executeOnGroupNameHooks(group Group) error {
for _, v := range h.onGroupName {
if err := v(group); err != nil {
return err
}
}

return nil
}

func (h *hooks) executeOnListenHooks() error {
for _, v := range h.onListen {
if err := v(); err != nil {
return err
}
}

return nil
}

func (h *hooks) executeOnShutdownHooks() {
for _, v := range h.onShutdown {
_ = v()
}
}
Loading

0 comments on commit bd20e90

Please sign in to comment.