gRPC helloworld service, RESTful JSON API gateway and swagger UI


概述

本篇博文完整講述了如果通過 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 文件,再重新編譯一下即可。

參考:

 

********************************************************

轉載請標注原創出處:lienhua34 http://www.cnblogs.com/lienhua34/p/6285829.html

********************************************************


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM