Hertz[hts] 是一个 Golang 微服务 HTTP 结构,在设计之初参看了其他开源结构fasthttp、gin、echo的优势, 并结合字节跳动内部的需求,使其具有高易用性、高性能、高扩展性等特征,现在在字节跳动内部已广泛运用。 如今越来越多的微服务选择运用 Golang,假设对微服务性能有要求,又期望结构可以充分满足内部的可定制化需求,Hertz 会是一个不错的选择。
设备Hertz命令行东西
请确保您的Go版别在1.15及以上版别,笔者用的版别是1.18
装备好GO的环境后,依照Hertz的命名行东西
go install github.com/cloudwego/hertz/cmd/hz@latest
验证Hertz东西是否设备成功,履行下面指令
hz -v
对应的输出hertz命令行东西版别
hz version v0.2.0
新建一个Hertz项目
- 进入到
$GOPATH
下面,新建src
文件夹,创立hertz_demo
作为项目的根目录
cd $GOPATH
mkdir src
cd src
mkdir hertz_demo
cd hertz_demo
履行hz new
初始化项目,项目初始化后,hertz会主动创立对应项目文件,文件结构如下
.
├── biz
│ ├── handler
│ │ └── ping.go
│ └── router
│ └── register.go
├── go.mod
├── main.go
├── router.go
└── router_gen.go
工作Hertz项目
- 履行
go mod tidy
收拾项目
发起hertz
控制台输出如下:
2022[/07/24]() 23:08:10.114348 engine.go:524: [Debug] HERTZ: Method=GET absolutePath=[/ping]() --> handlerName=hertz_demo[/biz/handler.Ping]() (num=2 handlers)
2022[/07/24]() 23:08:10.115227 transport.go:91: [Info] HERTZ: HTTP server listening on address=[::]:8888
此刻拜访localhost:8888/ping
服务器回来
{"message":"pong"}
Hertz获取央求参数
Query参数
通过c.Query
获取途径参数
func Person(ctx context.Context, c *app.RequestContext) {
name := c.Query("name")
c.JSON(200, utils.H{
"data": name,
})
}
curl http://localhost:8888/person?name=erik
输出:
{"data":"erik"}
途径参数
通过c.Param
获取途径参数
func HelloPerson(ctx context.Context, c *app.RequestContext) {
name := c.Query("name")
age := c.Param("age")
c.JSON(200, utils.H{
"age": age,
"name": name,
})
}
路由注册
r.GET("/person/:age", hello.HelloPerson)
央求:
curl http://localhost:8888/hello/person/12?name=erik
{
"age": "12",
"name": "erik"
}
获取央求Body
func PersonInfo(ctx context.Context, c *app.RequestContext) {
type Person struct {
Age int `json:"age"`
Name string `json:"name"`
}
body, err := c.Body()
if err != nil {
panic(err)
}
var p Person
if err := json.Unmarshal(body, &p); err != nil {
panic(err)
}
c.JSON(200, utils.H{
"person": p,
})
}
curl
curl --location --request POST 'localhost:8888/person_info' \
--header 'Content-Type: application/json' \
--data-raw '{
"age":12,
"name":"erik"
}'
{
"person": {
"age": 12,
"name": "erik"
}
}
表单参数
func PersonForm(ctx context.Context, c *app.RequestContext) {
age, _ := c.GetPostForm("age")
name, _ := c.GetPostForm("name")
c.JSON(200, utils.H{
"age": age,
"name": name,
})
}
curl --location --request POST 'localhost:8888/person_form' \
--form 'name="erik"' \
--form 'age="12"'
{
"age": "12",
"name": "erik"
}
文件上传&文件下载
func PersonUpload(ctx context.Context, c *app.RequestContext) {
fileHeader, err := c.FormFile("file")
if err != nil {
panic(err)
}
open, err := fileHeader.Open()
if err != nil {
panic(err)
}
// 读取文件到字节数组
fileRaw, err := ioutil.ReadAll(open)
if err != nil {
panic(err)
}
// 将读取到的文件写入到照应
_, err = c.Write(fileRaw)
if err != nil {
panic(err)
}
}
curl --location --request POST 'localhost:8888/person_upload' \
--form 'file=@"/img.jpeg"'
参数绑定
代码绑定是Hertz超级赞的一部分,可以非常高雅的完结央求参数映射到结构体与央求参数的验证
此处参数绑定运用了 github.com/bytedance/g…
上面别离介绍了http中常见的参数获取api,hertz出了供应api获取参数信息,还供应了参数绑定功用,帮忙我们直接将央求参数绑定到结构体上并校验参数的合法性。
func PersonBind(ctx context.Context, c *app.RequestContext) {
type person struct {
Age int `path:"age" json:"age"` // 从途径中获取参数
Name string `query:"name" json:"name"` // 从query中获取参数
City string `json:"city"` // 从body中获取参数
}
var p person
if err := c.BindAndValidate(&p); err != nil {
panic(err)
}
c.JSON(200, utils.H{
"person": p,
})
}
curl
curl --location --request POST 'http://localhost:8888/person_bind/12?name=erik' \
--header 'Content-Type: application/json' \
--data-raw '{
"city":"BeiJing"
}'
{
"person": {
"age": 12,
"name": "erik",
"city": "BeiJing"
}
}
中间件
中间件首尾相连毕竟构成一个过滤器链,用户可以在中间件中设定一些通用的处理规则,比如:共同差错处理,用户信息验证,跨域处理等
Hertz供应了两个通用的中间件,一个是JWT
验证,一个是Cors
跨域中间件,开箱即用,概况可以参看:www.cloudwego.io/zh/docs/her…
运用跨域中间件示例
func main() {
h := server.Default()
// CORS for https://foo.com and https://github.com origins, allowing:
// - PUT and PATCH methods
// - Origin header
// - Credentials share
// - Preflight requests cached for 12 hours
h.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://foo.com"},
AllowMethods: []string{"PUT", "PATCH"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return origin == "https://github.com"
},
MaxAge: 12 * time.Hour,
}))
h.Spin()
}
差错处理
我们可以凭仗Hertz供应的中间件的才干,共同对差错进行处理。即在最外层的中间件捕获差错,然后根据差错类型做对应的处理。
这儿需求凭仗三方库errors
来获取go的差错库房,便当我们排查问题
- 引进
errors
go get github.com/pkg/errors
- hertz的
app.RequestContext
供应了c.Error(err)
办法用于保存事务中产生的差错,c.Errors()
获取事务中产生的差错。所以假设程序工作时产生差错,我们可以将差错保存到app.RequestContext
中,并在中间件中获取这个差错,判别差错的类型进行对应的处理。
共同失常处理中间件代码如下:
package middleware
import (
"context"
"errors"
"fmt"
"github.com/bytedance/gopkg/util/logger"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/common/utils"
)
func GlobalErrorHandler(ctx context.Context, c *app.RequestContext) {
c.Next(ctx)
if len(c.Errors) == 0 {
// 没有收集到失常直接回来
fmt.Println("retun")
return
}
hertzErr := c.Errors[0]
// 获取errors包装的err
err := hertzErr.Unwrap()
// 打印失常库房
logger.CtxErrorf(ctx, "%+v", err)
// 获取原始err
err = errors.Unwrap(err)
// todo 进行差错类型判别
c.JSON(400, utils.H{
"code": 400,
"message": err.Error(),
})
}
装备中间件
package main
import (
"hertz_demo/biz/middleware"
"github.com/cloudwego/hertz/pkg/app/server"
)
func main() {
h := server.Default()
h.Use(middleware.GlobalErrorHandler)
register(h)
h.Spin()
}
事务代码中将差错存放到app.RequestContext
中直接退出
err = c.BindAndValidate(&req)
if err != nil {
fmt.Printf("%v", err.Error())
_ = c.Error(errors.WithStack(err))
return
}
参数校验失常时,失常库房信息如下:
validating: expr_path=Name, cause=invalid
2022/07/25 23:41:47.977087 logger.go:190: [Error] [validating: expr_path=Name, cause=invalid
hertz_demo/biz/handler/person.PersonInfo
/Users/xxx/gopath/src/hertz_demo/biz/handler/person/person_service.go:23
github.com/cloudwego/hertz/pkg/app.(*RequestContext).Next
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz@v0.2.0/pkg/app/context.go:611
hertz_demo/biz/middleware.GlobalErrorHandler
/Users/xxx/gopath/src/hertz_demo/biz/middleware/global_error_handler.go:14
github.com/cloudwego/hertz/pkg/app.(*RequestContext).Next
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz@v0.2.0/pkg/app/context.go:611
github.com/cloudwego/hertz/pkg/app/middlewares/server/recovery.Recovery.func1
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz@v0.2.0/pkg/app/middlewares/server/recovery/recovery.go:51
github.com/cloudwego/hertz/pkg/app.(*RequestContext).Next
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz@v0.2.0/pkg/app/context.go:611
github.com/cloudwego/hertz/pkg/route.(*Engine).ServeHTTP
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz@v0.2.0/pkg/route/engine.go:607
github.com/cloudwego/hertz/pkg/protocol/http1.Server.Serve
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz@v0.2.0/pkg/protocol/http1/server.go:244
github.com/cloudwego/hertz/pkg/route.(*Engine).Serve
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz@v0.2.0/pkg/route/engine.go:456
github.com/cloudwego/hertz/pkg/route.(*Engine).onData
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz@v0.2.0/pkg/route/engine.go:353
github.com/cloudwego/hertz/pkg/network/netpoll.(*transporter).ListenAndServe.func2
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/hertz@v0.2.0/pkg/network/netpoll/transport.go:83
github.com/cloudwego/netpoll.(*connection).onRequest.func2
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/netpoll@v0.2.4/connection_onevent.go:153
github.com/cloudwego/netpoll.(*connection).onProcess.func1
/Users/xxx/gopath/pkg/mod/github.com/cloudwego/netpoll@v0.2.4/connection_onevent.go:176
github.com/bytedance/gopkg/util/gopool.(*worker).run.func1.1
/Users/xxx/gopath/pkg/mod/github.com/bytedance/gopkg@v0.0.0-20220623074550-9d6d3df70991/util/gopool/worker.go:69
github.com/bytedance/gopkg/util/gopool.(*worker).run.func1
/Users/xxx/gopath/pkg/mod/github.com/bytedance/gopkg@v0.0.0-20220623074550-9d6d3df70991/util/gopool/worker.go:70
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1571]
照应结果:
{
"code": 400,
"message": "validating: expr_path=Name, cause=invalid"
}
代码生成
上面所有的代码都可以通过Hertz
供应的代码生成器生成,Hertz代码生成是通过thrift
或许grpc
的idl生成的。概况可以检查www.cloudwego.io/zh/docs/her… 这儿引荐运用grpc
生成代码
这儿介绍通过GRPC
生成Hertz
代码的方式
- 设备
protobuf
东西
// brew 设备
brew install protobuf
// 官方镜像设备,以 macos 为例
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-osx-x86_64.zip
unzip protoc-3.19.4-osx-x86_64.zip
cp bin/protoc /usr/local/bin/protoc
// 确保 include/google 放入 /usr/local/include下
cp -r include/google /usr/local/include/google
- 项目根目录新建idl文件夹,添加
api.proto
syntax = "proto3";
package api;
import "google/protobuf/descriptor.proto";
option go_package = "/api";
extend google.protobuf.FieldOptions {
optional string raw_body = 50101;
optional string query = 50102;
optional string header = 50103;
optional string cookie = 50104;
optional string body = 50105;
optional string path = 50106;
optional string vd = 50107;
optional string form = 50108;
optional string go_tag = 51001;
optional string js_conv = 50109;
}
extend google.protobuf.MethodOptions {
optional string get = 50201;
optional string post = 50202;
optional string put = 50203;
optional string delete = 50204;
optional string patch = 50205;
optional string options = 50206;
optional string head = 50207;
optional string any = 50208;
optional string gen_path = 50301;
optional string api_version = 50302;
optional string tag = 50303;
optional string name = 50304;
optional string api_level = 50305;
optional string serializer = 50306;
optional string param = 50307;
optional string baseurl = 50308;
}
extend google.protobuf.EnumValueOptions {
optional int32 http_code = 50401;
}
- 新建person文件夹,新建person.proto文件
syntax = "proto3";
package person;
option go_package = "hertz/person";
import "api.proto";
message PersonReq {
string name = 1[(api.query)="name",(api.vd)="len($)>0"];
int32 age = 2[(api.path)="age"];
string city = 3[(api.body)="city"];
}
message PersonResp {
string name = 1;
int32 age = 2;
string city = 3;
}
service PersonService {
rpc PersonInfo(PersonReq) returns(PersonResp) {
option (api.post) = "/person_info/:age";
}
}
- 整个idl的途径如下:
├── idl
│ ├── api.proto
│ └── person
│ └── person.proto
- 生成代码
hz new -I idl -idl idl/person/person.proto
- 收拾代码
go mod tidy
- 生成代码后,项目途径如下:
.
├── biz
│ ├── handler
│ │ ├── person
│ │ │ └── person_service.go
│ │ └── ping.go
│ ├── model
│ │ ├── api
│ │ │ └── api.pb.go
│ │ └── hertz
│ │ └── person
│ │ └── person.pb.go
│ └── router
│ ├── person
│ │ ├── middleware.go
│ │ └── person.go
│ └── register.go
├── go.mod
├── go.sum
├── idl
│ ├── api.proto
│ └── person
│ └── person.proto
├── main.go
├── router.go
└── router_gen.go
我们只需求在对应的person_service.go
中添加事务逻辑即可
// Code generated by hertz generator.
package person
import (
"context"
person "hertz_demo/biz/model/hertz/person"
"github.com/cloudwego/hertz/pkg/app"
"github.com/pkg/errors"
)
// PersonInfo .
// @router /person_info [GET]
func PersonInfo(ctx context.Context, c *app.RequestContext) {
var err error
var req person.PersonReq
err = c.BindAndValidate(&req)
if err != nil {
_ = c.Error(errors.WithStack(err))
return
}
resp := &person.PersonResp{
Name: req.Name,
Age: req.Age,
City: req.City,
}
c.JSON(200, resp)
}
关于Hertz的往常日常开发的相关知识点差不多就是这些,假设你对Hertz感兴趣的话,可以阅读一下对应的文档,文档写的非常通俗易懂,假设这篇文章对你有帮忙的话,麻烦点个赞注重一下,谢谢~
本文正在参与技术专题18期-聊聊Go言语结构