编译运行
kitex 工具已经帮我们生成好了编译和运行所需的脚本:
编译:
$ sh build.sh
执行上述命令后,会生成一个 output
目录,里面含有我们的编译产物。
运行:
///
$ sh output/bootstrap.sh
执行上述命令后,Echo
服务就开始运行了。
编写客户端
有了服务端后,接下来就编写一个客户端用于调用刚刚运行起来的服务端。
首先,同样的,先创建一个目录用于存放我们的客户端代码:
$ mkdir client
进入目录:
$ cd client
创建一个 main.go
文件,然后就开始编写客户端代码了。
首先让我们创建一个调用所需的 client
:
import "example/kitex_gen/api/echo" import "github.com/cloudwego/kitex/client" ... c, err := echo.NewClient("example", client.WithHostPorts("0.0.0.0:8888")) if err != nil { log.Fatal(err) }
上述代码中,echo.NewClient
用于创建 client
,其第一个参数为调用的 服务名(用于微服务中的服务发现),第二个参数为 options,用于传入参数, 此处的 client.WithHostPorts
用于指定服务端的地址。
发起调用
import "example/kitex_gen/api" ... req := &api.Request{Message: "my request"} resp, err := c.Echo(context.Background(), req, callopt.WithRPCTimeout(3*time.Second)) if err != nil { log.Fatal(err) } log.Println(resp)
上述代码中,我们首先创建了一个请求 req
, 然后通过 c.Echo
发起了调用。
其第一个参数为 context.Context
,通过通常用其传递信息或者控制本次调用的一些行为,你可以在后续章节中找到如何使用它。
其第二个参数为本次调用的请求。
其第三个参数为本次调用的 options
,Kitex 提供了一种 callopt
机制,顾名思义——调用参数 ,有别于创建 client 时传入的参数,这里传入的参数仅对此次生效。 此处的 callopt.WithRPCTimeout
用于指定此次调用的超时(通常不需要指定,此处仅作演示之用)。
在编写完一个简单的客户端后,我们终于可以发起调用了。
可以通过下述命令来完成这一步骤:
$ go run main.go
如果不出意外,可以看到类似如下输出:
2023/01/26 07:23:35 Response({Message:my request}) 至此成功编写了一个 Kitex 的服务端和客户端,并完成了一次调用!
Hertz
安装命令行工具hz
首先,我们需要安装使用demo所需要的命令行工具 hz:
- 确保
GOPATH
环境变量已经被正确地定义(例如export GOPATH=~/go
)并且将$GOPATH/bin
添加到PATH
环境变量之中(例如export PATH=$GOPATH/bin:$PATH
);请勿将GOPATH
设置为当前用户没有读写权限的目录 - 安装 hz:
go install github.com/cloudwego/hertz/cmd/hz@latest
确定代码放置位置
- 若将代码放置于
$GOPATH/src
下,需在$GOPATH/src
下创建额外目录,进入该目录后再获取代码:
shell
复制代码
$ mkdir -p $(go env GOPATH)/src/github.com/cloudwego
$ cd $(go env GOPATH)/src/github.com/cloudwego
- 若将代码放置于 GOPATH 之外,可直接获取
编写示例代码
- 在当前目录下创建 hertz_demo 文件夹,进入该目录中
- 创建
main.go
文件 - 在
main.go
文件中添加以下代码
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() }
- 生成
go.mod
文件
$ go mod init hertz_demo
- 整理 & 拉取依赖
$ go mod tidy
运行示例代码
完成以上操作后,我们可以直接编译并启动 Server
$ go build -o hertz_demo && ./hertz_demo
如果成功启动,将看到以下信息
2023/01/28 21:47:09.626332 engine.go:567: [Debug] HERTZ: Method=GET absolutePath=/ping --> handlerName=main.main.func1 (num=2 handlers) 2023/01/28 21:47:09.629874 transport.go:84: [Info] HERTZ: HTTP server listening on address=[::]:8888
接下来,我们可以对接口进行测试
$ curl http://127.0.0.1:8888/ping 如果不出意外,可以看到类似如下输出 ruby 复制代码 $ {"message":"pong"}
到现在,我们已经成功启动了 Hertz Server,并完成了一次调用!
Hertz路由优先级
Hertz提供了参数路由和通配路由,路由的优先级为:静态路由>命名路由>通配路由
Hertz中间件
Hertz中间件的种类是多种多样的,简单分为两大类:
- 服务端中间件
- 客户端中间件
服务端中间件
中间件可以在请求更深入地传递到业务逻辑之前或之后执行:
- 中间件可以在请求到达业务逻辑之前执行,比如执行身份认证和权限认证,当中间件只有初始化(pre-handle)相关逻辑,且没有和 real handler 在一个函数调用栈中的需求时,中间件中可以省略掉最后的
.Next
,如图中的中间件 B。 - 中间件也可以在执行过业务逻辑之后执行,比如记录响应时间和从异常中恢复。如果在业务 handler 处理之后有其它处理逻辑( post-handle ),或对函数调用链(栈)有强需求,则必须显式调用
.Next
,如图中的中间件 C。
实现一个中间件
// 方式一 func MyMiddleware() app.HandlerFunc { return func(ctx context.Context, c *app.RequestContext) { // pre-handle // ... c.Next(ctx) } } // 方式二 func MyMiddleware() app.HandlerFunc { return func(ctx context.Context, c *app.RequestContext) { c.Next(ctx) // call the next middleware(handler) // post-handle // ... } }
中间件会按定义的先后顺序依次执行,如果想快速终止中间件调用,可以使用以下方法,注意当前中间件仍将执行。
Abort()
:终止后续调用AbortWithMsg(msg string, statusCode int)
:终止后续调用,并设置 response中body,和状态码AbortWithStatus(code int)
:终止后续调用,并设置状态码
Server级别中间件
Server级别中间件会对整个server的路由生效
h := server.Default() h.Use(GlobalMiddleware())
路由组级别中间件
路由组级别中间件对当前路由组下的路径生效
h := server.Default() group := h.Group("/group") group.Use(GroupMiddleware())
或者
package main import ( "context" "fmt" "github.com/cloudwego/hertz/pkg/app" "github.com/cloudwego/hertz/pkg/app/server" ) func GroupMiddleware() []app.HandlerFunc { return []app.HandlerFunc{func(ctx context.Context, c *app.RequestContext) { fmt.Println("group middleware") c.Next(ctx) }} } func main() { h := server.Default(server.WithHostPorts("127.0.0.1:8888")) group := h.Group("/group", append(GroupMiddleware(), func(ctx context.Context, c *app.RequestContext) { fmt.Println("group middleware 2") c.Next(ctx) })...) // ... h.Spin() }
使用默认中间件
Hertz 框架已经预置了常用的 recover 中间件,使用 server.Default()
默认可以注册该中间件
客户端中间件
客户端中间件可以在请求发出之前或获取响应之后执行:
- 中间件可以在请求发出之前执行,比如统一为请求添加签名或其他字段。
- 中间件也可以在收到响应之后执行,比如统一修改响应结果适配业务逻辑。
实现一个中间件
客户端中间件实现和服务端中间件不同。Client 侧无法拿到中间件 index 实现递增,因此 Client 中间件采用提前构建嵌套函数的形式实现,在实现一个中间件时,可以参考下面的代码。
func MyMiddleware(next client.Endpoint) client.Endpoint { return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) { // pre-handle // ... err = next(ctx, req, resp) if err != nil { return } // post-handle // ... } }
注册一个中间件
package main import ( "context" "fmt" "github.com/cloudwego/hertz/pkg/app/client" "github.com/cloudwego/hertz/pkg/protocol" ) func MyMiddleware(next client.Endpoint) client.Endpoint { return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) { // pre-handle // ... fmt.Println("before request") req.AppendBodyString("k1=v1&") err = next(ctx, req, resp) if err != nil { return } // post-handle // ... fmt.Println("after request") return nil } } func main() { client, _ := client.NewClient() client.Use(MyMiddleware) statusCode, body, err := client.Post(context.Background(), []byte{}, "http://httpbin.org/redirect-to?url=http%3A%2F%2Fhttpbin.org%2Fpost&status_code=302", &protocol.Args{}) fmt.Printf("%d, %s, %s", statusCode, body, err) }
总结
- 了解Gorm/Kitex/Hertz是什么
- 熟悉Gorm/Kitex/Hertz的基础使用
- 通过实战案例分析将三个框架的使用串联起来