HTTP 框架 Hertz 初体验丨青训营笔记
这是我参与「第五届青训营」笔记创作活动的第 8 天。
一、本堂课重点内容
Hertz 是一个 Golang 微服务 HTTP 框架,在设计之初参考了其他开源框架 fasthttp、gin、echo 的优势, 并结合字节跳动内部的需求,使其具有高易用性、高性能、高扩展性等特点,目前在字节跳动内部已广泛使用。
本堂课主要对 Hertz 的入门使用进行了简单的介绍,结合其框架特点:高易用性、高性能、高扩展性等特点,结合多种使用场景对 Hertz 的功能进行了详细的阐述。
Hertz 的架构设计如下图所示:
二、详细知识点介绍
1. Hertz 的基本使用
使用 Hertz 实现,服务监听 8080 端口并注册了一个 GET 方法的路由函数。
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"
)
func main() {
h := server.Default()
h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"message": "pong"})
})
h.Spin()
}
运行代码后,可以看到以下信息:
2023/02/14 14:54:18.950262 engine.go:617: [Debug] HERTZ: Method=GET absolutePath=/ping --> handlerName=hertz_demo/biz/handler.Ping (num=2 handlers)
2023/02/14 14:54:18.980850 engine.go:389: [Info] HERTZ: Using network library=standard
2023/02/14 14:54:18.984221 transport.go:65: [Info] HERTZ: HERTZ: HTTP server listening on address=[::]:8888
在控制台输入curl http://127.0.0.1:8888/ping ,结果如下图所示:
2. Hertz 路由
路由注册
Hertz 提供了 GET、POST、PUT、DELETE、ANY 等方法用于注册路由。
示例代码:
package main
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
func main(){
// 定义HTTP服务,并在端口8080上监听客户端请求
h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
// 用于注册静态文件
h.StaticFS("/", &app.FS{Root: "./", GenerateIndexPages: true})
h.GET("/get", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "get")
})
h.POST("/post", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "post")
})
h.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "put")
})
h.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "delete")
})
h.Any("/ping_any", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "any")
})
h.Spin()
}
路由组
Hertz 提供了路由组( Group )的能力,用于支持路由分组的功能,同时中间件也可以注册到路由组上。
示例代码:
package main
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
func main(){
h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
v1 := h.Group("/v1")
v1.GET("/get", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "get")
})
v1.POST("/post", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "post")
})
v2 := h.Group("/v2")
v2.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "put")
})
v2.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "delete")
})
h.Spin()
}
首先定义了一个HTTP服务,并在端口8080上监听客户端请求。该服务使用Hertz的默认设置,并设置了路由规则。通过调用h.Group()方法,创建了两个路由分组v1和v2,分别用于处理以/v1和/v2开头的HTTP请求。每个路由分组中又分别定义了多个HTTP方法的处理函数,分别处理GET、POST、PUT和DELETE请求,并返回相应的响应结果。
最后,调用h.Spin()方法启动HTTP服务,开始监听客户端请求。
路由类型
Hertz 支持丰富的路由类型用于实现复杂的功能,包括静态路由、参数路由、通配路由。
路由的优先级:静态路由 > 命名路由 > 通配路由
// This handler will match: "/hertz/version", but will not match : "/hertz/" or "/hertz"
h.GET("/hertz/:version", func(ctx context.Context, c *app.RequestContext) {
version := c.Param("version")
c.String(consts.StatusOK, "Hello %s", version)
})
// However, this one will match "/hertz/v1/" and "/hertz/v2/send"
h.GET("/hertz/:version/*action", func(ctx context.Context, c *app.RequestContext) {
version := c.Param("version")
action := c.Param("action")
message := version + " is " + action
c.String(consts.StatusOK, message)
})
3. Hertz 参数绑定
Hertz 提供了 Bind、Validate、BindAndValidate 函数用于进行参数绑定和校验。
func main() {
r := server.New()
r.GET("/hello", func(c context.Context, ctx *app.RequestContext) {
// 参数绑定需要配合特定的go tag使用
type Test struct {
A string `query:"a" vd:"$!='Hertz'"`
}
// BindAndValidate
var req Test
err := ctx.BindAndValidate(&req)
...
// Bind
req = Test{}
err = ctx.Bind(&req)
...
// Validate,需要使用 "vd" tag
err = ctx.Validate(&req)
...
})
...
}
代码中定义了一个名为Test的结构体,该结构体包含一个名为A的字段,并使用了query和vd两个标记。query标记表示A字段需要从请求的查询参数中获取值,而vd标记表示需要对A字段进行值的验证,确保其不等于Hertz。
4. Hertz 中间件
Hertz中间件的种类是多种多样的,简单分为两大类:
- 服务端中间件
- 客户端中间件
func MyMiddleware() app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) {
// pre-handle
fmt.Println("pre-handle")
// call the next middleware(handler)
c.Next(ctx)
// post-handle
fmt.Println("post-handle")
}
}
func main(){
h := server.Default(server.WithHostPorts("127.0.0.1:8888"))
h.Use(MyMiddleware())
h.GET("/middleware",func(ctx context.Context, c *app.RequestContext)){
c.String(consts.StatusOK,"hello hertz!")
}
h.spin()
}
中间件会按定义的先后顺序依次执行,如果想快速终止中间件调用,可以使用以下方法,注意当前中间件仍将执行。
Abort():终止后续调用AbortWithMsg(msg string, statusCode int):终止后续调用,并设置 response 中 body,和状态码AbortWithStatus(code int):终止后续调用,并设置状态码
5. Hertz Client
Hertz 提供了 HTTP Client 用于帮助用户发送 HTTP 请求。
Hertz 的 HTTP Client 支持 GET、POST、PUT、DELETE 等常见的 HTTP 请求方法,同时还支持自定义 Header、Query 参数、Request Body 等。可以使用 app.NewHTTPClient() 函数创建一个 HTTP Client 实例,并使用 client.Do() 方法发送请求。
func main() {
client := app.NewHTTPClient()
resp, err := client.GET("https://api.example.com/v1/users?id=1234",
app.WithHeader("Authorization", "Bearer xxxxxxxx"))
if err != nil {
fmt.Println("Failed to send HTTP request:", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Failed to read response body:", err)
}
fmt.Println(string(body))
}
6. Hertz 代码生成工具
Hertz 提供了代码生成工具 Hz,通过定义 IDL 文件即可生成对应的基础服务代码。目前,hz 可以基于 thrift 和 protobuf 的 IDL 生成 Hertz 项目的脚手架。
namespace go hello.example
struct HelloReq {
1: string Name (api.query="name"); // 添加 api 注解为方便进行参数绑定
}
struct HelloResp {
1: string RespBody;
}
service HelloService {
HelloResp HelloMethod(1: HelloReq request) (api.get="/hello");
}