《本文不涉及原理,純使用操作》
操作系統: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()函數中的監聽端口訪問