Hooks

钩子函数(Hooks)是一个通用的概念,表示某事件触发时所伴随的操作。

Hertz 提供了全局的 Hook 注入能力,用于在服务触发启动后退出前注入自己的处理逻辑。

StartHook

StartHook 在 Hertz 当中表示服务触发启动后需调用的函数,使用 CtxErrCallback 类型表示。Hertz 使用 OnRun 属性存储 StartHook 列表。

// CtxErrCallback 参见下方其函数签名
OnRun []CtxErrCallback

触发 Server 启动后,框架会按函数声明顺序依次调用所有的 StartHook 函数,完成调用之后,才会正式开始端口监听,如果发生错误,则立刻终止服务。

函数签名:

type CtxErrCallback func(ctx context.Context) error

示例代码:

package main

import (
    "context"

    "github.com/cloudwego/hertz/pkg/app"
    "github.com/cloudwego/hertz/pkg/app/server"
    "github.com/cloudwego/hertz/pkg/common/hlog"
    "github.com/cloudwego/hertz/pkg/common/utils"
    "github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
    h := server.Default()

    h.OnRun = append(h.OnRun, func(ctx context.Context) error {
        hlog.Info("run the first start hook")
        return nil
    })
    h.OnRun = append(h.OnRun, func(ctx context.Context) error {
        hlog.Info("run the second start hook")
        return nil
    })
    h.OnRun = append(h.OnRun, func(ctx context.Context) error {
        hlog.Info("run the third start hook")
        return nil
    })

    h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
        ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
    })

    h.Spin()
}

提示:启动服务,将在控制台顺序打印三个 StartHook 函数的日志。

main.go:17: [Info] run the first start hook
main.go:21: [Info] run the second start hook
main.go:25: [Info] run the third start hook

ShutdownHook

ShutdownHook 在 Hertz 当中表示服务退出前需调用的函数,使用 CtxCallback 类型表示。Hertz 使用 OnShutdown 属性存储 ShutdownHook 列表。

Server 退出前,框架会并发地调用所有声明的 ShutdownHook 函数,并且可以通过 server.WithExitWaitTime配置最大等待时长,默认为 5 秒,如果超时,则立刻终止服务。

ShutdownHook 的调用本质上是 Hertz 优雅退出 的一环。

函数签名:

type CtxCallback func(ctx context.Context)

示例代码 1:

package main

import (
    "context"

    "github.com/cloudwego/hertz/pkg/app"
    "github.com/cloudwego/hertz/pkg/app/server"
    "github.com/cloudwego/hertz/pkg/common/hlog"
    "github.com/cloudwego/hertz/pkg/common/utils"
    "github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
    h := server.Default()

    h.OnShutdown = append(h.OnShutdown, func(ctx context.Context) {
        hlog.Info("run the first shutdown hook")
    })
    h.OnShutdown = append(h.OnShutdown, func(ctx context.Context) {
        hlog.Info("run the second shutdown hook")
    })
    h.OnShutdown = append(h.OnShutdown, func(ctx context.Context) {
        hlog.Info("run the third shutdown hook")
    })

    h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
        ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
    })

    h.Spin()
}

提示:终止服务,将在控制台乱序打印三个 ShutdownHook 函数的日志。

hertz.go:77: [Info] HERTZ: Begin graceful shutdown, wait at most num=5 seconds...
main.go:22: [Info] run the third shutdown hook
main.go:16: [Info] run the first shutdown hook
main.go:19: [Info] run the second shutdown hook
engine.go:279: [Info] HERTZ: Execute OnShutdownHooks finish

示例代码 2:

package main

import (
	"context"
    "time"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
	h := server.Default(server.WithExitWaitTime(time.Second * 2))

	h.OnShutdown = append(h.OnShutdown, func(ctx context.Context) {
		hlog.Info("run shutdown hook")
		time.Sleep(time.Second * 5)
	})

	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
		ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
	})

	h.Spin()
}

提示:终止服务时,因为钩子函数执行时间超过 2 秒,打印超时日志。

hertz.go:77: [Info] HERTZ: Begin graceful shutdown, wait at most num=2 seconds...
main.go:17: [Info] run shutdown hook
engine.go:276: [Info] HERTZ: Execute OnShutdownHooks timeout: error=context deadline exceeded

OnAccept

OnAccept 是一个在连接建立后且被添加到 epoll 前调用的函数。

OnAccept func(conn net.Conn) context.Context

示例代码:

package main

import (
    "context"
    "github.com/cloudwego/hertz/pkg/app"
    "github.com/cloudwego/hertz/pkg/app/server"
    "github.com/cloudwego/hertz/pkg/common/utils"
    "github.com/cloudwego/hertz/pkg/protocol/consts"
    "github.com/cloudwego/hertz/pkg/common/hlog"
    "net"
)

func main() {

    h := server.New(
        server.WithOnAccept(func(conn net.Conn) context.Context {
            hlog.Info("run the onAccept")
            return context.Background()
        }),
        server.WithHostPorts("localhost:9230"))
    h.GET("", func(c context.Context, ctx *app.RequestContext) {
        hlog.Info("pong")
        ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
    })

    h.Spin()

}

提示:在发出请求后,将在控制台打印 OnAccept 函数的日志。

main.go:32: [Info] run the onAccept
main.go:38: [Info] pong

OnConnect

OnConnect 是一个在其被添加到 epoll 后调用的函数。它和 OnAccept 的不同之处在于它可以获取数据但是 OnAccept 不可以。

OnConnect func(ctx context.Context, conn network.Conn) context.Context

示例代码:

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/cloudwego/hertz/pkg/network"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {

	h := server.New(
		server.WithHostPorts("localhost:9229"),
		server.WithOnConnect(func(ctx context.Context, conn network.Conn) context.Context {
			b, _ := conn.Peek(3)
			hlog.Info("onconnect")
			hlog.Info(b)
			return ctx
		}))
	h.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
		c.JSON(consts.StatusOK, utils.H{"ping": "pong"})
	})
	h.Spin()

}

提示:在发出请求后,将在控制台打印 OnConnect 函数的日志。

main.go:19: [Info] onconnect
main.go:20: [Info] [71 69 84]

最后修改 July 18, 2023 : chore: fix space and typo (#721) (b6f0388)