安装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 或者使用hz new -module example 初始化项目,项目初始化后,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)
}

golang WEB框架Hertz --- 获取参数的更多相关文章

  1. golang web框架设计6:上下文设计

    context,翻译为上下文,为什么要设计这个结构?就是把http的请求和响应,以及参数结合在一起,便于集中处理信息,以后框架的扩展等.好多框架比如gin,都是有这个上下文结构. context结构为 ...

  2. golang web框架设计3:controller设计

    继续学习golang web框架设计 controller作用 MVC设计模式里面的这个C,控制器. Model是后台返回的数据: View是渲染页面,通常是HTML的模板页面: Controller ...

  3. golang web框架设计2:自定义路由

    继续学习谢大的Go web框架设计 HTTP路由 http路由负责将一个http的请求交到对应的函数处理(或者一个struct的方法),路由在框架中相当于一个事件处理器,而这个时间包括 用户请求的路径 ...

  4. golang web框架设计7:整合框架

    把前面写好的路由器,控制器,日志,都整合在一起 全局变量和初始化 定义一些框架的全局变量 var ( BeeApp *App AppName string AppPath string StaticD ...

  5. golang web框架设计5:配置设计

    配置信息的解析,实现的是一个key=value,键值对的一个配置文件,类似于ini的配置格式,然后解析这个文件,把解析的数据保存到map中,最后调用的时候通过几个string,int之类的函数返回相应 ...

  6. golang web框架设计4:日志设计

    beego的日志设计思路来自于seelog,根据不同的level来记录日志,beego设计的日志是一个轻量级的,采用系统log.Logger接口,默认输出到os.Stdout,用户可以实现这个接口然后 ...

  7. golang web框架 kratos中的日志框架

    kratos是bilibili开源的一个web框架. 日志用法: logger.go package kratoslog import ( "flag" "github. ...

  8. golang web框架设计1:框架规划

    GO WEB 编程13节,如何设计一个web框架 学习谢大的web框架设计 总体介绍 实现一个简易的web框架,我们采用mvc模式来进行开发. model:模型,代表数据结构.通常来说,模型类时包含查 ...

  9. golang web框架 beego 学习 (三) beego获取参数

    直接上常用的例子吧: A:     获取URL中的参数 router func init() { beego.Router("/task/?:id/?:name", &co ...

  10. [Asp.Net] MVC 和Web API Action 获取参数的区别

    Asp.net MVC 和web api 的action 在获取从前台传入的数据是有很大不同 前台使用ajax的方式向后台发起post的请求 Content-Type:application/json ...

随机推荐

  1. LaTeX 三种短横线的区别

    在 LaTeX 中,有三种基本的短横线,它们各自的长度和用法都有所不同.这三种短横线分别是连字符.短划线(或数字短横)和长划线.下面是它们的具体描述和用法: 连字符 (Hyphen, '-') 用法: ...

  2. WebShell流量特征检测_中国菜刀篇

    80后用菜刀,90后用蚁剑,95后用冰蝎和哥斯拉,以phpshell连接为例,本文主要是对这四款经典的webshell管理工具进行流量分析和检测. 什么是一句话木马? 1.定义 顾名思义就是执行恶意指 ...

  3. Linux下载安装jdk1.8

    Linux下载安装jdk1.8 一.下载 wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=a ...

  4. RxJS 系列 – 目录

    请按顺序阅读 概念篇 Observable & Creation Operators Subject Observable to Subject (Hot, Cold, Warm, conne ...

  5. JavaScript – Modular

    前言 我几乎闪过了那几年的 Modular 混乱时代. CommonJS 火的时候, 我没有用 Node.js AMD, CMD 火的时候, 我的项目还小, 加上用了 AngularJS 自带模块功能 ...

  6. .NET常见的几种项目架构模式,你知道几种?(附带使用情况投票)

    前言 项目架构模式在软件开发中扮演着至关重要的角色,它们为开发者提供了一套组织和管理代码的指导原则,以提高软件的可维护性.可扩展性.可重用性和可测试性. 假如你有其他的项目架构模式推荐,欢迎在文末留言 ...

  7. 多线程ExecutorService 的理解与使用

    原文链接:https://www.cnblogs.com/gxz-sw/p/6754476.html 接口 Java.util.concurrent.ExecutorService 表述了异步执行的机 ...

  8. 痞子衡嵌入式:如果i.MXRT离线无法启动,试着分析ROM启动日志

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MXRT系列MCU的ROM启动日志. 关于 i.MX RT 启动问题解决的文章,痞子衡写过非常多,其中大部分都是具体到某一类启 ...

  9. 线段树can you answer these queries-------hdu4027

    问题描述: 给定一个数列,要求对指定区间内所有数开方,输出查询区间和 输入: 有很多个测试用例,每个用例第一行输出一个整数N,表示数列有N个数,1<=N<=100000;第二行输入N个整数 ...

  10. Windows右下角时间显示到秒(改注册表)

    ​ 事件起因: 由于京东秒杀,要准点抢购,于是想着能不能把Windows右下角的时间显示到秒,于是在网上查了一下,修改注册表即可 解决办法: 新建一个 ShowSecondsInSystemClock ...