Skip to content

Commit

Permalink
Add support for basic gRPC API Configuration YAML files (#521)
Browse files Browse the repository at this point in the history
* Add support for basic gRPC API Configuration YAML files

Separate gRPC API Configuration files as described by google at
https://cloud.google.com/endpoints/docs/grpc/grpc-service-config
can be used to separate the description of the basic API surface
in .proto files from their mapping to HTTP REST. In grpc-gateway
this allows exposing gRPC services without having to add any
annotations to their .proto files.

The format of gRPC API Configuration YAML files is defined by the
proto message schema google.api.service which can be found as
service.proto in the googleapis repository. This patch exposes
the Http part of the service configuration to the gateway
generator.

To enable this all HttpRules contained in the Http part of the
description are loaded into the generator registry as a map from
service method to HttpRules called externalHttpRules. During
loading of the proto files we can now use these externalHttpRules
to look for additional rules to apply to a service method in addition
to the ones from the annotations found in the proto file. As the
types are the same no fundamentally new codepaths are required in
either of the generators.

google.api.service is quite a complex protobuf message with lots
of dependencies on other google api. To not have to pull in all
other dependent types this patch constructs a reduced service
message type with only the Http field. This relies on protobufs
backwards compatibility guarantees which allows us to remove all
other fields. Also we do not need all protobuf features on this
custom message.

To load the actual YAML file as a protobuf message this patch uses
a new dependency on github.com/ghodss/yaml to convert the YAML into
JSON which we can then unmarshal into protobuf using jsonpb.

* Add a "Generating for unannotated protos" section to README.md

Create a new documentation page describing how to use
grpc-gateway without annotations by the use of gRPC
API Configuration files. This new section explains the
use of gRPC API Configuration YAML files to expose
unannotated .proto files in contrast to the annotation
approach.

Adjusted other parts of the documentation to hint/link
at that capability.

* Add unannotated example for e2e testing

The e2e test is basically just the echo example but with an
additional inclusion of the google Duration WKT to ensure
we properly generate the imports for those even if the annotation
WKT is not used.

To make this work we have to adjust the makefile to pass
the additional grpc api configuration file. We also have
to provide a basic server implementation and use the
generated client from the tests.
  • Loading branch information
hacst authored and achew22 committed Apr 26, 2018
1 parent b502d2d commit 5ae7f11
Show file tree
Hide file tree
Showing 37 changed files with 2,034 additions and 43 deletions.
37 changes: 30 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ GATEWAY_PLUGIN_SRC= utilities/doc.go \
protoc-gen-grpc-gateway/httprule/types.go \
protoc-gen-grpc-gateway/main.go
GATEWAY_PLUGIN_FLAGS?=
SWAGGER_PLUGIN_FLAGS?=

GOOGLEAPIS_DIR=third_party/googleapis
OUTPUT_DIR=_output
Expand All @@ -49,18 +50,24 @@ OPENAPIV2_PROTO=protoc-gen-swagger/options/openapiv2.proto protoc-gen-swagger/op
OPENAPIV2_GO=$(OPENAPIV2_PROTO:.proto=.pb.go)

PKGMAP=Mgoogle/protobuf/descriptor.proto=$(GO_PLUGIN_PKG)/descriptor,Mexamples/proto/sub/message.proto=$(PKG)/examples/proto/sub
ADDITIONAL_FLAGS=
ADDITIONAL_GW_FLAGS=
ifneq "$(GATEWAY_PLUGIN_FLAGS)" ""
ADDITIONAL_FLAGS=,$(GATEWAY_PLUGIN_FLAGS)
ADDITIONAL_GW_FLAGS=,$(GATEWAY_PLUGIN_FLAGS)
endif
ADDITIONAL_SWG_FLAGS=
ifneq "$(SWAGGER_PLUGIN_FLAGS)" ""
ADDITIONAL_SWG_FLAGS=,$(SWAGGER_PLUGIN_FLAGS)
endif
SWAGGER_EXAMPLES=examples/proto/examplepb/echo_service.proto \
examples/proto/examplepb/a_bit_of_everything.proto \
examples/proto/examplepb/wrappers.proto
examples/proto/examplepb/wrappers.proto \
examples/proto/examplepb/unannotated_echo_service.proto
EXAMPLES=examples/proto/examplepb/echo_service.proto \
examples/proto/examplepb/a_bit_of_everything.proto \
examples/proto/examplepb/stream.proto \
examples/proto/examplepb/flow_combination.proto \
examples/proto/examplepb/wrappers.proto
examples/proto/examplepb/wrappers.proto \
examples/proto/examplepb/unannotated_echo_service.proto
EXAMPLE_SVCSRCS=$(EXAMPLES:.proto=.pb.go)
EXAMPLE_GWSRCS=$(EXAMPLES:.proto=.pb.gw.go)
EXAMPLE_SWAGGERSRCS=$(SWAGGER_EXAMPLES:.proto=.swagger.json)
Expand Down Expand Up @@ -89,7 +96,13 @@ ABE_EXAMPLE_SRCS=$(EXAMPLE_CLIENT_DIR)/abe/a_bit_of_everything_nested.go \
$(EXAMPLE_CLIENT_DIR)/abe/nested_deep_enum.go \
$(EXAMPLE_CLIENT_DIR)/abe/protobuf_empty.go \
$(EXAMPLE_CLIENT_DIR)/abe/sub_string_message.go
EXAMPLE_CLIENT_SRCS=$(ECHO_EXAMPLE_SRCS) $(ABE_EXAMPLE_SRCS)
UNANNOTATED_ECHO_EXAMPLE_SPEC=examples/proto/examplepb/unannotated_echo_service.swagger.json
UNANNOTATED_ECHO_EXAMPLE_SRCS=$(EXAMPLE_CLIENT_DIR)/unannotatedecho/api_client.go \
$(EXAMPLE_CLIENT_DIR)/unannotatedecho/api_response.go \
$(EXAMPLE_CLIENT_DIR)/unannotatedecho/configuration.go \
$(EXAMPLE_CLIENT_DIR)/unannotatedecho/echo_service_api.go \
$(EXAMPLE_CLIENT_DIR)/unannotatedecho/examplepb_simple_message.go
EXAMPLE_CLIENT_SRCS=$(ECHO_EXAMPLE_SRCS) $(ABE_EXAMPLE_SRCS) $(UNANNOTATED_ECHO_EXAMPLE_SRCS)
SWAGGER_CODEGEN=swagger-codegen

PROTOC_INC_PATH=$(dir $(shell which protoc))/../include
Expand Down Expand Up @@ -120,10 +133,14 @@ $(EXAMPLE_DEPSRCS): $(GO_PLUGIN) $(EXAMPLE_DEPS)
mkdir -p $(OUTPUT_DIR)
protoc -I $(PROTOC_INC_PATH) -I. --plugin=$(GO_PLUGIN) --go_out=$(PKGMAP),plugins=grpc:$(OUTPUT_DIR) $(@:.pb.go=.proto)
cp $(OUTPUT_DIR)/$(PKG)/$@ $@ || cp $(OUTPUT_DIR)/$@ $@

$(EXAMPLE_GWSRCS): ADDITIONAL_GW_FLAGS:=$(ADDITIONAL_GW_FLAGS),grpc_api_configuration=examples/proto/examplepb/unannotated_echo_service.yaml
$(EXAMPLE_GWSRCS): $(GATEWAY_PLUGIN) $(EXAMPLES)
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(GATEWAY_PLUGIN) --grpc-gateway_out=logtostderr=true,$(PKGMAP)$(ADDITIONAL_FLAGS):. $(EXAMPLES)
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(GATEWAY_PLUGIN) --grpc-gateway_out=logtostderr=true,$(PKGMAP)$(ADDITIONAL_GW_FLAGS):. $(EXAMPLES)

$(EXAMPLE_SWAGGERSRCS): ADDITIONAL_SWG_FLAGS:=$(ADDITIONAL_SWG_FLAGS),grpc_api_configuration=examples/proto/examplepb/unannotated_echo_service.yaml
$(EXAMPLE_SWAGGERSRCS): $(SWAGGER_PLUGIN) $(SWAGGER_EXAMPLES)
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(SWAGGER_PLUGIN) --swagger_out=logtostderr=true,$(PKGMAP):. $(SWAGGER_EXAMPLES)
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(SWAGGER_PLUGIN) --swagger_out=logtostderr=true,$(PKGMAP)$(ADDITIONAL_SWG_FLAGS):. $(SWAGGER_EXAMPLES)

$(ECHO_EXAMPLE_SRCS): $(ECHO_EXAMPLE_SPEC)
$(SWAGGER_CODEGEN) generate -i $(ECHO_EXAMPLE_SPEC) \
Expand All @@ -137,6 +154,12 @@ $(ABE_EXAMPLE_SRCS): $(ABE_EXAMPLE_SPEC)
@rm -f $(EXAMPLE_CLIENT_DIR)/abe/README.md \
$(EXAMPLE_CLIENT_DIR)/abe/git_push.sh \
$(EXAMPLE_CLIENT_DIR)/abe/.travis.yml
$(UNANNOTATED_ECHO_EXAMPLE_SRCS): $(UNANNOTATED_ECHO_EXAMPLE_SPEC)
$(SWAGGER_CODEGEN) generate -i $(UNANNOTATED_ECHO_EXAMPLE_SPEC) \
-l go -o examples/clients/unannotatedecho --additional-properties packageName=unannotatedecho
@rm -f $(EXAMPLE_CLIENT_DIR)/unannotatedecho/README.md \
$(EXAMPLE_CLIENT_DIR)/unannotatedecho/git_push.sh \
$(EXAMPLE_CLIENT_DIR)/unannotatedecho/.travis.yml

examples: $(EXAMPLE_SVCSRCS) $(EXAMPLE_GWSRCS) $(EXAMPLE_DEPSRCS) $(EXAMPLE_SWAGGERSRCS) $(EXAMPLE_CLIENT_SRCS)
test: examples
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ Make sure that your `$GOPATH/bin` is in your `$PATH`.
+ }
}
```

If you do not want to modify the proto file for use with grpc-gateway you can alternatively use an external [gRPC API Configuration](https://cloud.google.com/endpoints/docs/grpc/grpc-service-config) file. [Check our documentation](https://grpc-ecosystem.github.io/grpc-gateway/docs/grpcapiconfiguration.html) for more information.

3. Generate gRPC stub

```sh
Expand Down Expand Up @@ -196,9 +199,10 @@ This parameter can be useful to pass request scoped context between the gateway

## More Examples
More examples are available under `examples` directory.
* `examplepb/echo_service.proto`, `examplepb/a_bit_of_everything.proto`: service definition
* `examplepb/echo_service.pb.go`, `examplepb/a_bit_of_everything.pb.go`: [generated] stub of the service
* `examplepb/echo_service.pb.gw.go`, `examplepb/a_bit_of_everything.pb.gw.go`: [generated] reverse proxy for the service
* `proto/examplepb/echo_service.proto`, `proto/examplepb/a_bit_of_everything.proto`, `proto/examplepb/unannotated_echo_service.proto`: service definition
* `proto/examplepb/echo_service.pb.go`, `proto/examplepb/a_bit_of_everything.pb.go`, `proto/examplepb/unannotated_echo_service.pb.go`: [generated] stub of the service
* `proto/examplepb/echo_service.pb.gw.go`, `proto/examplepb/a_bit_of_everything.pb.gw.go`, `proto/examplepb/uannotated_echo_service.pb.gw.go`: [generated] reverse proxy for the service
* `proto/examplepb/unannotated_echo_service.yaml`: gRPC API Configuration for ```unannotated_echo_service.proto```
* `server/main.go`: service implementation
* `main.go`: entrypoint of the generated reverse proxy

Expand All @@ -215,6 +219,7 @@ To use the same port for custom HTTP handlers (e.g. serving `swagger.json`), gRP
* Mapping HTTP headers with `Grpc-Metadata-` prefix to gRPC metadata (prefixed with `grpcgateway-`)
* Optionally emitting API definition for [Swagger](http://swagger.io).
* Setting [gRPC timeouts](http://www.grpc.io/docs/guides/wire.html) through inbound HTTP `Grpc-Timeout` header.
* Partial support for [gRPC API Configuration]((https://cloud.google.com/endpoints/docs/grpc/grpc-service-config)) files as an alternative to annotation.

### Want to support
But not yet.
Expand All @@ -238,6 +243,7 @@ But patch is welcome.
* HTTP headers that start with 'Grpc-Metadata-' are mapped to gRPC metadata (prefixed with `grpcgateway-`)
* While configurable, the default {un,}marshaling uses [jsonpb](https://godoc.org/github.com/golang/protobuf/jsonpb) with `OrigName: true`.


# Contribution
See [CONTRIBUTING.md](http://github.com/grpc-ecosystem/grpc-gateway/blob/master/CONTRIBUTING.md).

Expand Down
12 changes: 12 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ go_repository(
importpath = "github.com/go-resty/resty",
)

go_repository(
name = "com_github_ghodss_yaml",
commit = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7",
importpath = "github.com/ghodss/yaml",
)

go_repository(
name = "in_gopkg_yaml_v2",
commit = "eb3733d160e74a9c7e442f435eb3bea458e1d19f",
importpath = "gopkg.in/yaml.v2",
)

load("//:repositories.bzl", "repositories")

repositories()
6 changes: 6 additions & 0 deletions docs/_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ collections:
docs:
output: true

defaults:
- scope:
path: ""
values:
layout: "default"

plugins:
- jekyll-toc

Expand Down
7 changes: 4 additions & 3 deletions docs/_docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ category: documentation
# Examples

Examples are available under `examples` directory.
* `examplepb/echo_service.proto`, `examplepb/a_bit_of_everything.proto`: service definition
* `examplepb/echo_service.pb.go`, `examplepb/a_bit_of_everything.pb.go`: [generated] stub of the service
* `examplepb/echo_service.pb.gw.go`, `examplepb/a_bit_of_everything.pb.gw.go`: [generated] reverse proxy for the service
* `proto/examplepb/echo_service.proto`, `proto/examplepb/a_bit_of_everything.proto`, `proto/examplepb/unannotated_echo_service.proto`: service definition
* `proto/examplepb/echo_service.pb.go`, `proto/examplepb/a_bit_of_everything.pb.go`, `proto/examplepb/unannotated_echo_service.pb.go`: [generated] stub of the service
* `proto/examplepb/echo_service.pb.gw.go`, `proto/examplepb/a_bit_of_everything.pb.gw.go`, `proto/examplepb/uannotated_echo_service.pb.gw.go`: [generated] reverse proxy for the service
* `proto/examplepb/unannotated_echo_service.yaml`: gRPC API Configuration for ```unannotated_echo_service.proto```
* `server/main.go`: service implementation
* `main.go`: entrypoint of the generated reverse proxy

Expand Down
1 change: 1 addition & 0 deletions docs/_docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ category: documentation
* Mapping HTTP headers with `Grpc-Metadata-` prefix to gRPC metadata (prefixed with `grpcgateway-`)
* Optionally emitting API definition for [Swagger](http://swagger.io).
* Setting [gRPC timeouts](http://www.grpc.io/docs/guides/wire.html) through inbound HTTP `Grpc-Timeout` header.
* Partial support for [gRPC API Configuration](https://cloud.google.com/endpoints/docs/grpc/grpc-service-config) files as an alternative to annotation.

## Want to support
But not yet.
Expand Down
149 changes: 149 additions & 0 deletions docs/_docs/grpcapiconfiguration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
---
title: Usage without annotations (gRPC API Configuration)
category: documentation
order: 100
---

# gRPC API Configuration
In some sitations annotating the .proto file of a service is not an option. For example you might not have control over the .proto file or you might want to expose the same gRPC API multiple times in completely different ways.

Google Cloud Platform a way to do this for services hosted with them called ["gRPC API Configuration"](https://cloud.google.com/endpoints/docs/grpc/grpc-service-config). It can be used to define the behavior of a gRPC API service without modifications to the service itself in the form of [YAML](https://en.wikipedia.org/wiki/YAML) configuration files.

grpc-gateway generators implement the [HTTP rules part](https://cloud.google.com/endpoints/docs/grpc-service-config/reference/rpc/google.api#httprule) of this specification. This allows you to take a completely unannotated service proto file, add a YAML file describing its HTTP endpoints and use them together like a annotated proto file with the grpc-gateway generators.

## Usage of gRPC API Configuration YAML files
The following is equivalent to the basic [usage example](usage.html) but without direct annotation for grpc-gateway in the .proto file. Only some steps require minor changes to use a gRPC API Configuration YAML file instead:

1. Define your service in gRPC as usual

your_service.proto:
```protobuf
syntax = "proto3";
package example;
message StringMessage {
string value = 1;
}
service YourService {
rpc Echo(StringMessage) returns (StringMessage) {}
}
```

2. Instead of annotating the .proto file in this step leave it untouched and create a `your_service.yaml` with the following content:
```yaml
type: google.api.Service
config_version: 3

http:
rules:
- selector: example.YourService.Echo
post: /v1/example/echo
body: "*"
```
Use a [linter](http://www.yamllint.com/) to validate your YAML.
3. Generate gRPC stub as before
```sh
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:. \
path/to/your_service.proto
```

It will generate a stub file `path/to/your_service.pb.go`.
4. Implement your service in gRPC as usual
1. (Optional) Generate gRPC stub in the language you want.

e.g.
```sh
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--ruby_out=. \
path/to/your/service_proto
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--plugin=protoc-gen-grpc=grpc_ruby_plugin \
--grpc-ruby_out=. \
path/to/your/service.proto
```
2. Add the googleapis-common-protos gem (or your language equivalent) as a dependency to your project.
3. Implement your service

5. Generate reverse-proxy. Here we have to pass the path to the `your_service.yaml` in addition to the .proto file:
```sh
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,grpc_api_configuration=path/to/your_service.yaml:. \
path/to/your_service.proto
```

It will generate a reverse proxy `path/to/your_service.pb.gw.go` that is identical to the one produced for the annotated proto.

Note: After generating the code for each of the stubs, in order to build the code, you will want to run ```go get .``` from the directory containing the stubs.

6. Write an entrypoint

Now you need to write an entrypoint of the proxy server. This step is the whether the file is annotated or not.
```go
package main
import (
"flag"
"net/http"
"github.com/golang/glog"
"golang.org/x/net/context"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"google.golang.org/grpc"
gw "path/to/your_service_package"
)
var (
echoEndpoint = flag.String("echo_endpoint", "localhost:9090", "endpoint of YourService")
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := gw.RegisterYourServiceHandlerFromEndpoint(ctx, mux, *echoEndpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":8080", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
```

7. (Optional) Generate swagger definitions

Swagger generation in this step is equivalent to gateway generation. Again pass the path to the yaml file in addition to the proto:

```sh
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,grpc_api_configuration=path/to/your_service.yaml:. \
path/to/your_service.proto
```

All other steps work as before. If you want you can remove the googleapis include path in step 3 and 4 as the unannotated proto no longer requires them.
3 changes: 3 additions & 0 deletions docs/_docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ Make sure that your `$GOPATH/bin` is in your `$PATH`.
+ }
}
```

If you do not want to modify the proto file for use with grpc-gateway you can alternatively use an external [gRPC API Configuration](https://cloud.google.com/endpoints/docs/grpc/grpc-service-config) file. [Check our documentation](grpcapiconfiguration.html) for more information.

3. Generate gRPC stub

```sh
Expand Down
1 change: 1 addition & 0 deletions examples/clients/unannotatedecho/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/docs
1 change: 1 addition & 0 deletions examples/clients/unannotatedecho/.swagger-codegen-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.gitignore
16 changes: 16 additions & 0 deletions examples/clients/unannotatedecho/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")

package(default_visibility = ["//visibility:public"])

go_library(
name = "go_default_library",
srcs = [
"api_client.go",
"api_response.go",
"configuration.go",
"examplepb_unannotated_simple_message.go",
"unannotated_echo_service_api.go",
],
importpath = "github.com/grpc-ecosystem/grpc-gateway/examples/clients/unannotatedecho",
deps = ["@com_github_go_resty_resty//:go_default_library"],
)
Loading

0 comments on commit 5ae7f11

Please sign in to comment.