一文带你快速入门GolangHTTP框架Hertz

1,795 阅读7分钟

什么是Hertz

Hertz [həːts]是一个高性能高易用性高扩展性的Go HTTP框架。它旨在简化开发人员构建微服务的过程。

为什么选择Hertz

Hertz的亮点之一是其极高的性能。你可以通过查看以下关于echo请求的统计数据来直观地了解这一点。

  • 四个框架的横向比较

performance-4.png

  • 三个框架的横向比较

performance-3.png

你可以参考 hertz-benchmark 来获得更多有关Hertz性能的信息。

另一个亮点是它的高易用性,我们将在下文中讨论。

如何使用Hertz

这里我们将来编写一个简单的示例Demo来帮助你快速熟悉Hertz的基本特性。你可以去Github上将它clone下来对照着文章帮助你理解,也可以跟着我一步一步走,最终自己写出这个简单的Demo。

安装

在使用Hertz之前,你需要设置Golang开发环境,并确保Golang版本 >= v1.15。

一旦我们准备好了Golang环境,让我们创建示例Demo的项目文件夹,通常在 $GOPATH/src 下。

mkdir userdemo
cd userdemo

我强烈建议你使用Hertz自带的命令行工具 hz 来辅助开发。

hz 是由Hertz框架提供的用于生成代码的工具。目前,hz可以基于thriftprotobuf IDL为Hertz项目生成脚手架。

hz生成的代码,一部分由底层编译器生成(通常是关于IDL中定义的结构体),另一部分是用户自定义的路由、方法和其他IDL信息。用户可以直接运行代码。

在执行流方面,当hz使用thrift IDL生成代码时,hz调用thrift- go来生成go结构体代码,并将自己作为插件执行到thrift -gen-hertz (命名为thrift-gen-hertz)以生成其余代码。在使用protobuf IDL时也是如此。

您可以参考hz toolkit usage了解更多信息。

使用以下命令安装hz:

go install github.com/cloudwego/hertz/cmd/hz@latest
hz -v

如果hz版本信息如下所示,则说明安装完成,已准备好了基础的Hertz开发环境。

hz version v0.2.0

定义IDL

在本节中,我们将为项目userdemo编写IDL文件。

hz可以使用thrift IDLprotobuf IDL来生成代码,并且需要安装适当的编译器 thriftgoprotoc。我们将以thrift为例。

让我们创建一个idl文件夹并定义user.thrift

// idl/user.thrift
namespace go user

struct BaseResp {
    1: i64 StatusCode;
    2: string StatusMsg;
    3: string data;
}

struct RegisterRequest {
    1: string Username;
    2: string Password;
}

struct RegisterResponse {
    1: BaseResp BaseResp;
}

struct LoginRequest {
    1: string Username;
    2: string Password;
}

struct LoginResponse {
    1: BaseResp BaseResp;
}

struct InfoRequest {
    1: string Username;
}

struct InfoResponse {
    1: BaseResp BaseResp;
}

service UserService {
    RegisterResponse Register(1: RegisterRequest req) (api.post="/user/register");
    LoginResponse Login(1: LoginRequest req) (api.post="/user/login");
    InfoResponse Info(1: InfoRequest req) (api.get="/user/:username");
}

如你所见,我们以api注解的形式定义了HTTP请求方法和路由。hz将根据这些注释为我们生成对应的Handler,如果你是Java选手,Controller可能可以帮助你理解。

您可以参考 支持的api注释 以获得hz支持的所有api注释。

生成脚手架代码

在项目目录下执行以下命令。hz将为我们生成脚手架代码。

hz new -idl idl/user.thrift
go mod tidy

如果你修改了已经生成过代码的 user.thrift,你可以通过以下命令来更新你的脚手架。

hz update -idl idl/user.thrift

这是由user.thrift生成的代码结构,简单理解以下可以快速帮助你上手。

.
├── biz                               // business layer, which stores business logic related processes
│   ├── handler                       // store handler file
│   │   ├── user                      // user corresponds to the namespace defined in thrift IDL; for protobuf IDL, it corresponds to the last level of go_package
│   │   │   |
│   │   │   |__  user_service.go      // the handler file, the user will implement the method defined by the IDL service in this file, it will search for the existing handler in the current file when "update" command, and append a new handler to the end
│   │   └── ping.go                   // ping handler carried by default, used to generate code for quick debugging, no other special meaning
│   ├── model                         // IDL content-related generation code
│   │   └── user                      // hello/example corresponds to the namespace defined in thrift IDL; for protobuf IDL, it corresponds to the last level of go_package 
│   │         └── user.go             // the product of thriftgo, It contains go code generated from the contents of hello.thrift definition. And it will be regenerated when use "update" command.
│   └── router                        // generated code related to the definition of routes in IDL
│       ├── user                      // hello/example corresponds to the namespace defined in thrift IDL; for protobuf IDL, it corresponds to the last level of go_package
│       │     ├── hello.go            // the route registration code generated for the routes defined in hello.thrift by hz; this file will be regenerated each time the relevant IDL is updated
│       │     └── middleware.go       // default middleware function, hz adds a middleware for each generated route group by default; when updating, it will look for the existing middleware in the current file and append new middleware at the end
│       └── register.go               // call and register the routing definition in each IDL file; when a new IDL is added, the call of its routing registration will be automatically inserted during the update; do not edit
├── go.mod                            // go.mod file, if not specified on the command line, defaults to a relative path to GOPATH as the module name
├── idl                               // user defined IDL, location can be arbitrary
│   └── user.thrift
├── main.go                           // program entry
├── router.go                         // user defined routing methods other than IDL
└── router_gen.go                     // the route registration code generated by hz, for calling user-defined routes and routes generated by hz

中间件的使用

中间件可以在请求正式进入主Handler函数之前或之后进行一些处理,例如进行jwt身份验证。

Hertz支持许多常用的中间件。在本例中,我们将使用Session中间件来帮助我们统计用户登录的次数。

如前所述,hz帮助我们设置了许多脚手架代码。我们只需要关注业务代码。比如这里我们使用Session中间件,你只需要简单地修改middleware.go 中的_loginMw方法。如下所示:

func _loginMw() []app.HandlerFunc {
   // your code...
   return []app.HandlerFunc{
      // use session middleware
      sessions.Sessions("usersession", cookie.NewStore([]byte("secret"))),
   }
}

是不是非常简单?

修改Handler

接下来我们将编写路由处理Handler,生成好的脚手架在user_service.go 文件中。

Hertz会先进行数据的绑定验证和一些杂务工作(下篇文章介绍),我们需要做的就是处理请求。

注意:本示例Demo没有涉及数据库操作,所有数据将简单存储在一个不能进行持久存储的map结构中。

  • 让我们先来看看Register方法。

我们可以通过PostForm方法从POST请求表单中接收数据(类比Spring的@RequestBody)。你也可以使用StringJSON方法向客户端返回字符串或JSON数据并指定响应状态码。

// Register .
// @router /user/register/ [POST]
func Register(ctx context.Context, c *app.RequestContext) {
   var err error
   var req user.RegisterRequest
   err = c.BindAndValidate(&req)
   if err != nil {
      c.String(400, err.Error())
      return
   }

   resp := new(user.RegisterResponse)

   username := c.PostForm("username")
   password := c.PostForm("password")

   if dal.CheckUsername(username) {
      dal.CreateUser(username, password)

      resp.BaseResp = &user.BaseResp{
         StatusCode: 0,
         StatusMsg:  "register success",
      }

      c.JSON(200, resp.BaseResp)
      return
   }

   resp.BaseResp = &user.BaseResp{
      StatusCode: 1,
      StatusMsg:  "register failed",
   }
   c.JSON(400, resp.BaseResp)
}
  • 接下来是Login方法

这个方法中的大多数都Register方法一样,除了这里我们使用了Session中间件来统计不同用户登录的次数。

我们可以使用sessions.Default方法来获取会话对象,并使用GetSet方法来编辑存储在Session中的值。

// Login .
// @router /user/login/ [POST]
func Login(ctx context.Context, c *app.RequestContext) {
   var err error
   var req user.LoginRequest
   err = c.BindAndValidate(&req)
   if err != nil {
      c.String(400, err.Error())
      return
   }

   resp := new(user.LoginResponse)

   username := c.PostForm("username")
   password := c.PostForm("password")

   if dal.CheckPassword(username, password) {
      session := sessions.Default(c)
      var count int
      cnt := session.Get(username)
      if cnt == nil {
         count = 0
         dal.SetFrequency(username, count)
      } else {
         count = cnt.(int)
         count++
         dal.SetFrequency(username, count)
      }
      session.Set(username, count)
      _ = session.Save()

      resp.BaseResp = &user.BaseResp{
         StatusCode: 0,
         StatusMsg:  "login success",
      }
      c.JSON(200, resp.BaseResp)
      return
   }

   resp.BaseResp = &user.BaseResp{
      StatusCode: 1,
      StatusMsg:  "login failed",
   }
   c.JSON(400, resp.BaseResp)
}
  • 最后让我们来看看Info方法

在这个方法中,我们使用了Hertz的参数路由特性,它允许我们使用命名参数(如:name)指定路由(类比Spring的@PathVariable),以便该参数匹配路径段。

Demo中我们设置:username参数路由,并使用Param方法来获取请求路径中的值。

例如,如果你的请求路径是/user/foobar,那么username就是foobar

// Info .
// @router /user/:username [GET]
func Info(ctx context.Context, c *app.RequestContext) {
   var err error
   var req user.InfoRequest
   err = c.BindAndValidate(&req)
   if err != nil {
      c.String(400, err.Error())
      return
   }

   resp := new(user.InfoResponse)

   username := c.Param("username")

   frequency := dal.GetFrequency(username)

   resp.BaseResp = &user.BaseResp{
      StatusCode: 0,
      StatusMsg:  "info success",
      Data:       strconv.Itoa(frequency),
   }

   c.JSON(200, resp)
}

运行Demo

在项目根目录下执行以下命令:

go build -o userdemo
./userdemo

或者

go run .

Hertz默认监听8888端口,你可以运行以下命令进行测试:

curl 127.0.0.1:8888/ping

如果您收到以下响应,那么恭喜你成功运行了这个演示程序!

{"message":"pong"}

其他特性

如果你查看hz生成的router/user/user.go,就可以发现它自动使用了路由组特性,这可以帮助你更好的组织不同分类的路由并设置中间件等。

当我们在main.go中使用server.Default方法,Hertz也会自动为我们使用recover中间件,这可以对panic进行优雅的处理。

现在我们已经介绍了一些主要的赫兹方法以及如何使用它们,我希望这将帮助你快速开始使用赫兹。

总结

这个演示只涵盖了Hertz特性的很小一部分。你可以查看cloudwego/hertz了解更多信息。我相信官方文档能回答你几乎所有的问题,如果有不懂的地方也可以评论或者私信我。

这个演示的代码在这里,这只是一个简单的例子,有很多不完美的地方,但如果这能帮到你,我会很高兴 :)。

BTW,下一篇文章我会介绍一个使用Hertz和Gorm的示例Demo,将着重介绍hz的参数绑定与校验ww

参考列表