From 1c2146e7dd38a25bb09410734ee4b47ce257fa64 Mon Sep 17 00:00:00 2001 From: jmanjon <67091862+juandemanjon@users.noreply.github.com> Date: Mon, 10 Jan 2022 13:45:38 -0800 Subject: [PATCH] Fixed for Support grpc-gateway generate_unbound_methods option #226 (#227) * Fixed for Support grpc-gateway generate_unbound_methods option #226 * test for Support grpc-gateway generate_unbound_methods option #226 * test for Support grpc-gateway generate_unbound_methods option #226 Co-authored-by: Ido David <36866853+ido-namely@users.noreply.github.com> --- README.md | 9 +++++- all/entrypoint.sh | 15 +++++++-- all/test.sh | 18 +++++++++++ all/test/test.proto | 4 +++ gwy/generate_gateway.sh | 14 +++++++- gwy/test.sh | 72 +++++++++++++++++++++++++++++++++++++++-- gwy/test/include.proto | 2 ++ gwy/test/test.proto | 8 ++++- 8 files changed, 133 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3e4124c5..1b4c3835 100755 --- a/README.md +++ b/README.md @@ -127,7 +127,8 @@ docker build -t my-grpc-gateway gen/grpc-gateway/ _NOTE_: If your service does not contain any `(google.api.http)` annotations, this build will fail with an error `...HandlerFromEndpoint is undefined`. You need to have at least one rpc -method annotated to build a gRPC Gateway. +method annotated to build a gRPC Gateway, or use `--generate-unbound-methods` option to expose +all the methods in your proto file Run this image with @@ -279,6 +280,12 @@ CONTAINER=namely/protoc-all:VVV make test (`VVV` is your version from the tag in the console output when running `make build`.) Running this will demonstrate that your new image can successfully build containers for each language. +#### gRPC Gateway test +```sh +cd gwy +./test.sh namely/gen-grpc-gateway:VVV +``` + ### Release Handled automatically via CI (githubaction). diff --git a/all/entrypoint.sh b/all/entrypoint.sh index 30b2b482..9358ee71 100755 --- a/all/entrypoint.sh +++ b/all/entrypoint.sh @@ -35,6 +35,7 @@ printUsage() { echo " --with-swagger-json-names Use with --with-gateway flag. Generated swagger file will use JSON names instead of protobuf names. (deprecated. Please use --with-openapi-json-names)" echo " --with-openapi-json-names Use with --with-gateway flag. Generated OpenAPI file will use JSON names instead of protobuf names." + echo " --generate-unbound-methods Use with --with-gateway flag. Produce the HTTP mapping even for methods without any HttpRule annotation." echo " --js-out This option overrides the 'js_out=' argument in the grpc-node and grpc-web code generation. Defaults to 'import_style=commonjs'." echo " --grpc-out This option allows overriding the left-half of the 'grpc_out=' argument (before the colon) with grpc-node and grpc-web code generation. Options are: generate_package_definition, grpc_js or grpc(depricated from April 2021). Defaults to grpc_js." echo " --grpc-web-out This option overrides the 'grpc-web_out=' argument in the grpc-web code generation. Defaults to 'import_style=typescript'." @@ -64,6 +65,7 @@ DESCR_FILENAME="descriptor_set.pb" CSHARP_OPT="" SCALA_OPT="" OPENAPI_JSON=false +GENERATE_UNBOUND_METHODS=false JS_OUT="import_style=commonjs" WEB_OUT="import_style=typescript" GRPC_OUT="grpc_js" @@ -207,6 +209,10 @@ while test $# -gt 0; do OPENAPI_JSON=true shift ;; + --generate-unbound-methods) + GENERATE_UNBOUND_METHODS=true + shift + ;; --js-out) shift JS_OUT=$1 @@ -458,13 +464,16 @@ if [ $GEN_GATEWAY = true ]; then mkdir -p ${GATEWAY_DIR} protoc $PROTO_INCLUDE \ - --grpc-gateway_out=logtostderr=true:$GATEWAY_DIR ${PROTO_FILES[@]} + --grpc-gateway_out=logtostderr=true:$GATEWAY_DIR ${PROTO_FILES[@]} \ + --grpc-gateway_opt generate_unbound_methods=$GENERATE_UNBOUND_METHODS if [[ $OPENAPI_JSON == true ]]; then protoc $PROTO_INCLUDE \ - --openapiv2_out=logtostderr=true,json_names_for_fields=true:$GATEWAY_DIR ${PROTO_FILES[@]} + --openapiv2_out=logtostderr=true,json_names_for_fields=true:$GATEWAY_DIR ${PROTO_FILES[@]} \ + --openapiv2_opt generate_unbound_methods=$GENERATE_UNBOUND_METHODS else protoc $PROTO_INCLUDE \ - --openapiv2_out=logtostderr=true,json_names_for_fields=false:$GATEWAY_DIR ${PROTO_FILES[@]} + --openapiv2_out=logtostderr=true,json_names_for_fields=false:$GATEWAY_DIR ${PROTO_FILES[@]} \ + --openapiv2_opt generate_unbound_methods=$GENERATE_UNBOUND_METHODS fi fi diff --git a/all/test.sh b/all/test.sh index f207e89d..e90558ee 100755 --- a/all/test.sh +++ b/all/test.sh @@ -11,6 +11,8 @@ fi JSON_PARAM_NAME="additionalParam" +UNBOUND_METHOD="UnboundUnary" + # Checks that directories were appropriately created, and deletes the generated directory. testGeneration() { lang=$1 @@ -126,6 +128,19 @@ testGeneration() { echo "$expected_file_name2 file was not generated with json names" exit 1 fi + elif [[ "$extra_args" == *"--generate-unbound-methods"* ]]; then + # Test that we have mapped the unbound method + if ! grep -q $UNBOUND_METHOD "$expected_output_dir$expected_file_name1" ; then + echo "$expected_file_name1 does not contain the expected method $UNBOUND_METHOD" + exit 1 + fi + else + # No extra arguments + # Test that we haven't mapped the unbound method + if grep -q $UNBOUND_METHOD "$expected_output_dir$expected_file_name1" ; then + echo "$expected_file_name1 should not contain the unexpected method $UNBOUND_METHOD" + exit 1 + fi fi fi @@ -241,6 +256,9 @@ testGeneration go "gen/pb-go" 0 --with-gateway --with-openapi-json-names # Test grpc-gateway generation + json (deprecated) (only valid for Go) testGeneration go "gen/pb-go" 0 --with-gateway --with-swagger-json-names +# Test grpc-gateway generation with unbound methods (only valid for Go) +testGeneration go "gen/pb-go" 0 --with-gateway --generate-unbound-methods + # Test go source relative generation testGeneration go "gen/pb-go" 0 --go-source-relative diff --git a/all/test/test.proto b/all/test/test.proto index b91d75ab..a41d8584 100644 --- a/all/test/test.proto +++ b/all/test/test.proto @@ -20,6 +20,7 @@ service Message { body: "*" }; } + rpc UnboundUnary (UnboundUnaryRequest) returns (UnboundUnaryResponse) {} } message ListMessageRequest { @@ -33,3 +34,6 @@ message UpdateMessageRequest { } message UpdateMessageResponse {} + +message UnboundUnaryRequest {} +message UnboundUnaryResponse {} diff --git a/gwy/generate_gateway.sh b/gwy/generate_gateway.sh index 46c3537d..3be10423 100755 --- a/gwy/generate_gateway.sh +++ b/gwy/generate_gateway.sh @@ -13,6 +13,7 @@ printUsage() { echo "-a, --additional_interfaces The set of additional interfaces to bind to this gateway." echo "-o, --out DIRECTORY Optional. The output directory for the gateway. By default, gen/grpc-gateway." echo "--go-package-map Optional. Map proto imports to go import paths" + echo "--generate-unbound-methods Optional. Produce the HTTP mapping even for methods without any HttpRule annotation." } # Path to the proto file @@ -26,6 +27,8 @@ OUT_DIR="" GO_PACKAGE_MAP="" # Extra includes. INCLUDES="" +# Generate unbound methods +GENERATE_UNBOUND_METHODS=false while test $# -gt 0; do case "$1" in @@ -95,6 +98,10 @@ while test $# -gt 0; do fi shift ;; + --generate-unbound-methods) + GENERATE_UNBOUND_METHODS=true + shift + ;; *) echo "Unrecognized option or argument: $1 in $@" echo "" @@ -123,7 +130,12 @@ fi # Generate the gateway files PROTO_DIR=$(dirname $FILE) GEN_PATH=${OUT_DIR}/gen/ -entrypoint.sh -d ${PROTO_DIR} -l go --with-gateway -o ${GEN_PATH} --go-package-map ${GO_PACKAGE_MAP} ${INCLUDES} + +if [ $GENERATE_UNBOUND_METHODS = true ]; then + entrypoint.sh -d ${PROTO_DIR} -l go --with-gateway --generate-unbound-methods -o ${GEN_PATH} --go-package-map ${GO_PACKAGE_MAP} ${INCLUDES} +else + entrypoint.sh -d ${PROTO_DIR} -l go --with-gateway -o ${GEN_PATH} --go-package-map ${GO_PACKAGE_MAP} ${INCLUDES} +fi GATEWAY_IMPORT_DIR=`find ${GEN_PATH} -type f -name "*.gw.go" -print | head -n 1 | xargs -n1 dirname` GATEWAY_IMPORT_DIR=${GATEWAY_IMPORT_DIR#"$OUT_DIR/"} diff --git a/gwy/test.sh b/gwy/test.sh index 38941a38..655b3dc2 100755 --- a/gwy/test.sh +++ b/gwy/test.sh @@ -11,8 +11,65 @@ fi CONTAINER=$1 HEADERS_FILE="./.headers" SOME_RESP_HEADER="SOME-RESPONSE-HEADER" + # Test building the gateway. -docker run --rm -v=`pwd`:/defs $CONTAINER -f test/test.proto -s Message +docker run --rm -v=`pwd`:/defs $CONTAINER -f test/test.proto -i . -s Message + +# And make sure that we can build the test gateway too. +docker build -t $CONTAINER-test-gateway gen/grpc-gateway/ + +# Now run the test container with a prefix in the background +docker run -p=8080:80 -e 'MESSAGE_PROXY_API-PREFIX=/api/' -e 'MESSAGE_RESPONSE-HEADERS_'${SOME_RESP_HEADER}'=some-value' $CONTAINER-test-gateway & + +# Give it a few to start accepting requests +sleep 5 + +# Now use curl to make sure we get an expected status. +# From https://superuser.com/a/442395 +status=`curl -i -s -o $HEADERS_FILE -w "%{http_code}" localhost:8080/api/messages` + +# For now, we expect a 503 service unavailable, since we don't have a grpc service +# running. In the future, if this was a real backend we should get a 200. However, +# here we can use the 503 to indicate that the gateway tried to send the request +# downstream. +if [ "$status" -ne "503" ]; then + kill $! + echo "Invalid status: '$status' with /api/messages http request" + exit 1 +fi + +if ! grep -qi "$SOME_RESP_HEADER" "$HEADERS_FILE"; then + kill $! + echo "header $SOME_RESP_HEADER was not found in response" + rm $HEADERS_FILE + exit 1 +fi +rm $HEADERS_FILE + +# If we call an endpoint that does not exist (say just messages), we should +# get a 404, since there's no handler for that endpoint. +status=`curl -s -o /dev/null -w "%{http_code}" localhost:8080/messages` +if [ "$status" -ne "404" ]; then + kill $! + echo "Invalid status: '$status' with /messages http request" + exit 1 +fi + +# UnboundUnary should not work +# Unbound methods require the request payload as request body (curl --data 'payload') +status=`curl -s -o /dev/null -w "%{http_code}" --data '{}' localhost:8080/api/Messages.Message/UnboundUnary` +if [ "$status" -ne "404" ]; then + kill $! + echo "Invalid status: '$status' with /api/Messages.Message/UnboundUnary http request" + exit 1 +fi + +kill $! + + + +# Test building the gateway with unbound methods. +docker run --rm -v=`pwd`:/defs $CONTAINER -f test/test.proto -i . -s Message --generate-unbound-methods # And make sure that we can build the test gateway too. docker build -t $CONTAINER-test-gateway gen/grpc-gateway/ @@ -33,10 +90,19 @@ status=`curl -i -s -o $HEADERS_FILE -w "%{http_code}" localhost:8080/api/message # downstream. if [ "$status" -ne "503" ]; then kill $! - echo "Invalid status: '$status'" + echo "Invalid status: '$status' with /api/messages http request" exit 1 fi +# UnboundUnary should work +# Unbound methods require the request payload as request body (curl --data 'payload') +status=`curl -i -s -o $HEADERS_FILE -w "%{http_code}" --data '{}' localhost:8080/api/Messages.Message/UnboundUnary` + +if [ "$status" -ne "503" ]; then + kill $! + echo "Invalid status: '$status' with /api/Messages.Message/UnboundUnary http request" + exit 1 +fi if ! grep -qi "$SOME_RESP_HEADER" "$HEADERS_FILE"; then kill $! @@ -51,7 +117,7 @@ rm $HEADERS_FILE status=`curl -s -o /dev/null -w "%{http_code}" localhost:8080/messages` if [ "$status" -ne "404" ]; then kill $! - echo "Invalid status: '$status'" + echo "Invalid status: '$status' with /messages http request" exit 1 fi diff --git a/gwy/test/include.proto b/gwy/test/include.proto index 25de493e..40cf12ac 100644 --- a/gwy/test/include.proto +++ b/gwy/test/include.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package Messages; +option go_package = "test/;test"; + message ListMessageResponse { repeated string messages = 1; } diff --git a/gwy/test/test.proto b/gwy/test/test.proto index 0e1ebce0..806ed3ae 100644 --- a/gwy/test/test.proto +++ b/gwy/test/test.proto @@ -3,7 +3,9 @@ syntax = "proto3"; package Messages; import "google/api/annotations.proto"; -import "include.proto"; +import "test/include.proto"; + +option go_package = "test/;test"; service Message { rpc ListMessage (ListMessageRequest) returns (ListMessageResponse) { @@ -11,8 +13,12 @@ service Message { get: "/messages" }; } + rpc UnboundUnary (UnboundUnaryRequest) returns (UnboundUnaryResponse) {} } message ListMessageRequest { string query = 1; } + +message UnboundUnaryRequest {} +message UnboundUnaryResponse {}