gRPC 网关插件能够让 protocol buffers 编译器读取 gRPC 服务定义,并生成反向代理服务器端,该服务器端能够将 RESTful JSON API 翻译为gRPC。这是专门为 Go 编写的,为了同时支持从 gRPC 和 HTTP 客户端应用程序调用 gRPC 服务。图 8-1 展示了如何以 gRPC 方式和 RESTful方式调用 gRPC 服务。

图 8-1:gRPC 网关

如图 8-1 所示,有一个ProductInfo 服务契约,使用该契约构建了名为 ProductInfo 服务的 gRPC 服务。在此之前,我们曾构建了 gRPC客户端来与该 gRPC 服务进行交互,但在这里,我们没有构建 gRPC 客户端,而是构建了一个反向代理服务。该服务为 gRPC 服务中的每个远程方法暴露了 RESTful API,并且接收了来自 REST 客户端的 HTTP 请求,之后,它会将请求翻译成 gRPC 消息,并调用后端服务的远程方法。来自后端服务器的响应消息会再次转换成 HTTP 响应,并发送给客户端。

要为服务定义生成反向代理服务,首先需要更新服务定义,从而将gRPC 方法映射为 HTTP 资源。我们以已经创建好的同一个ProductInfo 服务为例,为其添加映射条目。代码清单 8-1 展示了更新后的 protocol buffers 定义。

代码清单 8-1 更新 ProductInfo 服务的 protocol buffers 定义

syntax = "proto3";

import "google/protobuf/wrappers.proto";
import "google/api/annotations.proto"; ➊

package ecommerce;

service ProductInfo {
    rpc addProduct(Product) returns (google.protobuf.StringValue) {
        option (google.api.http) = { ➋
            post: "/v1/product"
            body: "*"
        };
    }

    rpc getProduct(google.protobuf.StringValue) returns (Product) {
        option (google.api.http) = { ➌
            get:"/v1/product/{value}"
        };
    }
}

message Product {
    string id = 1;
    string name = 2;
    string description = 3;
    float price = 4;
}
  • ❶ 导入 proto 文件(google/api/annotations.proto)以添加对协议定义的注解支持。
  • ❷ 为 addProduct 方法添加 gRPC/HTTP 映射。声明 URL 路径模板(/v1/product)、HTTP 方法(post)以及消息体的样子。在这里,消息体映射使用了“*”,表示没有在路径模板绑定的所有字段都应该映射到请求体中。
  • ❸ 为 getProduct 方法添加 gRPC/HTTP 映射。这里是一个 GET 方法,URL 路径模板是 /v1/product/{value},传入的 ProductID 作为路径参数。

在将 gRPC 方法映射为 HTTP 资源时,还有另一些需要知道的规则。下面列出几个重要的规则。你可以参考 Google API 文档了解 HTTP 和
gRPC 映射的更多详细信息。

每个映射都需要指定一个 URL 路径模板和一个 HTTP 方法。路径模板可以包含一个或多个 gRPC 请求消息中的字段。但是,这些字段应该是 nonrepeated 的原始类型字段。

如果没有 HTTP 请求体,那么出现在请求消息中但没有出现在路径模板中的字段,将自动成为 HTTP 查询参数。

映射为 URL 查询参数的字段应该是原始类型、repeated 原始类型或 nonrepeated 消息类型。

对于查询参数的 repeated 字段,参数可以在 URL 中重复,形式为 ...?param=A&param=B

对于查询参数中的消息类型,消息的每个字段都会映射为单独的参数,比如 ...?foo.a=A&foo.b=B&foo.c=C

在编写完服务定义后,需要使用 protocol buffers 编译器对其进行编译,并生成反向代理服务的源代码。接下来讨论一下如何通过 Go 语言生成代码并实现服务器端。

在编译服务定义之前,需要获取几个依赖包。通过以下命令下载这些包:

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

在下载完这些包之后,执行以下命令编译服务定义(product_info.proto)并生成存根:

protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
product_info.proto

执行完命令后,会在相同的位置生成一个存根(product_info.pb.go)。除了生成的存根之外,我们还需要创建一个反向代理服务,以支持 RESTful 客户端的调用。这个反向代理服务可以通过 Go 编译器支持的网关插件来生成。

gRPC 网关只支持 Go 语言,这意味着我们无法为其他语言编译和生成 gRPC 网关的反向代理服务。

可以通过执行以下命令根据服务定义生成反向代理服务:

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:. \
product_info.proto

执行完命令后,它会在相同的位置生成一个反向代理服务(product_info.pb.gw.go)。

接下来为 HTTP 服务器创建监听器端点,并运行刚刚创建的反向代理服务。代码清单 8-2 展示了如何创建新的服务器实例并注册服务,以监听传入的 HTTP 请求。

代码清单 8-2 以 Go 语言编写的 HTTP 反向代理

package main

import (
    "context"
    "log"
    "net/http"
    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "google.golang.org/grpc"
    gw "github.com/grpc-up-and-running/samples/ch08/grpc-gateway/go/gw" ➊
)

var (
    grpcServerEndpoint = "localhost:50051" ➋
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    mux := runtime.NewServeMux()
    opts := []grpc.DialOption{grpc.WithInsecure()}

    err := gw.RegisterProductInfoHandlerFromEndpoint(ctx, mux,grpcServerEndpoint, opts) ➌
    if err != nil {
        log.Fatalf("Fail to register gRPC gateway service endpoint: %v", err)
    }

    if err := http.ListenAndServe(":8081", mux); err != nil { ➍
        log.Fatalf("Could not setup HTTP endpoint: %v", err)
    }
}
  • ❶ 导入生成的反向代理代码所在的包。
  • ❷ 声明 gRPC 服务器端点 URL,确保后端 gRPC 服务器在所述的端点上正常运行,这里使用与第 2 章相同的 gRPC 服务。
  • ❸ 使用代理 handler 注册 gRPC 服务器端点。在运行时,请求多路转换器(multiplexer)将 HTTP 请求匹配为模式,并调用对应的handler。
  • ❹ 开始在端口 8081 上监听 HTTP 请求。

构建完 HTTP 反向代理服务器后,就可以通过同时运行 gRPC 服务器和HTTP 服务器来测试它了。在本例中,gRPC 服务器监听端口 50051,而HTTP 服务器监听端口 8081

我们通过 curl 发送几个 HTTP 请求并观察它的行为。

01. 添加新商品到 ProductInfo 服务:

$ curl -X POST http://localhost:8081/v1/product -d '{"name": "Apple", "description": "iphone7", "price": 699}'
"38e13578-d91e-11e9"

02. 使用 ProductID 获取已有的商品:

$ curl http://localhost:8081/v1/product/38e13578-d91e-11e9{"id":"38e13578-d91e-11e9","name":"Apple","description":"iphone7","price":699}

03. 添加反向代理服务后,gRPC 网关还支持生成反向代理服务的

swagger 定义,这可以通过执行以下命令实现:

protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/\
third_party/googleapis \
--swagger_out=logtostderr=true:. \
product_info.proto

04. 执行命令后,它会在相同的位置为反向代理服务生成 swagger 定

义(product_info.swagger.json)。对于 ProductInfo 服务来说,生成的 swagger 定义如下所示:

{
    "swagger": "2.0",
    "info": {
        "title": "product_info.proto",
        "version": "version not set"
    },
    "schemes": [
        "http",
        "https"
    ],
    "consumes": [
        "application/json"
    ],
    "produces": [
        "application/json"
    ],
    "paths": {
        "/v1/product": {
            "post": {
                "operationId": "addProduct",
                "responses": {
                    "200": {
                        "description": "A successful response.",
                        "schema": {
                            "type": "string"
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "body",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/ecommerceProduct"
                        }
                    }
                ],
                "tags": [
                    "ProductInfo"
                ]
            }
        },
        "/v1/product/{value}": {
            "get": {
                "operationId": "getProduct",
                "responses": {
                    "200": {
                        "description": "A successful response.",
                        "schema": {
                            "$ref": "#/definitions/ecommerceProduct"
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "value",
                        "description": "The string value.",
                        "in": "path",
                        "required": true,
                        "type": "string"
                    }
                ],
                "tags": [
                    "ProductInfo"
                ]
            }
        }
    },
    "definitions": {
        "ecommerceProduct": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "string"
                },
                "name": {
                    "type": "string"
                },
                "description": {
                    "type": "string"
                },
                "price": {
                    "type": "number",
                    "format": "float"
                }
            }
        }
    }
}

到现在为止,我们使用 gRPC 网关为 gRPC 服务实现了 HTTP 反向代理服务。通过这种方式,可以将 gRPC 服务器端暴露给 HTTP 客户端应用程序使用。通过 gRPC 网关的仓库,可以了解更多关于网关实现的信息。

如前所述,gRPC 网关只支持 Go,同样的概念也被称为HTTP/JSON 转码。8.2 节将讨论 HTTP/JSON 转码的更多内容。

文档更新时间: 2023-09-02 07:29   作者:Minho