Hertz Engine 的核心功能解析

news/2025/2/24 16:48:44/文章来源:https://www.cnblogs.com/guangdelw/p/18734434

server.HertzHertz 的核心类型,它由 route.Engine 以及 signalWaiter 组成,Hertz 服务器的启动、路由注册、中间件注册以及退出等重要方法均包含在 server.Hertz 中。

以下是 server.Hertz 的定义:

// Hertz is the core struct of hertz.
type Hertz struct {*route.Engine// 用于接收信号以实现优雅退出signalWaiter func(err chan error) error
}

route.Engineserver.Hertz 的重要组成部分,Engine 的定义位于 Engine。

配置

配置项 默认值 说明
WithTransport network.NewTransporter 更换底层 transport
WithHostPorts :8888 指定监听的地址和端口
WithKeepAliveTimeout 1min tcp 长连接保活时间,一般情况下不用修改,更应该关注 idleTimeout
WithReadTimeout 3min 底层读取数据超时时间
WithIdleTimeout 3min 长连接请求链接空闲超时时间
WithMaxRequestBodySize 4 _ 1024 _ 1024 配置最大的请求体大小
WithRedirectTrailingSlash true 自动根据末尾的 / 转发,例如:如果 router 只有 /foo/,那么 /foo 会重定向到 /foo/ ;如果只有 /foo,那么 /foo/ 会重定向到 /foo
WithRemoveExtraSlash false RemoveExtraSlash 当有额外的 / 时也可以当作参数。如:user/:name,如果开启该选项 user//xiaoming 也可匹配上参数
WithUnescapePathValues true 如果开启,请求路径会被自动转义(eg. ‘%2F’ -> ‘/’)。如果 UseRawPath 为 false(默认情况),则 UnescapePathValues 实际上为 true,因为 .URI().Path() 将被使用,它已经是转义后的。设置该参数为 false,需要配合 WithUseRawPath(true)
WithUseRawPath false 如果开启,会使用原始 path 进行路由匹配
WithHandleMethodNotAllowed false 如果开启,当当前路径不能被匹配上时,server 会去检查其他方法是否注册了当前路径的路由,如果存在则会响应"Method Not Allowed",并返回状态码 405; 如果没有,则会用 NotFound 的 handler 进行处理
WithDisablePreParseMultipartForm false 如果开启,则不会预处理 multipart form。可以通过 ctx.Request.Body() 获取到 body 后由用户处理
WithStreamBody false 如果开启,则会使用流式处理 body
WithNetwork “tcp” 设置网络协议,可选:tcp,udp,unix(unix domain socket),默认为 tcp
WithExitWaitTime 5s 设置优雅退出时间。Server 会停止建立新的连接,并对关闭后的每一个请求设置 Connection: Close 的 header,当到达设定的时间关闭 Server。当所有连接已经关闭时,Server 可以提前关闭
WithTLS nil 配置 server tls 能力,详情可见 TLS
WithListenConfig nil 设置监听器配置,可用于设置是否允许 reuse port 等
WithALPN false 是否开启 ALPN
WithTracer []interface{}{} 注入 tracer 实现,如不注入 Tracer 实现,默认关闭
WithTraceLevel LevelDetailed 设置 trace level
WithWriteTimeout 无限长 写入数据超时时间
WithRedirectFixedPath false 如果开启,当当前请求路径不能匹配上时,server 会尝试修复请求路径并重新进行匹配,如果成功匹配并且为 GET 请求则会返回状态码 301 进行重定向,其他请求方式返回 308 进行重定向
WithBasePath / 设置基本路径,前缀和后缀必须为 /
WithMaxKeepBodySize 4 _ 1024 _ 1024 设置回收时保留的请求体和响应体的最大大小。单位:字节
WithGetOnly false 如果开启则只接受 GET 请求
WithKeepAlive true 如果开启则使用 HTTP 长连接
WithAltTransport network.NewTransporter 设置备用 transport
WithH2C false 设置是否开启 H2C
WithReadBufferSize 4 * 1024 设置读缓冲区大小,同时限制 HTTP header 大小
WithRegistry registry.NoopRegistry, nil 设置注册中心配置,服务注册信息
WithAutoReloadRender false, 0 设置自动重载渲染配置
WithDisablePrintRoute false 设置是否禁用 debugPrintRoute
WithOnAccept nil 设置在 netpoll 中当一个连接被接受但不能接收数据时的回调函数,在 go net 中在转换 TLS 连接之前被调用
WithOnConnect nil 设置 onConnect 函数。它可以接收来自 netpoll 连接的数据。在 go net 中,它将在转换 TLS 连接后被调用
WithDisableHeaderNamesNormalizing false 设置是否禁用 Request 和 Response Header 名字的规范化 (首字母和破折号后第一个字母大写)

Server Connection 数量限制:

  • 如果是使用标准网络库,无此限制
  • 如果是使用 netpoll,最大连接数为 10000 (这个是 netpoll 底层使用的 gopool )控制的,修改方式也很简单,调用 gopool 提供的函数即可:gopool.SetCap(xxx)(main.go 中调用一次即可)。
    package mainimport ("context""github.com/bytedance/gopkg/util/gopool""github.com/cloudwego/hertz/pkg/app""github.com/cloudwego/hertz/pkg/app/server""net/http"
    )func main() {// 设置新的最大连接数,例如设置为 20000newCap := 20000gopool.SetCap(int32(newCap))// 创建一个 Hertz 服务器实例h := server.Default()// 定义路由h.GET("/", func(c context.Context, ctx *app.RequestContext) {ctx.String(http.StatusOK, "Hello, Hertz!")},)// 启动服务器h.Spin()
    }
    

Server 侧的配置项均在初始化 Server 时采用 server.WithXXX 的方式,如:

package mainimport ("context""github.com/cloudwego/hertz/pkg/app""github.com/cloudwego/hertz/pkg/app/server""net/http""time"
)func main() {// 创建一个 Hertz 服务器实例h := server.Default(// 设置监听端口server.WithHostPorts(":8080"),// 设置读取超时时间server.WithReadTimeout(time.Minute*5),// 设置写入超时时间server.WithBasePath("/api"),)// 定义路由h.GET("/", func(c context.Context, ctx *app.RequestContext) {ctx.String(http.StatusOK, "Hello, Hertz!")},)// 启动服务器h.Spin()
}

初始化服务

有两种方式:

  1. func Default(opts ...config.Option) *Hertz
  2. func New(opts ...config.Option) *Hertz

Default(推荐)

Default 用于初始化服务,默认使用了 Recovery 中间件以保证服务在运行时不会因为 panic 导致服务崩溃。

函数签名:

func Default(opts ...config.Option) *Hertz

示例代码:

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

New

New 用于初始化服务,没有使用默认的 Recovery 中间件。

函数签名:

func New(opts ...config.Option) *Hertz

示例代码:

func main() {h := server.New()h.Spin()
}

测试

要测试这一点的区别,可以通过如下代码运行对比

Default

package mainimport ("context""github.com/cloudwego/hertz/pkg/app""github.com/cloudwego/hertz/pkg/app/server""net/http""time"
)func main() {// 创建一个 Hertz 服务器实例h := server.Default()// 定义路由h.GET("/test_panic", func(c context.Context, ctx *app.RequestContext) {panic("panic")ctx.String(http.StatusOK, "Hello, Hertz!")},)h.GET("/test_normal", func(c context.Context, ctx *app.RequestContext) {ctx.String(http.StatusOK, "Hello, Hertz!")},)// 启动服务器h.Spin()
}

New

package mainimport ("context""github.com/cloudwego/hertz/pkg/app""github.com/cloudwego/hertz/pkg/app/server""net/http""time"
)func main() {// 创建一个 Hertz 服务器实例h := server.New()// 定义路由h.GET("/test_panic", func(c context.Context, ctx *app.RequestContext) {panic("panic")ctx.String(http.StatusOK, "Hello, Hertz!")},)h.GET("/test_normal", func(c context.Context, ctx *app.RequestContext) {ctx.String(http.StatusOK, "Hello, Hertz!")},)// 启动服务器h.Spin()
}

分别运行代码,然后分别访问

  • 127.0.0.1:8888/test_panic
  • 127.0.0.1:8888/test_normal

可以看到

NEW Default
127.0.0.1:8888/test_panic返回 无返回 500状态码
服务端 服务报错,退出运行 服务正常,打印报错日志,不退出
再访问 127.0.0.1:8888/test_normal 无法访问 还可以正常访问

服务运行与退出

func (h *Hertz) Spin()
func (engine *Engine) Run() (err error)
func (h *Hertz) SetCustomSignalWaiter(f func(err chan error) error)

Spin(推荐)

Spin 函数用于运行 Hertz 服务器,接收到退出信号后可退出服务。

该函数支持服务的优雅退出,优雅退出的详细内容请看 优雅退出。

在使用 服务注册发现 的功能时,Spin 会在服务启动时将服务注册进入注册中心,并使用 signalWaiter 监测服务异常。

函数签名:

func (h *Hertz) Spin()

示例代码:

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

Run

Run 函数用于运行 Hertz 服务器,接收到退出信号后可退出服务。

该函数不支持服务的优雅退出,除非有特殊需求,不然一般使用 Spin 函数用于运行服务。

函数签名:

func (engine *Engine) Run() (err error)

示例代码:

func main() {h := server.Default()if err := h.Run(); err != nil {// ...panic(err)}
}

SetCustomSignalWaiter

SetCustomSignalWaiter 函数用于自定义服务器接收信号后的处理函数,若没有设置自定义函数,Hertz 使用 waitSignal 函数作为信号处理的默认实现方式,详细内容请看 优雅退出。

函数签名:

func (h *Hertz) SetCustomSignalWaiter(f func(err chan error) error)

示例代码:

func main() {h := server.New()h.SetCustomSignalWaiter(func(err chan error) error {return nil})h.Spin()
}

Spin与Run的区别

Spin的退出

2025/02/23 11:50:01.612320 engine.go:397: [Info] HERTZ: Using network library=standard
2025/02/23 11:50:01.612834 transport.go:65: [Info] HERTZ: HTTP server listening on address=[::]:8080# 下面就是优雅退出的部分
2025/02/23 11:50:12.437447 hertz.go:113: [Info] HERTZ: Received signal: interrupt
2025/02/23 11:50:12.437447 hertz.go:77: [Info] HERTZ: Begin graceful shutdown, wait at most num=5 seconds...
2025/02/23 11:50:12.438197 transport.go:71: [Error] HERTZ: Error=accept tcp [::]:8080: use of closed network connection
2025/02/23 11:50:17.439975 engine.go:311: [Info] HERTZ: Execute OnShutdownHooks timeout: error=context deadline exceeded

Run的退出

2025/02/23 11:49:41.349221 engine.go:397: [Info] HERTZ: Using network library=standard
2025/02/23 11:49:41.350245 transport.go:65: [Info] HERTZ: HTTP server listening on address=[::]:8080
exit status 0xc000013a

中间件的使用

中间件的使用

func (engine *Engine) Use(middleware ...app.HandlerFunc) IRoutes

Use 函数用于将中间件注册进入路由。

Hertz 支持用户自定义中间件,Hertz 已经实现了一些常用的中间件,详情见 hertz-contrib。

Hertz 支持的中间件的使用方法包括全局注册路由组级别和单一路由级别的注册,详情见 服务端中间件。

Use 函数中 middleware 的形参必须为 app.HandlerFunc 的 http 处理函数:

type HandlerFunc func (ctx context.Context, c *app.RequestContext)

只要符合这个定义的方法,都可以成为中间件

例如

func main() {h := server.New()// 将内置的 Recovery 中间件注册进入路由h.Use(recovery.Recovery())// 使用自定义的中间件h.Use(exampleMiddleware())
}func exampleMiddleware() app.HandlerFunc {return func(ctx context.Context, c *app.RequestContext) {// 在 Next 中的函数执行之前打印日志hlog.Info("print before...")// 使用 Next 使得路由匹配的函数执行c.Next(ctx)// 在 Next 中的函数执行之后打印日志hlog.Info("print after...")}
}

gin中的访问日志

[GIN] 2025/02/23 - 10:58:31 | 200 |            0s |       127.0.0.1 | GET      "/api"
[GIN] 2025/02/23 - 10:58:33 | 200 |            0s |       127.0.0.1 | GET      "/api"
[GIN] 2025/02/23 - 10:58:34 | 200 |            0s |       127.0.0.1 | GET      "/api"
[GIN] 2025/02/23 - 10:58:34 | 200 |            0s |       127.0.0.1 | GET      "/api"

虽然Hertz中没有,但是我们可以通过中间件来达到目的

package mainimport ("context""fmt""github.com/cloudwego/hertz/pkg/app""github.com/cloudwego/hertz/pkg/app/server""net/http""time"
)// AccessLogMiddleware 自定义访问日志中间件
func AccessLogMiddleware() app.HandlerFunc {return func(c context.Context, ctx *app.RequestContext) {// 记录请求开始时间start := time.Now()// 继续处理请求ctx.Next(c)// 计算请求处理时间elapsed := time.Since(start)// 获取客户端 IP 地址clientIP := ctx.ClientIP()// 获取请求方法method := string(ctx.Method())// 获取请求路径path := string(ctx.Path())// 获取响应状态码statusCode := ctx.Response.StatusCode()// 输出类似 Gin 风格的访问日志// 格式化日期now := time.Now().Format("2006/01/02 - 15:04:05")// 输出类似 Gin 风格的访问日志fmt.Printf("[Hertz] %s | %d | %13v | %15s | %-7s %s\n",now, statusCode, elapsed, clientIP, method, path,)}
}
func main() {// 创建一个 Hertz 服务器实例h := server.Default(// 设置监听端口server.WithHostPorts(":8080"),)// 使用自定义的访问日志中间件h.Use(AccessLogMiddleware())// 定义路由h.GET("/", func(c context.Context, ctx *app.RequestContext) {ctx.String(http.StatusOK, "Hello, Hertz!")},)// 启动服务器h.Spin()
}

产生的日志

[Hertz] 2025/02/23 - 13:10:44 | 404 |            0s |       127.0.0.1 | GET     /api/aa
[Hertz] 2025/02/23 - 13:10:45 | 404 |            0s |       127.0.0.1 | GET     /api/aa
[Hertz] 2025/02/23 - 13:10:45 | 404 |            0s |       127.0.0.1 | GET     /api/aa

流式处理

Hertz 支持 Server 的流式处理,包括流式读和流式写。

注意:由于 netpoll 和 go net 触发模式不同,netpoll 流式为“伪”流式(由于 LT 触发,会由网络库将数据读取到网络库的 buffer 中),在大包的场景下(如:上传文件等)可能会有内存问题,推荐使用 go net。

流式读

Hertz Server 支持流式读取请求内容。

示例代码:

func main() {h := server.Default(server.WithHostPorts("127.0.0.1:8080"), server.WithStreamBody(true), server.WithTransport(standard.NewTransporter))h.POST("/bodyStream", handler)h.Spin()
}func handler(ctx context.Context, c *app.RequestContext) {// Acquire body streamingbodyStream := c.RequestBodyStream()// Read half of body bytesp := make([]byte, c.Request.Header.ContentLength()/2)r, err := bodyStream.Read(p)if err != nil {panic(err)}left, _ := ioutil.ReadAll(bodyStream)c.String(consts.StatusOK, "bytes streaming_read: %d\nbytes left: %d\n", r, len(left))
}

流式写

Hertz Server 支持流式写入响应。

提供了两种方式:

  1. 用户在 handler 中通过 ctx.SetBodyStream 函数传入一个 io.Reader,然后按与示例代码(利用 channel 控制数据分块及读写顺序)类似的方式分块读写数据。注意,数据需异步写入。

    若用户事先知道传输数据的总长度,可以在 ctx.SetBodyStream 函数中传入该长度进行流式写,示例代码如 /streamWrite1

    若用户事先不知道传输数据的总长度,可以在 ctx.SetBodyStream 函数中传入 -1 以 Transfer-Encoding: chunked 的方式进行流式写,示例代码如 /streamWrite2

    示例代码:

    func main() {h := server.Default(server.WithHostPorts("127.0.0.1:8080"), server.WithStreamBody(true), server.WithTransport(standard.NewTransporter))h.GET("/streamWrite1", func(ctx context.Context, c *app.RequestContext) {rw := newChunkReader()line := []byte("line\r\n")c.SetBodyStream(rw, 500*len(line))go func() {for i := 1; i <= 500; i++ {// For each streaming_write, the upload_file printsrw.Write(line)fmt.Println(i)time.Sleep(10 * time.Millisecond)}rw.Close()}()go func() {<-c..Finished()fmt.Println("request process end")}()})h.GET("/streamWrite2", func(ctx context.Context, c *app.RequestContext) {rw := newChunkReader()// Content-Length may be negative:// -1 means Transfer-Encoding: chunked.c.SetBodyStream(rw, -1)go func() {for i := 1; i < 1000; i++ {// For each streaming_write, the upload_file printsrw.Write([]byte(fmt.Sprintf("===%d===\n", i)))fmt.Println(i)time.Sleep(100 * time.Millisecond)}rw.Close()}()go func() {<-c..Finished()fmt.Println("request process end")}()})h.Spin()
    }type ChunkReader struct {rw  bytes.Bufferw2r chan struct{}r2w chan struct{}
    }func newChunkReader() *ChunkReader {var rw bytes.Bufferw2r := make(chan struct{})r2w := make(chan struct{})cr := &ChunkReader{rw, w2r, r2w}return cr
    }var closeOnce = new(sync.Once)func (cr *ChunkReader) Read(p []byte) (n int, err error) {for {_, ok := <-cr.w2rif !ok {closeOnce.Do(func() {close(cr.r2w)})n, err = cr.rw.Read(p)return}n, err = cr.rw.Read(p)cr.r2w <- struct{}{}if n == 0 {continue}return}
    }func (cr *ChunkReader) Write(p []byte) (n int, err error) {n, err = cr.rw.Write(p)cr.w2r <- struct{}{}<-cr.r2wreturn
    }func (cr *ChunkReader) Close() {close(cr.w2r)
    }
    
  2. 用户可以在 handler 中使用 pkg/protocol/http1/resp/writer 下提供的 NewChunkedBodyWriter 方法劫持 response 的 writer,然后使用 ctx.Write 函数将分块数据写入 Body 并将分块数据使用 ctx.Flush 函数立即发送给客户端。

    示例代码:

    h.GET("/flush/chunk", func(ctx context.Context, c *app.RequestContext) {// Hijack the writer of responsec.Response.HijackWriter(resp.NewChunkedBodyWriter(&c.Response, c.GetWriter()))for i := 0; i < 10; i++ {c.Write([]byte(fmt.Sprintf("chunk %d: %s", i, strings.Repeat("hi~", i)))) // nolint: errcheckc.Flush()                                                                 // nolint: errchecktime.Sleep(200 * time.Millisecond)}
    })
    

这两种方式的区别:第一种在执行完 handler 逻辑后再将数据按分块发送给客户端,第二种在 handler 逻辑中就可以将分块数据发送出去。

更多示例代码可参考 example。

Panic 处理函数

用于设置当程序发生 panic 时的处理函数,默认为 nil

注意:如果同时设置了 PanicHandlerRecovery 中间件,则 Recovery 中间件会覆盖 PanicHandler 的处理逻辑。

示例代码:

package mainimport ("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"
)func main() {// 创建一个 Hertz 服务器实例h := server.New(// 设置监听端口server.WithHostPorts(":8080"),)// 在 panic 时,会触发 PanicHandler 中的函数,返回 500 状态码并携带错误信息h.PanicHandler = func(ctx context.Context, c *app.RequestContext) {c.JSON(consts.StatusInternalServerError, utils.H{"message": "panic",},)}// 定义路由h.GET("/", func(c context.Context, ctx *app.RequestContext) {panic("panic")},)// 启动服务器h.Spin()
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/889077.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python异步编程终极指南:用协程与事件循环重构你的高并发系统

title: Python异步编程终极指南:用协程与事件循环重构你的高并发系统 date: 2025/2/24 updated: 2025/2/24 author: cmdragon excerpt: 🚀 深入剖析Python异步编程的核心机制。你将掌握:\n 事件循环的底层实现原理与调度算法\n async/await协程的6种高级用法模式\n 异步H…

Uniapp开发安卓app之使用360加固加壳处理

Uniapp开发安卓app之使用360加固加壳处理 注:360加固只针对Android App免费。360加固助手官方下载 1.流程 安卓加固 》 签名APK 2.前提 先使用Hbuilder完成app打包,得到apk文件。 3.安卓加固1)安装、登录360加固软件并打开 2)在安卓加固》APK加固》添加任务,选择hbi…

用python画五角星

import turtle turtle.setup(500,500) turtle.bgcolor("white") turtle.pensize(3) turtle.color("red") turtle.begin_fill() for _ in range(5): turtle.forward(200) turtle.right(144) turtle.end_fill() turtle.hideturtle() turtle.done()

win10忘记开机密码怎么办?

下面我们来看看win10笔记本电脑开机密码忘记了怎么办_ 方法一、重置电脑,但是数据可能会丢失 1、开机,在看到Windows10Logo,下面有个圆圈在转的时候,数5秒。然后直接按下电源键关机。2.桌面,如果您的台式机有重新启动键,也可以直接按重新启动键。这样重复2~3次。 三、鼠标…

mysql知识面试day1

1.介绍mysql MYSQL是一个关系型数据库 2.事务的四大特性 acidA原子性表示要么全部成功要么全部失败 C一致性事务执行前和执行后需要保持一致的状态 I隔离性一个事务只能读到已提交的更改 D持久性事务一旦被提交更改就是永久性的3.数据库的三大范式 第一范式inf:确保字段的原子性…

图像采集卡功能与应用详解

图像采集卡(Image Capture Card),也称为视频采集卡或图像采集设备,是一种用于将摄像头、相机、显微镜、传感器等设备输出的图像或视频信号转换为计算机可处理的数字信号的硬件设备。它在工业检测、医疗影像、机器视觉、安防监控、科学研究等领域有广泛应用。 一、核心功能 …

项目管理工具-禅道(2)

一、bug的等级 (1)1级bug (致命bug) (2)2级bug(严重bug) (3)3级bug(一般bug) (4)4级bug(简易性bug) 划分: 1级bug (致命bug) 必须优先修改,在测试中较少出现,一旦出现应立即中止当前版本测试; 致命bug: (1)常规操作引起的崩溃,死机,死循环,内存泄露…

npccctf第一周wp

1、全网呼叫密码人 题目 from Crypto.Cipher import AES from Crypto.Util.number import * from Crypto.Util.Padding import pad """ Dinzheng先生准备去买一根RuiKeV电子烟,在付款的时候忘记了它的支付密码。 还好他的朋友早有准备,留下了若干个密保问题,…

如何利用CRM系统进行客户细分和精准营销?

——怎么做才能在CRM系统里对客户进行细分并精准营销呢? 我有一个朋友这么问我。 其实,想做到这个并不难,不过需要先明白这两个的核心概念是什么。简单来说,客户细分——根据客户的不同特点,把客户分成几个群体。 精准营销——根据这些细分好的群体,量身定制符合他们需求…