概述
本篇博文完整講述了如果通過 protocol buffers 定義並啟動一個 gRPC 服務,然后在 gRPC 服務上提供一個 RESTful JSON API 的反向代理 gateway,最后通過 swagger ui 來提供 RESTful JSON API 的說明,完整代碼 helloworld_restful_swagger。
Helloworld gRPC Service
參考 gRPC Quick Start for Python。
Install gRPC
安裝 gRPC
運行命令,
$ python -m pip install grpcio
安裝 gRPC 工具箱
Python 的 gRPC 工具箱包括 protol buffer 編譯器 protoc 和一些特定插件用於從 .proto 服務定義文件生成 gRPC server 和 client 的代碼。
運行命令,
$ python -m pip install grpcio-tools
服務定義文件 helloworld.proto
在 pb 目錄下新建文件 helloworld.proto,然后在其中使用 protocol buffers 語法來編寫 gRPC 服務定義。文件內容如下:
syntax = "proto3"; package helloworld; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; }
protoc 編譯生成 server 和 client 類定義
使用下面命令來生成 gRPC 的 server 和 client 類定義代碼文件,
$ python -m grpc.tools.protoc -I. --python_out=. --grpc_python_out=. pb/helloworld.proto
命令沒有報錯的話,將會在 pb 目錄下生成兩個文件 helloworld_pb2.py 和 helloworld_pb2_grpc.py。
其中文件 helloworld_pb2.py 包含了 HelloRequest 和 HelloReploy 的消息類定義,而文件 helloworld_pb2_grpc.py 提供了 gRPC server 類(GreeterServicer)和 client 類(GreeterStub)定義。
編寫具體的 gRPC 服務類
文件 helloworld_pb2_grpc.py 提供了 gRPC server 類(GreeterServicer)提供了 gRPC 服務的規范定義,沒有具體的實現。我們需要自己編寫 gRPC 服務類文件 server.py,代碼如下,
from concurrent import futures import time import grpc import pb.helloworld_pb2 as pb_dot_helloworld__pb2 import pb.helloworld_pb2_grpc as pb_dot_helloworld_pb2__grpc _ONE_DAY_IN_SECONDS = 60 * 60 * 24 class MyServer(pb_dot_helloworld_pb2__grpc.GreeterServicer): def SayHello(self, request, context): print("Receive request, request.name: {0}".format(request.name)) return pb_dot_helloworld__pb2.HelloReply( message='Hello, {0}'.format(request.name)) def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) pb_dot_helloworld_pb2__grpc.add_GreeterServicer_to_server(MyServer(), server) server.add_insecure_port('[::]:50051') print("GreeterServicer start at port 50051...") server.start() try: while True: time.sleep(_ONE_DAY_IN_SECONDS) except KeyboardInterrupt: server.stop(0) if __name__ == '__main__': serve()
然后啟動 gRPC server,
$ python server.py
lienhuadeMacBook-Pro:helloworld_restful_swagger lienhua34$ python server.py
GreeterServicer start at port 50051...
編寫 gRPC client.py
文件 helloworld_pb2_grpc.py 提供了 gRPC client 類(GreeterStub)定義。我們需要編寫自己的 client.py 代碼來通過 GreeterStub 調用 gRPC server 方法。代碼內容如下:
import grpc import pb.helloworld_pb2 as pb_dot_helloworld__pb2 import pb.helloworld_pb2_grpc as pb_dot_helloworld_pb2__grpc def run(): channel = grpc.insecure_channel('localhost:50051') stub = pb_dot_helloworld_pb2__grpc.GreeterStub(channel) response = stub.SayHello(pb_dot_helloworld__pb2.HelloRequest(name="world")) print("GreeterService client received: " + response.message) if __name__ == '__main__': run()
運行 client.py 代碼,
lienhuadeMacBook-Pro:helloworld_restful_swagger lienhua34$ python client.py
GreeterService client received: Hello, world
至此,可見我們的 gRPC helloworld 服務已經可用。
RESTful JSON API Gateway
調用 gRPC 服務需要自己編寫相對應的 client 代碼才行,這無疑給訪問 gRPC 帶來了一定的難度。我們可以通過在 gRPC 服務上面提供一個 RESTful API gateway,可以直接通過 RESTful JSON API 來訪問。
grpc-gateway 是 protoc 的一個插件,用於讀取 gRPC 服務定義,然后生成一個反向代理服務來將 RESTful JSON API 轉換為 gRPC 調用。
Install grpc-gateway
確保你本地安裝了 golang 6.0 以上版本,並且將 $GOPATH/bin 添加到 $PATH 中。然后運行下面命令,
$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
$ go get -u github.com/golang/protobuf/protoc-gen-go
修改 helloworld.proto
修改文件 helloworld.proto,添加gateway option,
syntax = "proto3"; package helloworld; import "google/api/annotations.proto" // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) { option (google.api.http) = { post: "/v1/hello" body: "*" }; } } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; }
生成 gRPC golang stub 類
運行下面命令生成 gRPC golang stub 類文件,
$ python -m grpc.tools.protoc -I. \
-I/usr/local/include \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ --go_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:. \ pb/helloworld.proto
此時便在 pb 目錄下生成 helloworld.pb.go 文件。
生成反向代理代碼
運行下面命令生成反向代理代碼,
$ python -m grpc.tools.protoc -I. \
-I/usr/local/include \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ --grpc-gateway_out=logtostderr=true:. \ pb/helloworld.proto
沒有報錯的話,將在 pb 目錄下生成文件 helloworld.pb.gw.go。
編寫 entrypoint 文件
編寫 entrypoint 文件 proxy.go,內容如下:
package main import ( "flag" "log" "net/http" "github.com/grpc-ecosystem/grpc-gateway/runtime" "golang.org/x/net/context" "google.golang.org/grpc" gw "github.com/lienhua34/notes/grpc/helloworld_restful_swagger/pb" ) var ( greeterEndpoint = flag.String("helloworld_endpoint", "localhost:50051", "endpoint of Greeter gRPC Service") ) func run() error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} err := gw.RegisterGreeterHandlerFromEndpoint(ctx, mux, *greeterEndpoint, opts) if err != nil { return err } log.Print("Greeter gRPC Server gateway start at port 8080...") http.ListenAndServe(":8080", mux) return nil } func main() { flag.Parse() if err := run(); err != nil { log.Fatal(err) } }
編譯,生成可執行文件 helloworld_restful_swagger,
$ go build .
啟動服務
先啟動 gRPC 服務,
lienhuadeMacBook-Pro:helloworld_restful_swagger lienhua34$ python server.py
GreeterServicer start at port 50051...
然后啟動 RESTful JSON API gateway,
lienhuadeMacBook-Pro:helloworld_restful_swagger lienhua34$ ./helloworld_restful_swagger
2017/01/12 15:59:17 Greeter gRPC Server gateway start at port 8080...
通過 curl 進行訪問,
lienhuadeMacBook-Pro:helloworld_restful_swagger lienhua34$ curl -X POST -k http://localhost:8080/v1/hello -d '{"name": "world"}' {"message":"Hello, world"} lienhuadeMacBook-Pro:helloworld_restful_swagger lienhua34$ curl -X POST -k http://localhost:8080/v1/hello -d '{"name": "lienhua34"}' {"message":"Hello, lienhua34"}
自此,RESTful JSON API gateway 已經可用了。
swagger UI
RESTful JSON API 的 Swagger 說明
通過下面命令可以生成 RESTful JSON API 的 swagger 說明文件。
$ python -m grpc.tools.protoc -I. \
-I/usr/local/include \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ --swagger_out=logtostderr=true:. \ pb/helloworld.proto
該命令在 pb 目錄下生成一個 helloworld.swagger.json 文件。我們在 pb 目錄下直接新增一個文件 helloworld.swagger.go,然后在里面定義一個常量 Swagger,內容即為 helloworld.swagger.json 的內容。
修改 proxy.go 文件中的 run() 方法來添加一個 API 路由來返回 swagger.json 的內容,
func run() error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := http.NewServeMux() mux.HandleFunc("/swagger.json", func(w http.ResponseWriter, req *http.Request) { io.Copy(w, strings.NewReader(gw.Swagger)) }) gwmux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} err := gw.RegisterGreeterHandlerFromEndpoint(ctx, gwmux, *greeterEndpoint, opts) if err != nil { return err } log.Print("Greeter gRPC Server gateway start at port 8080...") http.ListenAndServe(":8080", mux) return nil }
重新編譯,並啟動 RESTful gateway,然后訪問 http://localhost:8080/swagger.json 便得到 helloworld RESTful API 的 swagger 說明了。
但是,swagger.json 內容顯示太不直觀了。swagger 提供了非常好的可視化 swagger-ui。我們將 swagger-ui 添加到我們的 gateway 中。
下載 swagger-ui 代碼
Swagger 提供了可視化的 API 說明。我們可以在 RESTful JSON API gateway 中添加 swagger-ui。
將 Swagger 源碼的 dist 目錄下包含了 swagger ui 所需的 HTML、css 和 js 代碼文件,我們將該目錄下的所有文件拷貝到 third_party/swagger-ui 目錄下。
將 swagger-ui 文件制作成 go 內置文件
我們可以使用 go-bindata 將 swagger-ui 的文件制作成 go 內置的數據文件進行訪問。
先安裝 go-bindata,
$ go get -u github.com/jteeuwen/go-bindata/...
然后將 third-party/swagger-ui 下的所有文件制作成 go 內置數據文件,
$ go-bindata --nocompress -pkg swagger -o pkg/ui/data/swagger/datafile.go third_party/swagger-ui/...
生成文件 pkg/ui/data/swagger/datafile.go,
$ ls -l pkg/ui/data/swagger/datafile.go
-rw-r--r-- 1 lienhua34 staff 3912436 1 12 22:56 pkg/ui/data/swagger/datafile.go
swagger-ui 文件服務器
使用 go-bindata 將 swagger-ui 制作成 go 內置數據文件之后,我們便可以使用 elazarl/go-bindata-assetfs 結合 net/http 來將 swagger-ui 內置數據文件對外提供服務。
安裝 elazarl/go-bindata-assetfs,
$ go get github.com/elazarl/go-bindata-assetfs/...
然后修改 proxy.go 代碼,最終代碼請看 proxy.go。
重新編譯,然后啟動 gateway 服務,在瀏覽器中輸入 http://localhost:8080/swagger-ui,
但是上面打開的 swagger-ui 默認打開的是一個 http://petstore.swagger.io/v2/swagger.json 的 API 說明信息。我們需要在輸入框中輸入我們 API 的地址 http://localhost:8080/swagger.json ,然后點擊回車鍵才能看到我們的 API 說明,
如果我們想讓它打開的時候默認就是我們的 API 說明怎么辦?將文件 third_party/swagger-ui/index.html 中的 http://petstore.swagger.io/v2/swagger.json 替換成 http://localhost:8080/swagger.json ,然后重新生成 pkg/ui/data/swagger/datafile.go 文件,再重新編譯一下即可。
參考:
- http://www.grpc.io/blog/coreos
- http://www.grpc.io/docs/quickstart/python.html
- https://github.com/grpc-ecosystem/grpc-gateway
- https://github.com/philips/grpc-gateway-example
********************************************************
轉載請標注原創出處:lienhua34 http://www.cnblogs.com/lienhua34/p/6285829.html
********************************************************