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_swaggercss

Helloworld gRPC Service

參考 gRPC Quick Start for Pythonhtml

Install gRPC

安裝 gRPC

運行命令,python

$ python -m pip install grpcio

安裝 gRPC 工具箱

Python 的 gRPC 工具箱包括 protol buffer 編譯器 protoc 和一些特定插件用於從 .proto 服務定義文件生成 gRPC server 和 client 的代碼。git

運行命令,github

$ python -m pip install grpcio-tools

服務定義文件 helloworld.proto

在 pb 目錄下新建文件 helloworld.proto,而後在其中使用 protocol buffers 語法來編寫 gRPC 服務定義。文件內容以下:golang

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 類定義代碼文件,shell

$ python -m grpc.tools.protoc -I. --python_out=. --grpc_python_out=. pb/helloworld.proto

命令沒有報錯的話,將會在 pb 目錄下生成兩個文件 helloworld_pb2.py 和 helloworld_pb2_grpc.py。json

其中文件 helloworld_pb2.py 包含了 HelloRequest 和 HelloReploy 的消息類定義,而文件 helloworld_pb2_grpc.py 提供了 gRPC server 類(GreeterServicer)和 client 類(GreeterStub)定義。api

編寫具體的 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

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

相關文章
相關標籤/搜索