[Golang] how to use the GRPC interface test tool? Swagger

Keywords: Google github JSON Makefile

This article does not involve principle, pure operation

Operating system: Ubuntu 18.04 + AMD64. I packed the executable program of my own platform. For other platforms, please use the source code compilation tools.

Before you start, install the protoc related tools and environment, specifically Baidu (by default, you must have installed it, and you can type the protoc command without reporting no command found). Then go to download my template project, and download the related dependencies and necessary tools. I should have packed them. Please inform me in time if there are any missing documents or problems. Thank you!

My example project: https://gitee.com/wzj2018/grpc-example , github is hard to say, using gitee. In theory, the executable program can be directly experienced.

Source code of tools used: https://github.com/grpc-ecosystem You need the inside go-grpc-middleware and grpc-gateway . Grpc gateway has what we need annotations.proto .

Packaged tools: https://download.csdn.net/download/m0_38059938/12537541 . After decompression, it is better to put it in GOPATH/bin. GOPATH/bin is also added to the system PATH.

Let's introduce the effect first...

Assuming dozens of rpc interfaces are made, do you want to test rpc calls and request handwriting one by one? Obviously not. Now there is a tool that can automatically generate a visual interface to call a test tool by writing a small amount of code:

Let's do the actual operation.

 

If you want to try it directly, you can open the download project example to explore and operate by yourself. There are also programs that I have compiled, both windows and linux.

 

1. Prepare your *. proto protocol file.

syntax = "proto3";

//Weather request query service interface test tool
package weather;

option optimize_for = CODE_SIZE;

//The key to this introduction
import "google/api/annotations.proto";

service Weather {
    rpc QueryCity(QueryType) returns (ResponseType) {
        //This option is also critical, which specifies the http request address of the swagger api
        option (google.api.http) = {
        post: "/v1/weather/city"
        body:"*"
        };
    }

    rpc QueryRegion(QueryType) returns (ResponseType) {
        //This option, which specifies the http request address of the swagger api, is also critical
        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

The makefile is placed in the same directory as the proto file. If you make an error, you can analyze the reason according to the error. The most common one is that you can't find the command. Most of this is that you haven't properly deployed the tool I packed.

Note that although I write *. Proto in my makefile, it means that he will process all proto files in this directory. However, if you do have multiple proto files, try to merge them into one proto for testing. Of course, multiple PROTOS can be used, but the generated code needs to be modified manually, because there will be repeated definitions of functions. I believe you really need it. You can take a little time to deal with it. I won't repeat it any more.

After make, several files will be generated in the current working directory: multiple go code files, one swagger.json Documents. In the go code file, there are already written grpc call functions that we can use directly. Which JSON is the interface description document, drop it into gateway/swagger and rename it as swagger.json , which can be accessed in the browser.

#Remember to change this
export GOPATH=/mnt/hgfs/GoPath

#Note that - I represents the search path of *. proto since I put it under ${GOPATH}/src, and
#${GOPATH}/src/ github.com/grpc -ecosystem/grpc-gateway/third_ Under the party / Google APIs directory, modify the path according to your situation.
#of course. And the current directory "."
#Others do not change.
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. Write a simulated server service

package main

import (
	"another/pb"
	"context"
	"math/rand"
)

//Implement the simplest server service
type WeatherServer struct {

}

//rpc1 simulation content
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 simulation content
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. Write server main program

//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)
	}

	// Create a new grpc service and pass it to the interceptor for authentication check
	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{}

	// Register grpc instance
	//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 judges the: authority content in the incoming metadata, and then judges the token after failure
func auth(ctx context.Context) (ctx1 context.Context, err error) {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		err = status.Error(codes.Unauthenticated, "token Information does not exist")
		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 Information does not exist")
		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 Authentication error")
		return
	}
	return
}

5. Write gateway program

The content of the client is relatively dull, which will not be explained here. For details, please refer to my project example. But in fact, gateway is not a 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"))

	//*****************Here are the changes********************
	//Every time you create a new rpc service, you need to register the new service here
	//Here is an example of the registration of three services: scannerservice, metalbathservice and sealerservice
	//The name of the registration function generated by the make command is fixed to: registerxxxxxxhandlerfromendpoint (...)

	//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
	}

	//Example:
	//	err = pb.RegisterNewXXXServiceHandlerFromEndpoint(ctx, gwmux, IP, opts)
	//	if err != nil {
	//		return err
	//	}

	//****************These are the areas that need to be modified***********************

	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()))
		}
	})

	//Specify the listening port of gateway switcher, currently 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)
	}
}

last

I've reduced everything to one main.go In theory, you just need to compile it.

Summary:
1. Put *. proto with the SWAGGER API option parameter in. / pb /
2. / pb / directory, execute make
3. / pb / directory, generate a number of. go codes and a apidocs.swagger.json , copy it to the / gateway/swagger directory, and replace the swagger.json
4. Modify / gateway/main.go In
1. The part to be modified in the run() function
2. Target server address in main() function
5. Execute the go build command in / gateway / directory to compile, get the executable program, and then run it to access the listening port in the run() function

Posted by mustng66 on Fri, 19 Jun 2020 20:29:29 -0700