HTTP 框架 Hertz 初体验丨青训营笔记

134 阅读4分钟

HTTP 框架 Hertz 初体验丨青训营笔记

这是我参与「第五届青训营」笔记创作活动的第 8 天。

一、本堂课重点内容

Hertz 是一个 Golang 微服务 HTTP 框架,在设计之初参考了其他开源框架 fasthttp、gin、echo 的优势, 并结合字节跳动内部的需求,使其具有高易用性、高性能、高扩展性等特点,目前在字节跳动内部已广泛使用。

本堂课主要对 Hertz 的入门使用进行了简单的介绍,结合其框架特点:高易用性、高性能、高扩展性等特点,结合多种使用场景对 Hertz 的功能进行了详细的阐述。

Hertz 的架构设计如下图所示:

image.png

二、详细知识点介绍

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 ,结果如下图所示:

image.png

2. Hertz 路由

路由注册

Hertz 提供了 GETPOSTPUTDELETEANY 等方法用于注册路由。

示例代码:

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()方法,创建了两个路由分组v1v2,分别用于处理以/v1/v2开头的HTTP请求。每个路由分组中又分别定义了多个HTTP方法的处理函数,分别处理GETPOSTPUTDELETE请求,并返回相应的响应结果。

最后,调用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 提供了 BindValidateBindAndValidate 函数用于进行参数绑定和校验。

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的字段,并使用了queryvd两个标记。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");
}

三、引用参考