hooks.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. package fiber
  2. import (
  3. "slices"
  4. "github.com/gofiber/fiber/v3/log"
  5. )
  6. type (
  7. // OnRouteHandler defines the hook signature invoked whenever a route is registered.
  8. OnRouteHandler = func(Route) error
  9. // OnNameHandler shares the OnRouteHandler signature for route naming callbacks.
  10. OnNameHandler = OnRouteHandler
  11. // OnGroupHandler defines the hook signature invoked whenever a group is registered.
  12. OnGroupHandler = func(Group) error
  13. // OnGroupNameHandler shares the OnGroupHandler signature for group naming callbacks.
  14. OnGroupNameHandler = OnGroupHandler
  15. // OnListenHandler runs when the application begins listening and receives the listener details.
  16. OnListenHandler = func(ListenData) error
  17. // OnPreStartupMessageHandler runs before Fiber prints the startup banner.
  18. OnPreStartupMessageHandler = func(*PreStartupMessageData) error
  19. // OnPostStartupMessageHandler runs after Fiber prints (or skips) the startup banner.
  20. OnPostStartupMessageHandler = func(*PostStartupMessageData) error
  21. // OnPreShutdownHandler runs before the application shuts down.
  22. OnPreShutdownHandler = func() error
  23. // OnPostShutdownHandler runs after shutdown and receives the shutdown result.
  24. OnPostShutdownHandler = func(error) error
  25. // OnForkHandler runs inside a forked worker process and receives the worker ID.
  26. OnForkHandler = func(int) error
  27. // OnMountHandler runs after a sub-application mounts to a parent and receives the parent app reference.
  28. OnMountHandler = func(*App) error
  29. )
  30. // Hooks is a struct to use it with App.
  31. type Hooks struct {
  32. // Embed app
  33. app *App
  34. // Hooks
  35. onRoute []OnRouteHandler
  36. onName []OnNameHandler
  37. onGroup []OnGroupHandler
  38. onGroupName []OnGroupNameHandler
  39. onListen []OnListenHandler
  40. onPreStartup []OnPreStartupMessageHandler
  41. onPostStartup []OnPostStartupMessageHandler
  42. onPreShutdown []OnPreShutdownHandler
  43. onPostShutdown []OnPostShutdownHandler
  44. onFork []OnForkHandler
  45. onMount []OnMountHandler
  46. }
  47. type StartupMessageLevel int
  48. const (
  49. // StartupMessageLevelInfo represents informational startup message entries.
  50. StartupMessageLevelInfo StartupMessageLevel = iota
  51. // StartupMessageLevelWarning represents warning startup message entries.
  52. StartupMessageLevelWarning
  53. // StartupMessageLevelError represents error startup message entries.
  54. StartupMessageLevelError
  55. )
  56. const errString = "ERROR"
  57. // startupMessageEntry represents a single line of startup message information.
  58. type startupMessageEntry struct {
  59. key string
  60. title string
  61. value string
  62. priority int
  63. level StartupMessageLevel
  64. }
  65. // ListenData contains the listener metadata provided to OnListenHandler.
  66. type ListenData struct {
  67. ColorScheme Colors
  68. Host string
  69. Port string
  70. Version string
  71. AppName string
  72. ChildPIDs []int
  73. HandlerCount int
  74. ProcessCount int
  75. PID int
  76. TLS bool
  77. Prefork bool
  78. }
  79. // PreStartupMessageData contains metadata exposed to OnPreStartupMessage hooks.
  80. type PreStartupMessageData struct {
  81. *ListenData
  82. // BannerHeader allows overriding the ASCII art banner displayed at startup.
  83. BannerHeader string
  84. entries []startupMessageEntry
  85. // PreventDefault, when set to true, suppresses the default startup message.
  86. PreventDefault bool
  87. }
  88. // AddInfo adds an informational entry to the startup message with "INFO" label.
  89. func (sm *PreStartupMessageData) AddInfo(key, title, value string, priority ...int) {
  90. pri := -1
  91. if len(priority) > 0 {
  92. pri = priority[0]
  93. }
  94. sm.addEntry(key, title, value, pri, StartupMessageLevelInfo)
  95. }
  96. // AddWarning adds a warning entry to the startup message with "WARNING" label.
  97. func (sm *PreStartupMessageData) AddWarning(key, title, value string, priority ...int) {
  98. pri := -1
  99. if len(priority) > 0 {
  100. pri = priority[0]
  101. }
  102. sm.addEntry(key, title, value, pri, StartupMessageLevelWarning)
  103. }
  104. // AddError adds an error entry to the startup message with "ERROR" label.
  105. func (sm *PreStartupMessageData) AddError(key, title, value string, priority ...int) {
  106. pri := -1
  107. if len(priority) > 0 {
  108. pri = priority[0]
  109. }
  110. sm.addEntry(key, title, value, pri, StartupMessageLevelError)
  111. }
  112. // EntryKeys returns all entry keys currently present in the startup message.
  113. func (sm *PreStartupMessageData) EntryKeys() []string {
  114. keys := make([]string, 0, len(sm.entries))
  115. for _, entry := range sm.entries {
  116. keys = append(keys, entry.key)
  117. }
  118. return keys
  119. }
  120. // ResetEntries removes all existing entries from the startup message.
  121. func (sm *PreStartupMessageData) ResetEntries() {
  122. sm.entries = sm.entries[:0]
  123. }
  124. // DeleteEntry removes a specific entry from the startup message by its key.
  125. func (sm *PreStartupMessageData) DeleteEntry(key string) {
  126. if sm.entries == nil {
  127. return
  128. }
  129. for i, entry := range sm.entries {
  130. if entry.key == key {
  131. sm.entries = append(sm.entries[:i], sm.entries[i+1:]...)
  132. return
  133. }
  134. }
  135. }
  136. func (sm *PreStartupMessageData) addEntry(key, title, value string, priority int, level StartupMessageLevel) {
  137. if sm.entries == nil {
  138. sm.entries = make([]startupMessageEntry, 0, 8)
  139. }
  140. for i, entry := range sm.entries {
  141. if entry.key != key {
  142. continue
  143. }
  144. sm.entries[i].value = value
  145. sm.entries[i].title = title
  146. sm.entries[i].level = level
  147. sm.entries[i].priority = priority
  148. return
  149. }
  150. sm.entries = append(sm.entries, startupMessageEntry{
  151. key: key,
  152. title: title,
  153. value: value,
  154. priority: priority,
  155. level: level,
  156. })
  157. }
  158. func newPreStartupMessageData(listenData *ListenData) *PreStartupMessageData {
  159. return &PreStartupMessageData{ListenData: listenData}
  160. }
  161. // PostStartupMessageData contains metadata exposed to OnPostStartupMessage hooks.
  162. type PostStartupMessageData struct {
  163. *ListenData
  164. // Disabled indicates whether the startup message was disabled via configuration.
  165. Disabled bool
  166. // IsChild indicates whether the current process is a child in prefork mode.
  167. IsChild bool
  168. // Prevented indicates whether the startup message was suppressed by a pre-startup hook using PreventDefault property.
  169. Prevented bool
  170. }
  171. func newPostStartupMessageData(listenData *ListenData, disabled, isChild, prevented bool) *PostStartupMessageData {
  172. clone := *listenData
  173. if len(listenData.ChildPIDs) > 0 {
  174. clone.ChildPIDs = slices.Clone(listenData.ChildPIDs)
  175. }
  176. return &PostStartupMessageData{
  177. ListenData: &clone,
  178. Disabled: disabled,
  179. IsChild: isChild,
  180. Prevented: prevented,
  181. }
  182. }
  183. func newHooks(app *App) *Hooks {
  184. return &Hooks{
  185. app: app,
  186. onRoute: make([]OnRouteHandler, 0),
  187. onGroup: make([]OnGroupHandler, 0),
  188. onGroupName: make([]OnGroupNameHandler, 0),
  189. onName: make([]OnNameHandler, 0),
  190. onListen: make([]OnListenHandler, 0),
  191. onPreStartup: make([]OnPreStartupMessageHandler, 0),
  192. onPostStartup: make([]OnPostStartupMessageHandler, 0),
  193. onPreShutdown: make([]OnPreShutdownHandler, 0),
  194. onPostShutdown: make([]OnPostShutdownHandler, 0),
  195. onFork: make([]OnForkHandler, 0),
  196. onMount: make([]OnMountHandler, 0),
  197. }
  198. }
  199. // OnRoute is a hook to execute user functions on each route registration.
  200. // Also you can get route properties by route parameter.
  201. func (h *Hooks) OnRoute(handler ...OnRouteHandler) {
  202. h.app.mutex.Lock()
  203. h.onRoute = append(h.onRoute, handler...)
  204. h.app.mutex.Unlock()
  205. }
  206. // OnName is a hook to execute user functions on each route naming.
  207. // Also you can get route properties by route parameter.
  208. //
  209. // WARN: OnName only works with naming routes, not groups.
  210. func (h *Hooks) OnName(handler ...OnNameHandler) {
  211. h.app.mutex.Lock()
  212. h.onName = append(h.onName, handler...)
  213. h.app.mutex.Unlock()
  214. }
  215. // OnGroup is a hook to execute user functions on each group registration.
  216. // Also you can get group properties by group parameter.
  217. func (h *Hooks) OnGroup(handler ...OnGroupHandler) {
  218. h.app.mutex.Lock()
  219. h.onGroup = append(h.onGroup, handler...)
  220. h.app.mutex.Unlock()
  221. }
  222. // OnGroupName is a hook to execute user functions on each group naming.
  223. // Also you can get group properties by group parameter.
  224. //
  225. // WARN: OnGroupName only works with naming groups, not routes.
  226. func (h *Hooks) OnGroupName(handler ...OnGroupNameHandler) {
  227. h.app.mutex.Lock()
  228. h.onGroupName = append(h.onGroupName, handler...)
  229. h.app.mutex.Unlock()
  230. }
  231. // OnListen is a hook to execute user functions on Listen or Listener.
  232. func (h *Hooks) OnListen(handler ...OnListenHandler) {
  233. h.app.mutex.Lock()
  234. h.onListen = append(h.onListen, handler...)
  235. h.app.mutex.Unlock()
  236. }
  237. // OnPreStartupMessage is a hook to execute user functions before the startup message is printed.
  238. func (h *Hooks) OnPreStartupMessage(handler ...OnPreStartupMessageHandler) {
  239. h.app.mutex.Lock()
  240. h.onPreStartup = append(h.onPreStartup, handler...)
  241. h.app.mutex.Unlock()
  242. }
  243. // OnPostStartupMessage is a hook to execute user functions after the startup message is printed (or skipped).
  244. func (h *Hooks) OnPostStartupMessage(handler ...OnPostStartupMessageHandler) {
  245. h.app.mutex.Lock()
  246. h.onPostStartup = append(h.onPostStartup, handler...)
  247. h.app.mutex.Unlock()
  248. }
  249. // OnPreShutdown is a hook to execute user functions before Shutdown.
  250. func (h *Hooks) OnPreShutdown(handler ...OnPreShutdownHandler) {
  251. h.app.mutex.Lock()
  252. h.onPreShutdown = append(h.onPreShutdown, handler...)
  253. h.app.mutex.Unlock()
  254. }
  255. // OnPostShutdown is a hook to execute user functions after Shutdown.
  256. func (h *Hooks) OnPostShutdown(handler ...OnPostShutdownHandler) {
  257. h.app.mutex.Lock()
  258. h.onPostShutdown = append(h.onPostShutdown, handler...)
  259. h.app.mutex.Unlock()
  260. }
  261. // OnFork is a hook to execute user function after fork process.
  262. func (h *Hooks) OnFork(handler ...OnForkHandler) {
  263. h.app.mutex.Lock()
  264. h.onFork = append(h.onFork, handler...)
  265. h.app.mutex.Unlock()
  266. }
  267. // OnMount is a hook to execute user function after mounting process.
  268. // The mount event is fired when sub-app is mounted on a parent app. The parent app is passed as a parameter.
  269. // It works for app and group mounting.
  270. func (h *Hooks) OnMount(handler ...OnMountHandler) {
  271. h.app.mutex.Lock()
  272. h.onMount = append(h.onMount, handler...)
  273. h.app.mutex.Unlock()
  274. }
  275. func (h *Hooks) executeOnRouteHooks(route *Route) error {
  276. if route == nil {
  277. return nil
  278. }
  279. cloned := *route
  280. // Check mounting
  281. if h.app.mountFields.mountPath != "" {
  282. cloned.path = h.app.mountFields.mountPath + cloned.path
  283. cloned.Path = cloned.path
  284. }
  285. for _, v := range h.onRoute {
  286. if err := v(cloned); err != nil {
  287. return err
  288. }
  289. }
  290. return nil
  291. }
  292. func (h *Hooks) executeOnNameHooks(route *Route) error {
  293. if route == nil {
  294. return nil
  295. }
  296. cloned := *route
  297. // Check mounting
  298. if h.app.mountFields.mountPath != "" {
  299. cloned.path = h.app.mountFields.mountPath + cloned.path
  300. cloned.Path = cloned.path
  301. }
  302. for _, v := range h.onName {
  303. if err := v(cloned); err != nil {
  304. return err
  305. }
  306. }
  307. return nil
  308. }
  309. func (h *Hooks) executeOnGroupHooks(group Group) error {
  310. // Check mounting
  311. if h.app.mountFields.mountPath != "" {
  312. group.Prefix = h.app.mountFields.mountPath + group.Prefix
  313. }
  314. for _, v := range h.onGroup {
  315. if err := v(group); err != nil {
  316. return err
  317. }
  318. }
  319. return nil
  320. }
  321. func (h *Hooks) executeOnGroupNameHooks(group Group) error {
  322. // Check mounting
  323. if h.app.mountFields.mountPath != "" {
  324. group.Prefix = h.app.mountFields.mountPath + group.Prefix
  325. }
  326. for _, v := range h.onGroupName {
  327. if err := v(group); err != nil {
  328. return err
  329. }
  330. }
  331. return nil
  332. }
  333. func (h *Hooks) executeOnListenHooks(listenData *ListenData) error {
  334. for _, v := range h.onListen {
  335. if err := v(*listenData); err != nil {
  336. return err
  337. }
  338. }
  339. return nil
  340. }
  341. func (h *Hooks) executeOnPreStartupMessageHooks(data *PreStartupMessageData) error {
  342. for _, handler := range h.onPreStartup {
  343. if err := handler(data); err != nil {
  344. return err
  345. }
  346. }
  347. return nil
  348. }
  349. func (h *Hooks) executeOnPostStartupMessageHooks(data *PostStartupMessageData) error {
  350. for _, handler := range h.onPostStartup {
  351. if err := handler(data); err != nil {
  352. return err
  353. }
  354. }
  355. return nil
  356. }
  357. func (h *Hooks) executeOnPreShutdownHooks() {
  358. for _, v := range h.onPreShutdown {
  359. if err := v(); err != nil {
  360. log.Errorf("failed to call pre shutdown hook: %v", err)
  361. }
  362. }
  363. }
  364. func (h *Hooks) executeOnPostShutdownHooks(err error) {
  365. for _, v := range h.onPostShutdown {
  366. if hookErr := v(err); hookErr != nil {
  367. log.Errorf("failed to call post shutdown hook: %v", hookErr)
  368. }
  369. }
  370. }
  371. func (h *Hooks) executeOnForkHooks(pid int) {
  372. for _, v := range h.onFork {
  373. if err := v(pid); err != nil {
  374. log.Errorf("failed to call fork hook: %v", err)
  375. }
  376. }
  377. }
  378. func (h *Hooks) executeOnMountHooks(app *App) error {
  379. for _, v := range h.onMount {
  380. if err := v(app); err != nil {
  381. return err
  382. }
  383. }
  384. return nil
  385. }