【Golang】无敌好用GRPC接口测试工具的使用-Swagger

《本文不涉及原理,纯使用操作》

操作系统:Ubuntu18.04+amd64,我打包了的是我自己的平台的可执行程序,其他平台请自行使用源码编译相关工具。

开始之前,先安装protoc相关工具和环境,具体百度(我默认你肯定已经装好了,能打出protoc命令而不报出no command found)。再去下载我的模板项目,并下载相关依赖和必须工具,边下边看我应该都打包好了。缺了文件或者有问题请及时通知我,谢谢!

我的示例工程:https://gitee.com/wzj2018/grpc-example,github一言难尽,用了gitee。理论上里面的可执行程序可以直接体验。

用到的工具源码:https://github.com/grpc-ecosystem ,你需要里面的go-grpc-middlewaregrpc-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()函数中的监听端口访问

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章