一般情况下测试 gRPC 服务,都是通过客户端来直接请求服务端。如果客户端还没准备好的话,也可以使用 BloomRPC 这样的 GUI 客户端。
如果环境不支持安装这种 GUI 客户端的话,那么有没有一种工具,类似于 curl
这样的,直接通过终端,在命令行发起请求呢?
答案肯定是有的,就是本文要介绍的 gRPCurl
。
gRPC Server
首先来写一个简单的 gRPC Server:
helloworld.proto:
syntax="proto3";packageproto;//Thegreetingservicedefinition.serviceGreeter{//SendsagreetingrpcSayHello(HelloRequest)returns(HelloReply){}}//Therequestmessagecontainingtheuser'sname.messageHelloRequest{stringname=1;}//TheresponsemessagecontainingthegreetingsmessageHelloReply{stringmessage=1;}
main.go
packagemainimport("context""fmt""grpc-hello/proto""log""net""google.golang.org/grpc""google.golang.org/grpc/reflection")funcmain(){lis,err:=net.Listen("tcp",":50051")iferr!=nil{log.Fatalf("failedtolisten:%v",err)}server:=grpc.NewServer()//注册grpcurl所需的reflection服务reflection.Register(server)//注册业务服务proto.RegisterGreeterServer(server,&greeter{})fmt.Println("grpcserverstart...")iferr:=server.Serve(lis);err!=nil{log.Fatalf("failedtoserve:%v",err)}}typegreeterstruct{}func(*greeter)SayHello(ctxcontext.Context,req*proto.HelloRequest)(*proto.HelloReply,error){fmt.Println(req)reply:=&proto.HelloReply{Message:"hello"}returnreply,nil}
运行服务:
gorunmain.goserverstart...
grpcurl 安装
这里我介绍三种方式:
Mac
brewinstallgrpcurl
Docker
#Downloadimagedockerpullfullstorydev/grpcurl:latest#Runthetooldockerrunfullstorydev/grpcurlapi.grpc.me:443list
go tool
如果有 Go 环境的话,可以通过 go tool 来安装:
goinstallgithub.com/fullstorydev/grpcurl/cmd/grpcurl@latest
grpcurl 使用
在使用 grpcurl 时,需要通过 -cert
和 -key
参数设置公钥和私钥文件,表示链接启用了 TLS 协议的服务。
对于没有启用 TLS 协议的 gRPC 服务,通过 -plaintext
参数忽略 TLS 证书的验证过程。
如果是 Unix Socket 协议,则需要指定 -unix
参数。
查看服务列表:
grpcurl-plaintext127.0.0.1:50051list
输出:
grpc.reflection.v1alpha.ServerReflectionproto.Greeter
查看某个服务的方法列表:
grpcurl-plaintext127.0.0.1:50051listproto.Greeter
输出:
proto.Greeter.SayHello
查看方法定义:
grpcurl-plaintext127.0.0.1:50051describeproto.Greeter.SayHello
输出:
proto.Greeter.SayHelloisamethod:rpcSayHello(.proto.HelloRequest)returns(.proto.HelloReply);
查看请求参数:
packagemainimport("context""fmt""grpc-hello/proto""log""net""google.golang.org/grpc""google.golang.org/grpc/reflection")funcmain(){lis,err:=net.Listen("tcp",":50051")iferr!=nil{log.Fatalf("failedtolisten:%v",err)}server:=grpc.NewServer()//注册grpcurl所需的reflection服务reflection.Register(server)//注册业务服务proto.RegisterGreeterServer(server,&greeter{})fmt.Println("grpcserverstart...")iferr:=server.Serve(lis);err!=nil{log.Fatalf("failedtoserve:%v",err)}}typegreeterstruct{}func(*greeter)SayHello(ctxcontext.Context,req*proto.HelloRequest)(*proto.HelloReply,error){fmt.Println(req)reply:=&proto.HelloReply{Message:"hello"}returnreply,nil}2
输出:
packagemainimport("context""fmt""grpc-hello/proto""log""net""google.golang.org/grpc""google.golang.org/grpc/reflection")funcmain(){lis,err:=net.Listen("tcp",":50051")iferr!=nil{log.Fatalf("failedtolisten:%v",err)}server:=grpc.NewServer()//注册grpcurl所需的reflection服务reflection.Register(server)//注册业务服务proto.RegisterGreeterServer(server,&greeter{})fmt.Println("grpcserverstart...")iferr:=server.Serve(lis);err!=nil{log.Fatalf("failedtoserve:%v",err)}}typegreeterstruct{}func(*greeter)SayHello(ctxcontext.Context,req*proto.HelloRequest)(*proto.HelloReply,error){fmt.Println(req)reply:=&proto.HelloReply{Message:"hello"}returnreply,nil}3
请求服务:
grpcurl-d'{"name":"zhangsan"}'-plaintext127.0.0.1:50051proto.Greeter.SayHello
输出:
packagemainimport("context""fmt""grpc-hello/proto""log""net""google.golang.org/grpc""google.golang.org/grpc/reflection")funcmain(){lis,err:=net.Listen("tcp",":50051")iferr!=nil{log.Fatalf("failedtolisten:%v",err)}server:=grpc.NewServer()//注册grpcurl所需的reflection服务reflection.Register(server)//注册业务服务proto.RegisterGreeterServer(server,&greeter{})fmt.Println("grpcserverstart...")iferr:=server.Serve(lis);err!=nil{log.Fatalf("failedtoserve:%v",err)}}typegreeterstruct{}func(*greeter)SayHello(ctxcontext.Context,req*proto.HelloRequest)(*proto.HelloReply,error){fmt.Println(req)reply:=&proto.HelloReply{Message:"hello"}returnreply,nil}5
-d
参数后面也可以跟 @
,表示从标准输入读取 json 参数,一般用于输入比较复杂的 json 数据,也可以用于测试流方法。
grpcurl-d@-plaintext127.0.0.1:50051proto.Greeter.SayHello
可能遇到的错误
可能会遇到三个报错:
1、gRPC Server 未启用 TLS:
报错信息:
packagemainimport("context""fmt""grpc-hello/proto""log""net""google.golang.org/grpc""google.golang.org/grpc/reflection")funcmain(){lis,err:=net.Listen("tcp",":50051")iferr!=nil{log.Fatalf("failedtolisten:%v",err)}server:=grpc.NewServer()//注册grpcurl所需的reflection服务reflection.Register(server)//注册业务服务proto.RegisterGreeterServer(server,&greeter{})fmt.Println("grpcserverstart...")iferr:=server.Serve(lis);err!=nil{log.Fatalf("failedtoserve:%v",err)}}typegreeterstruct{}func(*greeter)SayHello(ctxcontext.Context,req*proto.HelloRequest)(*proto.HelloReply,error){fmt.Println(req)reply:=&proto.HelloReply{Message:"hello"}returnreply,nil}7
解决:
请求时增加参数:-plaintext
,参考上面的命令。
2、服务没有启动 reflection 反射服务
报错信息:
packagemainimport("context""fmt""grpc-hello/proto""log""net""google.golang.org/grpc""google.golang.org/grpc/reflection")funcmain(){lis,err:=net.Listen("tcp",":50051")iferr!=nil{log.Fatalf("failedtolisten:%v",err)}server:=grpc.NewServer()//注册grpcurl所需的reflection服务reflection.Register(server)//注册业务服务proto.RegisterGreeterServer(server,&greeter{})fmt.Println("grpcserverstart...")iferr:=server.Serve(lis);err!=nil{log.Fatalf("failedtoserve:%v",err)}}typegreeterstruct{}func(*greeter)SayHello(ctxcontext.Context,req*proto.HelloRequest)(*proto.HelloReply,error){fmt.Println(req)reply:=&proto.HelloReply{Message:"hello"}returnreply,nil}8
解决:
这行代码是关键,一定要包含:
packagemainimport("context""fmt""grpc-hello/proto""log""net""google.golang.org/grpc""google.golang.org/grpc/reflection")funcmain(){lis,err:=net.Listen("tcp",":50051")iferr!=nil{log.Fatalf("failedtolisten:%v",err)}server:=grpc.NewServer()//注册grpcurl所需的reflection服务reflection.Register(server)//注册业务服务proto.RegisterGreeterServer(server,&greeter{})fmt.Println("grpcserverstart...")iferr:=server.Serve(lis);err!=nil{log.Fatalf("failedtoserve:%v",err)}}typegreeterstruct{}func(*greeter)SayHello(ctxcontext.Context,req*proto.HelloRequest)(*proto.HelloReply,error){fmt.Println(req)reply:=&proto.HelloReply{Message:"hello"}returnreply,nil}9
3、参数格式错误:
报错信息:
gorunmain.goserverstart...0
解决:
-d
后面参数为 json 格式,并且需要使用 ''
包裹起来。
总结:
用这个工具做一些简单的测试还是相当方便的,上手也简单。只要掌握文中提到的几条命令,基本可以涵盖大部分的测试需求了。
扩展阅读:
https://appimage.github.io/BloomRPC/
https://github.com/fullstorydev/grpcurl
文章中的脑图和源码都上传到了 GitHub,有需要的同学可自行下载。
地址: https://github.com/yongxinz/gopher/tree/main/blog