Skip to content

Commit 141dba3

Browse files
authored
Support generating unbound methods with option (#1652)
* Adds generate_unbound_methods parameter. This will also warn the user when they use the new option with the existing `warn_on_unbound_methods` * Handle generate unbound methods option. This adds the necessary code to handle the new `generate_unbound_methods` option. The strategy used is to generate default options when the service method has no option and the flag is enabled. It also adds a simple test. * Adds reference to gRPC HTTP/2 mapping. * Adds similar option to gen-swagger. * Adds example for generate_unbounud_methods parameter. * Adds resulting generated examples. * Adds missing bazel build updates. * Adds missing BUILD file. * Simplify .gitignore files. * TIDY: fixup comments. * Update generated files with new comments. * Adds documentation for generate_unbound_methods parameter. * Adds documentation for generate_unbound_methods to README. * Apply suggestions from code review `protoc` invocation example cleanup Co-authored-by: Johan Brandhorst-Satzkorn <[email protected]> * Some markdown tidyness, additional protoc invocation documentation. Co-authored-by: Johan Brandhorst-Satzkorn <[email protected]> Fixes #1649
1 parent 98045e1 commit 141dba3

29 files changed

+2311
-51
lines changed

Makefile

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ EXAMPLES=examples/internal/proto/examplepb/echo_service.proto \
7979
examples/internal/proto/examplepb/use_go_template.proto \
8080
examples/internal/proto/examplepb/response_body_service.proto
8181

82+
GENERATE_UNBOUND_METHODS_EXAMPLE=examples/internal/proto/examplepb/generate_unbound_methods.proto
83+
8284
HELLOWORLD=examples/internal/helloworld/helloworld.proto
8385

8486
EXAMPLE_SVCSRCS=$(EXAMPLES:.proto=.pb.go)
@@ -88,6 +90,10 @@ EXAMPLE_SWAGGER_MERGE=examples/internal/proto/examplepb/swagger_merge.swagger.js
8890
EXAMPLE_DEPS=examples/internal/proto/pathenum/path_enum.proto examples/internal/proto/sub/message.proto examples/internal/proto/sub2/message.proto
8991
EXAMPLE_DEPSRCS=$(EXAMPLE_DEPS:.proto=.pb.go)
9092

93+
GENERATE_UNBOUND_METHODS_EXAMPLE_SWAGGERSRCS=$(GENERATE_UNBOUND_METHODS_EXAMPLE:.proto=.swagger.json)
94+
GENERATE_UNBOUND_METHODS_EXAMPLE_SVCSRCS=$(GENERATE_UNBOUND_METHODS_EXAMPLE:.proto=.pb.go)
95+
GENERATE_UNBOUND_METHODS_EXAMPLE_GWSRCS=$(GENERATE_UNBOUND_METHODS_EXAMPLE:.proto=.pb.gw.go)
96+
9197
HELLOWORLD_SVCSRCS=$(HELLOWORLD:.proto=.pb.go)
9298
HELLOWORLD_GWSRCS=$(HELLOWORLD:.proto=.pb.gw.go)
9399

@@ -136,8 +142,14 @@ RESPONSE_BODY_EXAMPLE_SRCS=$(EXAMPLE_CLIENT_DIR)/responsebody/client.go \
136142
$(EXAMPLE_CLIENT_DIR)/responsebody/model_examplepb_response_body_out_response.go \
137143
$(EXAMPLE_CLIENT_DIR)/responsebody/model_response_response_type.go \
138144
$(EXAMPLE_CLIENT_DIR)/responsebody/api_response_body_service.go
139-
140-
EXAMPLE_CLIENT_SRCS=$(ECHO_EXAMPLE_SRCS) $(ABE_EXAMPLE_SRCS) $(UNANNOTATED_ECHO_EXAMPLE_SRCS) $(RESPONSE_BODY_EXAMPLE_SRCS)
145+
GENERATE_UNBOUND_METHODS_EXAMPLE_SPEC=examples/internal/proto/examplepb/generate_unbound_methods.swagger.json
146+
GENERATE_UNBOUND_METHODS_EXAMPLE_SRCS=$(EXAMPLE_CLIENT_DIR)/generateunboundmethods/client.go \
147+
$(EXAMPLE_CLIENT_DIR)/generateunboundmethods/response.go \
148+
$(EXAMPLE_CLIENT_DIR)/generateunboundmethods/configuration.go \
149+
$(EXAMPLE_CLIENT_DIR)/generateunboundmethods/model_examplepb_generate_unbound_methods_simple_message.go \
150+
$(EXAMPLE_CLIENT_DIR)/generateunboundmethods/api_generate_unbound_methods.go
151+
152+
EXAMPLE_CLIENT_SRCS=$(ECHO_EXAMPLE_SRCS) $(ABE_EXAMPLE_SRCS) $(UNANNOTATED_ECHO_EXAMPLE_SRCS) $(RESPONSE_BODY_EXAMPLE_SRCS) $(GENERATE_UNBOUND_METHODS_EXAMPLE_SRCS)
141153
SWAGGER_CODEGEN=swagger-codegen
142154

143155
PROTOC_INC_PATH=$(dir $(shell which protoc))/../include
@@ -182,6 +194,13 @@ $(EXAMPLE_SWAGGERSRCS): $(SWAGGER_PLUGIN) $(SWAGGER_EXAMPLES)
182194
$(EXAMPLE_SWAGGER_MERGE): $(SWAGGER_PLUGIN) $(SWAGGER_EXAMPLE_MERGE)
183195
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(SWAGGER_PLUGIN) --swagger_out=logtostderr=true,allow_repeated_fields_in_body=true,use_go_templates=true,allow_merge=true,merge_file_name=$(EXAMPLE_SWAGGER_MERGE:.swagger.json=),$(PKGMAP):. $(SWAGGER_EXAMPLE_MERGE)
184196

197+
$(GENERATE_UNBOUND_METHODS_EXAMPLE_GWSRCS): $(GATEWAY_PLUGIN) $(GENERATE_UNBOUND_METHODS_EXAMPLE)
198+
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(GATEWAY_PLUGIN) --grpc-gateway_out=logtostderr=true,allow_repeated_fields_in_body=true,generate_unbound_methods=true,$(PKGMAP)$(ADDITIONAL_GW_FLAGS):. $(GENERATE_UNBOUND_METHODS_EXAMPLE)
199+
$(GENERATE_UNBOUND_METHODS_EXAMPLE_SVCSRCS): $(GO_PLUGIN) $(GENERATE_UNBOUND_METHODS_EXAMPLE)
200+
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(GO_PLUGIN) --go_out=$(PKGMAP),plugins=grpc,paths=source_relative:. $(GENERATE_UNBOUND_METHODS_EXAMPLE)
201+
$(GENERATE_UNBOUND_METHODS_EXAMPLE_SWAGGERSRCS): $(SWAGGER_PLUGIN) $(GENERATE_UNBOUND_METHODS_EXAMPLE)
202+
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(SWAGGER_PLUGIN) --swagger_out=logtostderr=true,allow_repeated_fields_in_body=true,use_go_templates=true,generate_unbound_methods=true,$(PKGMAP):. $(GENERATE_UNBOUND_METHODS_EXAMPLE)
203+
185204
$(HELLOWORLD_SVCSRCS): $(GO_PLUGIN) $(HELLOWORLD)
186205
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(GO_PLUGIN) --go_out=$(PKGMAP),plugins=grpc,paths=source_relative:. $(HELLOWORLD)
187206

@@ -210,8 +229,13 @@ $(RESPONSE_BODY_EXAMPLE_SRCS): $(RESPONSE_BODY_EXAMPLE_SPEC)
210229
-l go -o examples/internal/clients/responsebody --additional-properties packageName=responsebody
211230
@rm -f $(EXAMPLE_CLIENT_DIR)/responsebody/README.md \
212231
$(EXAMPLE_CLIENT_DIR)/responsebody/git_push.sh
232+
$(GENERATE_UNBOUND_METHODS_EXAMPLE_SRCS): $(GENERATE_UNBOUND_METHODS_EXAMPLE_SPEC)
233+
$(SWAGGER_CODEGEN) generate -i $(GENERATE_UNBOUND_METHODS_EXAMPLE_SPEC) \
234+
-l go -o examples/internal/clients/generateunboundmethods --additional-properties packageName=generateunboundmethods
235+
@rm -f $(EXAMPLE_CLIENT_DIR)/generateunboundmethods/README.md \
236+
$(EXAMPLE_CLIENT_DIR)/generateunboundmethods/git_push.sh
213237

214-
examples: $(EXAMPLE_DEPSRCS) $(EXAMPLE_SVCSRCS) $(EXAMPLE_GWSRCS) $(EXAMPLE_SWAGGERSRCS) $(EXAMPLE_SWAGGER_MERGE) $(EXAMPLE_CLIENT_SRCS) $(HELLOWORLD_SVCSRCS) $(HELLOWORLD_GWSRCS)
238+
examples: $(EXAMPLE_DEPSRCS) $(EXAMPLE_SVCSRCS) $(EXAMPLE_GWSRCS) $(EXAMPLE_SWAGGERSRCS) $(EXAMPLE_SWAGGER_MERGE) $(EXAMPLE_CLIENT_SRCS) $(HELLOWORLD_SVCSRCS) $(HELLOWORLD_GWSRCS) $(GENERATE_UNBOUND_METHODS_EXAMPLE_GWSRCS) $(GENERATE_UNBOUND_METHODS_EXAMPLE_SVCSRCS) $(GENERATE_UNBOUND_METHODS_EXAMPLE_SWAGGERSRCS)
215239
testproto: $(RUNTIME_TEST_SRCS)
216240
test: examples testproto
217241
go test -short -race ./...
@@ -250,6 +274,9 @@ realclean: distclean
250274
rm -f $(EXAMPLE_GWSRCS)
251275
rm -f $(EXAMPLE_SWAGGERSRCS)
252276
rm -f $(EXAMPLE_CLIENT_SRCS)
277+
rm -f $(GENERATE_UNBOUND_METHODS_EXAMPLE_GWSRCS)
278+
rm -f $(GENERATE_UNBOUND_METHODS_EXAMPLE_SVCSRCS)
279+
rm -f $(GENERATE_UNBOUND_METHODS_EXAMPLE_SWAGGERSRCS)
253280
rm -f $(HELLOWORLD_SVCSRCS)
254281
rm -f $(HELLOWORLD_GWSRCS)
255282
rm -f $(OPENAPIV2_GO)

README.md

Lines changed: 94 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,60 @@ Make sure that your `$GOBIN` is in your `$PATH`.
9696
}
9797
```
9898

99-
2. Add a [`google.api.http`](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L46)
100-
annotation to your .proto file
99+
2. Generate gRPC stubs
100+
101+
This step generates the gRPC stubs that you can use to implement the service and consume from clients:
102+
103+
Here's an example of what a `protoc` command might look like to generate Go stubs:
104+
105+
```sh
106+
protoc -I . --go_out ./gen/go/ --go_opt plugins=grpc --go_opt paths=source_relative your/service/v1/your_service.proto
107+
```
108+
109+
3. Implement your service in gRPC as usual
110+
111+
1. (Optional) Generate gRPC stub in the [other programming languages](https://grpc.io/docs/).
112+
113+
For example, the following generates gRPC code for Ruby based on `your/service/v1/your_service.proto`:
114+
```sh
115+
protoc -I . --ruby_out ./gen/ruby your/service/v1/your_service.proto
116+
117+
protoc -I . --grpc-ruby_out ./gen/ruby your/service/v1/your_service.proto
118+
```
119+
2. Add the googleapis-common-protos gem (or your language equivalent) as a dependency to your project.
120+
3. Implement your gRPC service stubs
121+
122+
4. Generate reverse-proxy using `protoc-gen-grpc-gateway`
123+
124+
At this point, you have 3 options:
125+
126+
* no further modifications, use the default mapping to HTTP semantics (method, path, etc.)
127+
* this will work on any `.proto` file, but will not allow setting HTTP paths, request parameters or similar
128+
* additional `.proto` modifications to use a custom mapping
129+
* relies on parameters in the `.proto` file to set custom HTTP mappings
130+
* no `.proto` modifications, but use an external configuration file
131+
* relies on an external configuration file to set custom HTTP mappings
132+
* mostly useful when the source proto file isn't under your control
133+
134+
1. Using the default mapping
135+
136+
This requires no additional modification to the `.proto` file, but does require enabling a specific option when executing the plugin.
137+
The `generate_unbound_methods` should be enabled.
138+
139+
Here's what a `protoc` execution might look like with this option enabled:
140+
141+
```sh
142+
protoc -I . --grpc-gateway_out ./gen/go \
143+
--grpc-gateway_opt logtostderr=true \
144+
--grpc-gateway_opt paths=source_relative \
145+
--grpc-gateway_opt generate_unbound_methods=true \
146+
your/service/v1/your_service.proto
147+
```
148+
149+
2. With custom annotations
150+
151+
Add a [`google.api.http`](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L46)
152+
annotation to your .proto file
101153

102154
`your_service.proto`:
103155
```diff
@@ -130,46 +182,34 @@ annotation to your .proto file
130182
See [a_bit_of_everything.proto](examples/internal/proto/examplepb/a_bit_of_everything.proto)
131183
for examples of more annotations you can add to customize gateway behavior
132184
and generated Swagger output.
133-
134-
If you do not want to modify the proto file for use with grpc-gateway you can
185+
186+
Here's what a `protoc` execution might look like:
187+
188+
```sh
189+
protoc -I . --grpc-gateway_out ./gen/go \
190+
--grpc-gateway_opt logtostderr=true \
191+
--grpc-gateway_opt paths=source_relative \
192+
your/service/v1/your_service.proto
193+
```
194+
195+
3. External configuration
196+
If you do not want to (or cannot) modify the proto file for use with grpc-gateway you can
135197
alternatively use an external
136198
[gRPC Service Configuration](https://cloud.google.com/endpoints/docs/grpc/grpc-service-config) file.
137199
[Check our documentation](https://grpc-ecosystem.github.io/grpc-gateway/docs/grpcapiconfiguration.html)
138200
for more information.
139201
140-
3. Generate gRPC stub
141-
142-
Here is an example of what a `protoc` command might look like:
202+
Here's what a `protoc` execution might look like with this option enabled:
143203
144-
```sh
145-
protoc -I. --go_out=plugins=grpc,paths=source_relative:./gen/go/ your/service/v1/your_service.proto
146-
```
204+
```sh
205+
protoc -I . --grpc-gateway_out ./gen/go \
206+
--grpc-gateway_opt logtostderr=true \
207+
--grpc-gateway_opt paths=source_relative \
208+
--grpc-gateway_opt grpc_api_configuration=path/to/config.yaml \
209+
your/service/v1/your_service.proto
210+
```
147211
148-
It will generate a stub file with path `./gen/go/your/service/v1/your_service.pb.go`.
149-
150-
4. Implement your service in gRPC as usual
151-
152-
1. (Optional) Generate gRPC stub in the [other programming languages](https://grpc.io/docs/).
153-
154-
For example, the following generates gRPC code for Ruby based on `your/service/v1/your_service.proto`:
155-
```sh
156-
protoc -I. --ruby_out=./gen/ruby your/service/v1/your_service.proto
157-
158-
protoc -I. --grpc-ruby_out=./gen/ruby your/service/v1/your_service.proto
159-
```
160-
2. Add the googleapis-common-protos gem (or your language equivalent) as a dependency to your project.
161-
3. Implement your gRPC service stubs
162-
163-
5. Generate reverse-proxy using `protoc-gen-grpc-gateway`
164-
165-
```sh
166-
protoc -I. --grpc-gateway_out=logtostderr=true,paths=source_relative:./gen/go \
167-
your/service/v1/your_service.proto
168-
```
169-
170-
It will generate a reverse proxy `gen/go/your/service/v1/your_service.pb.gw.go`.
171-
172-
6. Write an entrypoint for the HTTP reverse-proxy server
212+
5. Write an entrypoint for the HTTP reverse-proxy server
173213
174214
```go
175215
package main
@@ -220,12 +260,14 @@ annotation to your .proto file
220260
}
221261
```
222262
223-
7. (Optional) Generate swagger definitions using `protoc-gen-swagger`
263+
6. (Optional) Generate swagger definitions using `protoc-gen-swagger`
224264
225265
```sh
226-
protoc -I. --swagger_out=logtostderr=true:./gen/swagger your/service/v1/your_service.proto
266+
protoc -I . --swagger_out ./gen/swagger --swagger_opt logtostderr=true your/service/v1/your_service.proto
227267
```
228268
269+
Note that this plugin also supports generating swagger definitions for unannotated methods; use the `generate_unbound_methods` option to enable this.
270+
229271
## Video intro
230272
231273
This GopherCon UK 2019 presentation from our maintainer
@@ -238,14 +280,27 @@ https://github.com/johanbrandhorst/grpc-gateway-boilerplate.
238280
## Parameters and flags
239281
240282
During code generation with `protoc`, flags to grpc-gateway tools must be passed
241-
through protoc using the `--<tool_suffix>_out=<flags>:<path>` pattern, for
242-
example:
283+
through protoc using one of 2 patterns:
284+
285+
* as part of the `--<tool_suffix>_out` `protoc` parameter: `--<tool_suffix>_out=<flags>:<path>`
243286
244287
```sh
245288
--grpc-gateway_out=logtostderr=true,repeated_path_param_separator=ssv:.
246289
--swagger_out=logtostderr=true,repeated_path_param_separator=ssv:.
247290
```
248291
292+
* using additional `--<tool_suffix>_opt` parameters: `--<tool_suffix>_opt=<flag>[,<flag>]*`
293+
294+
```sh
295+
--grpc-gateway_opt logtostderr=true,repeated_path_param_separator=ssv
296+
# or separately
297+
--grpc-gateway_opt logtostderr=true --grpc-gateway_opt repeated_path_param_separator=ssv
298+
299+
--swagger_opt logtostderr=true,repeated_path_param_separator=ssv
300+
# or separately
301+
--swagger_opt logtostderr=true --swagger_opt repeated_path_param_separator=ssv
302+
```
303+
249304
`protoc-gen-grpc-gateway` supports custom mapping from Protobuf `import` to
250305
Golang import paths. They are compatible with
251306
[the parameters with the same names in `protoc-gen-go`](https://github.com/golang/protobuf#parameters).

docs/_docs/grpcapiconfiguration.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,34 @@
11
---
2-
title: Usage without annotations (gRPC API Configuration)
2+
title: Usage without annotations
33
category: documentation
44
order: 100
55
---
66

77
# gRPC API Configuration
8-
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.
8+
In some situations 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.
99

10+
`grpc-gateway` supports 2 ways of dealing with these situations:
11+
12+
* [use the `generate_unbound_methods` option](#generate_unbound_methods)
13+
* [provide an external configuration file](#using-an-external-configuration-file) (gRPC API Configuration)
14+
15+
## `generate_unbound_methods`
16+
17+
Providing this parameter to the protoc plugin will make it produce the HTTP mapping even for methods without any `HttpRule` annotation.
18+
This is similar to how [Cloud Endpoints behaves](https://cloud.google.com/endpoints/docs/grpc/transcoding#where_to_configure_transcoding) and uses the way [gRPC itself](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) maps to HTTP/2:
19+
20+
* HTTP method is `POST`
21+
* URI path is built from the service's name and method: `/<fully qualified service name>/<method name>` (e.g.: `/my.package.EchoService/Echo`)
22+
* HTTP body is the serialized protobuf message.
23+
24+
NOTE: the same option is also supported by the `gen-swagger` plugin.
25+
26+
## Using an external configuration file
1027
Google Cloud Platform offers 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.
1128

1229
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.
1330

14-
## Usage of gRPC API Configuration YAML files
31+
### Usage of gRPC API Configuration YAML files
1532
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:
1633

1734
1. Define your service in gRPC as usual
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/docs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.gitignore
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2.4.8
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = [
6+
"api_generate_unbound_methods_echo_service.go",
7+
"client.go",
8+
"configuration.go",
9+
"model_examplepb_generate_unbound_methods_simple_message.go",
10+
"model_protobuf_any.go",
11+
"model_runtime_error.go",
12+
"response.go",
13+
],
14+
importpath = "github.com/grpc-ecosystem/grpc-gateway/examples/internal/clients/generateunboundmethods",
15+
visibility = ["//examples:__subpackages__"],
16+
deps = ["@org_golang_x_oauth2//:go_default_library"],
17+
)

0 commit comments

Comments
 (0)