【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()函數中的監聽端口訪問

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