《本文不涉及原理,纯使用操作》
操作系统:Ubuntu18.04+amd64,我打包了的是我自己的平台的可执行程序,其他平台请自行使用源码编译相关工具。
开始之前,先安装protoc相关工具和环境,具体百度(我默认你肯定已经装好了,能打出protoc命令而不报出no command found)。再去下载我的模板项目,并下载相关依赖和必须工具,边下边看我应该都打包好了。缺了文件或者有问题请及时通知我,谢谢!
我的示例工程:https://gitee.com/wzj2018/grpc-example,github一言难尽,用了gitee。理论上里面的可执行程序可以直接体验。
用到的工具源码:https://github.com/grpc-ecosystem ,你需要里面的go-grpc-middleware和grpc-gateway。grpc-gateway里面有我们需要的 annotations.proto。
打包的工具们:https://download.csdn.net/download/m0_38059938/12537541。解压后最好放到GOPATH/bin里面。GOPATH/bin也要添加到系统PATH中。
先通过效果介绍一下。。。
假设做了几十个rpc接口,那么你想测试rpc调用,你想一个个请求手写吗?显然不想。现在就有这么个工具,可以只需要写少量代码,即可自动化生成可视化接口调用测试工具:
下面就进行实际操作。
想直接试试的可以现在就打开下载的工程示例自己摸索操作了,里面也有我已经编译好的程序,windows 和 linux都有。
1、准备你的*.proto协议文件。
syntax = "proto3";
//天气请求查询服务接口测试工具
package weather;
option optimize_for = CODE_SIZE;
//这个引入关键
import "google/api/annotations.proto";
service Weather {
rpc QueryCity(QueryType) returns (ResponseType) {
//这个option也很关键,它指定了swagger api的http请求地址
option (google.api.http) = {
post: "/v1/weather/city"
body:"*"
};
}
rpc QueryRegion(QueryType) returns (ResponseType) {
//这个option也很关键,它指定了swagger api的http请求地址
option (google.api.http) = {
get: "/v1/weather/region"
};
}
}
message QueryType {
string Name = 1;
uint32 Code = 2;
}
message ResponseType {
string Result = 1;
uint32 Status = 2;
}
2、MakeFile
makefile放在和proto文件一个目录下。如果你执行make报错了,可以根据报的错分析原因,最常见的就是找不到命令,这个多半是我打包的工具你没有妥善部署。
注意了,尽管我makefile里面是写的*.proto,意味着他会处理所有这个目录下的proto文件。但是,如果你真的有多个proto文件,请尽量合并成一个proto来进行测试。当然多个proto是可以的,只不过生成的代码需要人为修改,因为会出现函数的重复定义。相信读者你实在是需要了,可以稍微花点功夫处理下,我不再赘述了。
make之后,会在当前工作目录下生成若干文件:多个go代码文件,一个swagger.json文件。go代码文件中就有我们可以直接使用的已经写好的grpc调用函数。哪个json是接口说明文档,把它丢到gateway/swagger中,重命名为swagger.json,即可再浏览器中访问。
#这个记得改了
export GOPATH=/mnt/hgfs/GoPath
#注意,-I 表示以来的 *.proto 的搜索路径,我的是放在 ${GOPATH}/src下面,还有
#${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis目录下面,根据你的情况修改路径。
#当然。还有当前目录 “.”
#其他的不改。
all:
protoc -I/usr/local/include -I. \
-I${GOPATH}/src \
--go_out=plugins=grpc:. \
*.proto
protoc -I/usr/local/include -I. \
-I${GOPATH}/src \
-I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=logtostderr=true:. \
*.proto
protoc -I/usr/local/include -I. \
-I${GOPATH}/src \
-I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--swagger_out=allow_delete_body=true,allow_merge=true,logtostderr=true:. \
*.proto
protoc -I/usr/local/include -I. \
--letmegrpc_out=. \
--proto_path=${GOPATH}/src/ \
*.proto
3、编写一个模拟的server service
package main
import (
"another/pb"
"context"
"math/rand"
)
//随便实现一个最简的server service
type WeatherServer struct {
}
//rpc1模拟内容
func (w *WeatherServer) QueryRegion(ctx context.Context, queryType *weather.QueryType) (*weather.ResponseType, error) {
return &weather.ResponseType{
Result: "Great weather, The REGION you're querying:" + queryType.Name,
Status: rand.Uint32(),
}, nil
}
//rpc2模拟内容
func (w *WeatherServer) QueryCity(ctx context.Context, queryType *weather.QueryType) (*weather.ResponseType, error) {
return &weather.ResponseType{
Result: "Great weather, although I just made it up! The city you're querying:" + queryType.Name,
Status: rand.Uint32(),
}, nil
}
4、编写server主程序
//go:generate protoc --go_out=plugins=grpc:../gp -I ../gp ../gp/weather.proto
package main
import (
smart "another/pb"
"context"
"flag"
"fmt"
go_grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/grpc-ecosystem/go-grpc-middleware/auth"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"log"
"net"
)
const Token = "a"
const Authority = "a"
func main() {
port := flag.Int("p", 8787, "listen port")
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 新建grpc服务,并传入拦截器进行认证检查
grpcServer := grpc.NewServer(
grpc.StreamInterceptor(go_grpc_middleware.ChainStreamServer(
grpc_auth.StreamServerInterceptor(auth),
)),
grpc.UnaryInterceptor(go_grpc_middleware.ChainUnaryServer(
grpc_auth.UnaryServerInterceptor(auth),
)),
)
//scannerIns := &ScannerServiceInstance{}
//sealerIns := &SealerInstance{}
//pcrIns := &PCRInstance{}
weatherInstance := &WeatherServer{}
// 注册grpc实例
//smart.RegisterScannerServiceServer(grpcServer, scannerIns)
//smart.RegisterSealerServiceServer(grpcServer, sealerIns)
//smart.RegisterMetalBathServiceServer(grpcServer, pcrIns)
smart.RegisterWeatherServer(grpcServer, weatherInstance)
log.Printf("server start at %v", *port)
_ = grpcServer.Serve(lis)
}
// auth 对传入的metadata中的:authority内容进行判断,失败后再对token进行判断
func auth(ctx context.Context) (ctx1 context.Context, err error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
err = status.Error(codes.Unauthenticated, "token信息不存在")
return
}
if authority, exists := md[":authority"]; exists && len(authority) > 0 {
if authority[0] == Authority {
ctx1 = ctx
return
}
}
tokenMD := md["token"]
if tokenMD == nil || len(tokenMD) < 1 {
err = status.Error(codes.Unauthenticated, "token信息不存在")
return
}
if err = tokenValidate(tokenMD[0]); err != nil {
err = status.Error(codes.Unauthenticated, err.Error())
return
}
ctx1 = ctx
return
}
func tokenValidate(token string) (err error) {
if token != Token {
err = fmt.Errorf("token认证错误")
return
}
return
}
5、编写gateway程序
客户端内容比较呆,这里不再阐述,详情可以看我的工程实例里面。但事实上,gateway又何尝不是一个clinet?
package main
import (
weather "another/pb"
"context"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"google.golang.org/grpc"
"io/ioutil"
"log"
"net/http"
"os"
)
func run(IP string) error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
gwmux := runtime.NewServeMux()
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
opts = append(opts, grpc.WithAuthority("a"))
//*****************以下是需要修改的地方********************
//每新建一个rpc service,就要在这里给新的service注册一下
//这里事例了三个service的注册:ScannerService, MetalBathService, SealerService(名字为对应proto中的service name)
//make命令生成的注册函数命名方式固定为:RegisterXXXXXXXHandlerFromEndpoint(...)
//err := pb.RegisterScannerServiceHandlerFromEndpoint(ctx, gwmux, IP, opts)
//if err != nil {
// return err
//}
//
//err = pb.RegisterMetalBathServiceHandlerFromEndpoint(ctx, gwmux, IP, opts)
//if err != nil {
// return err
//}
//
//err = pb.RegisterSealerServiceHandlerFromEndpoint(ctx, gwmux, IP, opts)
//if err != nil {
// return err
//}
err := weather.RegisterWeatherHandlerFromEndpoint(ctx, gwmux, IP, opts)
if err != nil {
return err
}
//示例:
// err = pb.RegisterNewXXXServiceHandlerFromEndpoint(ctx, gwmux, IP, opts)
// if err != nil {
// return err
// }
//****************以上是需要修改的地方***********************
mux := http.NewServeMux()
mux.Handle("/", gwmux)
bytes, _ := ioutil.ReadFile("./swagger/favicon.ico")
mux.Handle("/swagger/", http.FileServer(http.Dir(".")))
mux.HandleFunc("/favicon.ico", func(writer http.ResponseWriter, request *http.Request) {
_, err = writer.Write(bytes)
if err != nil {
_, _ = writer.Write([]byte(err.Error()))
}
})
//指定gateway swagger的监听端口,目前是localhost:8888
log.Println("Gateway started, visit localhost:8888/swagger in your Chrome browser.")
return http.ListenAndServe(":8888", mux)
}
func main() {
log.SetFlags(log.Lshortfile|log.Ltime)
if err := run(os.Args[1]); err != nil {
log.Fatal(err)
}
}
最后
我把所有内容都尽量精简并浓缩到了一个main.go中,理论上现在只需要编译通过就行了。
总结:
1、将填写了SWAGGER API option参数的 *.proto 放到 ./pb/ 目录下
2、/pb/ 目录下,执行make
3、/pb/ 目录下,生成.go代码若干以及一个apidocs.swagger.json,将它复制到 /gateway/swagger目录下,替换掉那里的swagger.json
4、修改 /gateway/main.go 中的
1、run()函数中的需要修改部分
2、main()函数中的目标server address
5、在 /gateway/ 目录下执行 go build 命令编译,得到可执行程序,运行即可在run()函数中的监听端口访问