From 6e10a0d0c11c34a2717c87f39680021e664052db Mon Sep 17 00:00:00 2001 From: Anil Dasari Date: Mon, 13 Apr 2020 12:13:28 -0700 Subject: [PATCH 1/3] stream changes --- .../internal/clients/responsebody/BUILD.bazel | 2 + .../clients/responsebody/api/swagger.yaml | 75 ++++++++++ .../responsebody/api_response_body_service.go | 99 ++++++++++++++ .../docs/ResponseBodyServiceApi.md | 27 ++++ .../responsebody/docs/RuntimeStreamError.md | 14 ++ .../StreamResultOfExamplepbResponseBodyOut.md | 11 ++ .../model_runtime_stream_error.go | 18 +++ ...m_result_of_examplepb_response_body_out.go | 15 ++ .../internal/integration/integration_test.go | 66 +++++---- .../examplepb/response_body_service.pb.go | 128 +++++++++++++----- .../examplepb/response_body_service.pb.gw.go | 78 +++++++++++ .../examplepb/response_body_service.proto | 7 + .../response_body_service.swagger.json | 64 +++++++++ examples/internal/server/responsebody.go | 8 ++ .../internal/gengateway/template.go | 9 +- runtime/handler.go | 22 +-- runtime/handler_test.go | 51 ++++++- 17 files changed, 624 insertions(+), 70 deletions(-) create mode 100644 examples/internal/clients/responsebody/docs/RuntimeStreamError.md create mode 100644 examples/internal/clients/responsebody/docs/StreamResultOfExamplepbResponseBodyOut.md create mode 100644 examples/internal/clients/responsebody/model_runtime_stream_error.go create mode 100644 examples/internal/clients/responsebody/model_stream_result_of_examplepb_response_body_out.go diff --git a/examples/internal/clients/responsebody/BUILD.bazel b/examples/internal/clients/responsebody/BUILD.bazel index 4b5a31badea..9acc3d60a14 100644 --- a/examples/internal/clients/responsebody/BUILD.bazel +++ b/examples/internal/clients/responsebody/BUILD.bazel @@ -14,6 +14,8 @@ go_library( "model_protobuf_any.go", "model_response_response_type.go", "model_runtime_error.go", + "model_runtime_stream_error.go", + "model_stream_result_of_examplepb_response_body_out.go", "response.go", ], importpath = "github.com/grpc-ecosystem/grpc-gateway/examples/internal/clients/responsebody", diff --git a/examples/internal/clients/responsebody/api/swagger.yaml b/examples/internal/clients/responsebody/api/swagger.yaml index 4a3e595dcce..9a8fd0ea52a 100644 --- a/examples/internal/clients/responsebody/api/swagger.yaml +++ b/examples/internal/clients/responsebody/api/swagger.yaml @@ -30,6 +30,32 @@ paths: description: "An unexpected error response" schema: $ref: "#/definitions/runtimeError" + /responsebody/stream/{data}: + get: + tags: + - "ResponseBodyService" + operationId: "ResponseBodyService_GetResponseBodyStream" + parameters: + - name: "data" + in: "path" + required: true + type: "string" + x-exportParamName: "Data" + responses: + 200: + description: "(streaming responses)" + schema: + type: "object" + properties: + result: + $ref: "#/definitions/examplepbResponseBodyOutResponse" + error: + $ref: "#/definitions/runtimeStreamError" + title: "Stream result of examplepbResponseBodyOut" + default: + description: "An unexpected error response" + schema: + $ref: "#/definitions/runtimeError" /responsebody/{data}: get: tags: @@ -175,6 +201,9 @@ definitions: \ custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\ \n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n\ \ \"value\": \"1.212s\"\n }" + example: + value: "value" + type_url: "type_url" runtimeError: type: "object" properties: @@ -189,3 +218,49 @@ definitions: type: "array" items: $ref: "#/definitions/protobufAny" + runtimeStreamError: + type: "object" + properties: + grpc_code: + type: "integer" + format: "int32" + http_code: + type: "integer" + format: "int32" + message: + type: "string" + http_status: + type: "string" + details: + type: "array" + items: + $ref: "#/definitions/protobufAny" + example: + http_code: 6 + http_status: "http_status" + details: + - value: "value" + type_url: "type_url" + - value: "value" + type_url: "type_url" + message: "message" + grpc_code: 0 + Stream result of examplepbResponseBodyOut: + properties: + result: + $ref: "#/definitions/examplepbResponseBodyOutResponse" + error: + $ref: "#/definitions/runtimeStreamError" + example: + result: + data: "data" + error: + http_code: 6 + http_status: "http_status" + details: + - value: "value" + type_url: "type_url" + - value: "value" + type_url: "type_url" + message: "message" + grpc_code: 0 diff --git a/examples/internal/clients/responsebody/api_response_body_service.go b/examples/internal/clients/responsebody/api_response_body_service.go index 4a4e2d411a8..968077eb532 100644 --- a/examples/internal/clients/responsebody/api_response_body_service.go +++ b/examples/internal/clients/responsebody/api_response_body_service.go @@ -124,6 +124,105 @@ func (a *ResponseBodyServiceApiService) ResponseBodyServiceGetResponseBody(ctx c return localVarReturnValue, localVarHttpResponse, nil } +/* +ResponseBodyServiceApiService + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param data + +@return StreamResultOfExamplepbResponseBodyOut +*/ +func (a *ResponseBodyServiceApiService) ResponseBodyServiceGetResponseBodyStream(ctx context.Context, data string) (StreamResultOfExamplepbResponseBodyOut, *http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Get") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + localVarReturnValue StreamResultOfExamplepbResponseBodyOut + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/responsebody/stream/{data}" + localVarPath = strings.Replace(localVarPath, "{"+"data"+"}", fmt.Sprintf("%v", data), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHttpContentTypes := []string{"application/json"} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarReturnValue, localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode < 300 { + // If we succeed, return the data, otherwise pass on to decode error. + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err == nil { + return localVarReturnValue, localVarHttpResponse, err + } + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + + if localVarHttpResponse.StatusCode == 200 { + var v StreamResultOfExamplepbResponseBodyOut + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + + if localVarHttpResponse.StatusCode == 0 { + var v RuntimeError + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, nil +} + /* ResponseBodyServiceApiService * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). diff --git a/examples/internal/clients/responsebody/docs/ResponseBodyServiceApi.md b/examples/internal/clients/responsebody/docs/ResponseBodyServiceApi.md index 480b621c095..852b6c4aaf0 100644 --- a/examples/internal/clients/responsebody/docs/ResponseBodyServiceApi.md +++ b/examples/internal/clients/responsebody/docs/ResponseBodyServiceApi.md @@ -5,6 +5,7 @@ All URIs are relative to *https://localhost* Method | HTTP request | Description ------------- | ------------- | ------------- [**ResponseBodyServiceGetResponseBody**](ResponseBodyServiceApi.md#ResponseBodyServiceGetResponseBody) | **Get** /responsebody/{data} | +[**ResponseBodyServiceGetResponseBodyStream**](ResponseBodyServiceApi.md#ResponseBodyServiceGetResponseBodyStream) | **Get** /responsebody/stream/{data} | [**ResponseBodyServiceListResponseBodies**](ResponseBodyServiceApi.md#ResponseBodyServiceListResponseBodies) | **Get** /responsebodies/{data} | [**ResponseBodyServiceListResponseStrings**](ResponseBodyServiceApi.md#ResponseBodyServiceListResponseStrings) | **Get** /responsestrings/{data} | @@ -35,6 +36,32 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **ResponseBodyServiceGetResponseBodyStream** +> StreamResultOfExamplepbResponseBodyOut ResponseBodyServiceGetResponseBodyStream(ctx, data) + + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **data** | **string**| | + +### Return type + +[**StreamResultOfExamplepbResponseBodyOut**](Stream result of examplepbResponseBodyOut.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **ResponseBodyServiceListResponseBodies** > []ExamplepbRepeatedResponseBodyOutResponse ResponseBodyServiceListResponseBodies(ctx, data) diff --git a/examples/internal/clients/responsebody/docs/RuntimeStreamError.md b/examples/internal/clients/responsebody/docs/RuntimeStreamError.md new file mode 100644 index 00000000000..efea01ae5cc --- /dev/null +++ b/examples/internal/clients/responsebody/docs/RuntimeStreamError.md @@ -0,0 +1,14 @@ +# RuntimeStreamError + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**GrpcCode** | **int32** | | [optional] [default to null] +**HttpCode** | **int32** | | [optional] [default to null] +**Message** | **string** | | [optional] [default to null] +**HttpStatus** | **string** | | [optional] [default to null] +**Details** | [**[]ProtobufAny**](protobufAny.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/examples/internal/clients/responsebody/docs/StreamResultOfExamplepbResponseBodyOut.md b/examples/internal/clients/responsebody/docs/StreamResultOfExamplepbResponseBodyOut.md new file mode 100644 index 00000000000..8cf8014022a --- /dev/null +++ b/examples/internal/clients/responsebody/docs/StreamResultOfExamplepbResponseBodyOut.md @@ -0,0 +1,11 @@ +# StreamResultOfExamplepbResponseBodyOut + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Result** | [***ExamplepbResponseBodyOutResponse**](examplepbResponseBodyOutResponse.md) | | [optional] [default to null] +**Error_** | [***RuntimeStreamError**](runtimeStreamError.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/examples/internal/clients/responsebody/model_runtime_stream_error.go b/examples/internal/clients/responsebody/model_runtime_stream_error.go new file mode 100644 index 00000000000..d6f45d3c3be --- /dev/null +++ b/examples/internal/clients/responsebody/model_runtime_stream_error.go @@ -0,0 +1,18 @@ +/* + * examples/internal/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * API version: version not set + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package responsebody + +type RuntimeStreamError struct { + GrpcCode int32 `json:"grpc_code,omitempty"` + HttpCode int32 `json:"http_code,omitempty"` + Message string `json:"message,omitempty"` + HttpStatus string `json:"http_status,omitempty"` + Details []ProtobufAny `json:"details,omitempty"` +} diff --git a/examples/internal/clients/responsebody/model_stream_result_of_examplepb_response_body_out.go b/examples/internal/clients/responsebody/model_stream_result_of_examplepb_response_body_out.go new file mode 100644 index 00000000000..4fc188ba216 --- /dev/null +++ b/examples/internal/clients/responsebody/model_stream_result_of_examplepb_response_body_out.go @@ -0,0 +1,15 @@ +/* + * examples/internal/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * API version: version not set + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package responsebody + +type StreamResultOfExamplepbResponseBodyOut struct { + Result *ExamplepbResponseBodyOutResponse `json:"result,omitempty"` + Error_ *RuntimeStreamError `json:"error,omitempty"` +} diff --git a/examples/internal/integration/integration_test.go b/examples/internal/integration/integration_test.go index 2c569270538..a044315295a 100644 --- a/examples/internal/integration/integration_test.go +++ b/examples/internal/integration/integration_test.go @@ -1476,26 +1476,46 @@ func TestResponseBody(t *testing.T) { } func testResponseBody(t *testing.T, port int) { - apiURL := fmt.Sprintf("http://localhost:%d/responsebody/foo", port) - resp, err := http.Get(apiURL) - if err != nil { - t.Errorf("http.Get(%q) failed with %v; want success", apiURL, err) - return - } - defer resp.Body.Close() - buf, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Errorf("ioutil.ReadAll(resp.Body) failed with %v; want success", err) - return - } + tests := []struct { + name string + url string + wantStatus int + wantResponse string + }{{ + name: "unary case", + url: "http://localhost:%d/responsebody/foo", + wantStatus: http.StatusOK, + wantResponse: `{"data":"foo"}`, + }, { + name: "stream case", + url: "http://localhost:%d/responsebody/stream/foo", + wantStatus: http.StatusOK, + wantResponse: `{"data":"foo"}`, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + apiURL := fmt.Sprintf("http://localhost:%d/responsebody/foo", port) + resp, err := http.Get(apiURL) + if err != nil { + t.Fatalf("http.Get(%q) failed with %v; want success", apiURL, err) + } - if got, want := resp.StatusCode, http.StatusOK; got != want { - t.Errorf("resp.StatusCode = %d; want %d", got, want) - t.Logf("%s", buf) - } + defer resp.Body.Close() + buf, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("ioutil.ReadAll(resp.Body) failed with %v; want success", err) + } - if got, want := string(buf), `{"data":"foo"}`; got != want { - t.Errorf("response = %q; want %q", got, want) + if got, want := resp.StatusCode, tt.wantStatus; got != want { + t.Errorf("resp.StatusCode = %d; want %d", got, want) + t.Logf("%s", buf) + } + + if got, want := string(buf), tt.wantResponse; got != want { + t.Errorf("response = %q; want %q", got, want) + } + }) } } @@ -1526,20 +1546,20 @@ func testResponseBodies(t *testing.T, port int) { func testResponseStrings(t *testing.T, port int) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + + port = 8087 // Run Secondary server with different marshalling ch := make(chan error) go func() { - if err := runGateway(ctx, ":8081", runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{EnumsAsInts: false, EmitDefaults: true})); err != nil { + if err := runGateway(ctx, fmt.Sprintf(":%d", port), runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{EnumsAsInts: false, EmitDefaults: true})); err != nil { ch <- fmt.Errorf("cannot run gateway service: %v", err) } }() - if err := waitForGateway(ctx, 8081); err != nil { + if err := waitForGateway(ctx, uint16(port)); err != nil { t.Fatalf("waitForGateway(ctx, 8081) failed with %v; want success", err) } - port = 8081 - for i, spec := range []struct { endpoint string expectedCode int @@ -1780,4 +1800,4 @@ func testNonStandardNames(t *testing.T, port int, method string, jsonBody string if got, want := string(body), jsonBody; got != want { t.Errorf("got %q; want %q", got, want) } -} +} \ No newline at end of file diff --git a/examples/internal/proto/examplepb/response_body_service.pb.go b/examples/internal/proto/examplepb/response_body_service.pb.go index 9bfa2cb7932..1d7bb328396 100644 --- a/examples/internal/proto/examplepb/response_body_service.pb.go +++ b/examples/internal/proto/examplepb/response_body_service.pb.go @@ -313,36 +313,38 @@ func init() { } var fileDescriptor_272b2870183bbe20 = []byte{ - // 451 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0xd4, 0xcf, 0x6a, 0xd4, 0x40, - 0x1c, 0x07, 0x70, 0x27, 0xbb, 0xd6, 0xee, 0x44, 0xda, 0x65, 0x2a, 0x6d, 0x58, 0x44, 0xe2, 0x20, - 0x1a, 0x2f, 0x09, 0xc6, 0x8b, 0x1e, 0x3c, 0x34, 0x17, 0xa9, 0xca, 0x16, 0x52, 0xa5, 0xe0, 0xc1, - 0x32, 0x69, 0x7e, 0x86, 0xc1, 0x38, 0x33, 0x64, 0xa6, 0xd5, 0x20, 0x5e, 0x7c, 0x03, 0xf1, 0x3d, - 0x3c, 0x7a, 0xd0, 0x9b, 0xaf, 0xe0, 0x2b, 0xf8, 0x20, 0xb2, 0xd9, 0x64, 0x88, 0x35, 0x88, 0xb6, - 0x82, 0xa7, 0x9d, 0x7f, 0x7c, 0xe7, 0xc3, 0xfe, 0x7e, 0x13, 0x7c, 0x0f, 0x5e, 0xb3, 0x97, 0xaa, - 0x04, 0x1d, 0x71, 0x61, 0xa0, 0x12, 0xac, 0x8c, 0x54, 0x25, 0x8d, 0x8c, 0xda, 0x75, 0x95, 0x45, - 0x15, 0x68, 0x25, 0x85, 0x86, 0x83, 0x4c, 0xe6, 0xf5, 0x81, 0x86, 0xea, 0x98, 0x1f, 0x42, 0xd8, - 0x9c, 0x22, 0x41, 0x51, 0xa9, 0xc3, 0xb0, 0x60, 0x06, 0x5e, 0xb1, 0x3a, 0xec, 0xb2, 0xc2, 0x2e, - 0x2b, 0xb4, 0x29, 0xb3, 0xcb, 0x85, 0x94, 0x45, 0x09, 0x11, 0x53, 0x3c, 0x62, 0x42, 0x48, 0xc3, - 0x0c, 0x97, 0x42, 0x2f, 0x73, 0xe8, 0x35, 0xbc, 0x96, 0xb6, 0xd7, 0x24, 0x32, 0xaf, 0x77, 0x04, - 0x21, 0x78, 0x9c, 0x33, 0xc3, 0x3c, 0xe4, 0xa3, 0x60, 0x92, 0x36, 0x63, 0xfa, 0x1e, 0xe1, 0xf5, - 0xfe, 0xb1, 0xdd, 0x23, 0x43, 0x9e, 0xe1, 0xd5, 0x0e, 0xe8, 0x39, 0x3e, 0x0a, 0xdc, 0x38, 0x09, - 0xff, 0x14, 0x15, 0x9e, 0x08, 0xb3, 0xf3, 0xd4, 0x66, 0xce, 0xae, 0xe0, 0xd5, 0x6e, 0x75, 0xd0, - 0xf4, 0xd9, 0xc1, 0x5b, 0x29, 0x28, 0x60, 0x06, 0xf2, 0x93, 0xb6, 0xe7, 0x3f, 0xd9, 0x46, 0x81, - 0x1b, 0x3f, 0xf8, 0x1b, 0xdb, 0x60, 0xe8, 0x90, 0xf1, 0x13, 0xfa, 0x3d, 0x92, 0xbc, 0xc0, 0x63, - 0x53, 0x2b, 0xf0, 0x46, 0x3e, 0x0a, 0xd6, 0xe2, 0xfd, 0x7f, 0x87, 0xb0, 0x83, 0xc7, 0xb5, 0x82, - 0xb4, 0xb9, 0x84, 0xde, 0xc4, 0x17, 0xfb, 0xab, 0xc4, 0xc5, 0x17, 0x9e, 0xcc, 0x1f, 0xce, 0x77, - 0xf7, 0xe7, 0xd3, 0x73, 0xe4, 0x3c, 0x46, 0xdb, 0x53, 0xb4, 0xf8, 0x49, 0xa6, 0x0e, 0xbd, 0xf5, - 0xeb, 0x7f, 0xb7, 0x67, 0x2a, 0x2e, 0x0a, 0x4d, 0x36, 0xf1, 0xca, 0x31, 0x2b, 0x8f, 0x40, 0x7b, - 0xc8, 0x1f, 0x05, 0x93, 0xb4, 0x9d, 0xc5, 0x5f, 0xc7, 0x78, 0xa3, 0xaf, 0xd9, 0x5b, 0xf6, 0x23, - 0xf9, 0x88, 0xf0, 0xfa, 0x7d, 0x30, 0xfd, 0x2d, 0x72, 0xe7, 0x74, 0x9d, 0xb0, 0x23, 0x66, 0x77, - 0x4f, 0xdd, 0x43, 0xf4, 0xfa, 0xbb, 0x6f, 0xdf, 0x3f, 0x38, 0x3e, 0xb9, 0x64, 0x1f, 0xce, 0xe2, - 0xdd, 0x44, 0x6f, 0x16, 0x85, 0x78, 0x9b, 0xd9, 0xa2, 0x91, 0x2f, 0x08, 0x93, 0x47, 0x5c, 0xf7, - 0xc5, 0x1c, 0xf4, 0x19, 0xcc, 0xdb, 0x67, 0x2e, 0x2b, 0x0d, 0x1a, 0x3b, 0x25, 0x9b, 0x7d, 0x3b, - 0x07, 0x3d, 0xa8, 0xdf, 0xe8, 0xeb, 0xbb, 0xb2, 0xfd, 0x17, 0x7e, 0x7b, 0x39, 0xbd, 0xd1, 0xf0, - 0xaf, 0x92, 0x2d, 0xcb, 0xd7, 0xcb, 0x9d, 0xce, 0xdf, 0x36, 0x51, 0xe2, 0x3e, 0x9d, 0xd8, 0xb4, - 0x6c, 0xa5, 0xf9, 0x04, 0xdd, 0xfe, 0x11, 0x00, 0x00, 0xff, 0xff, 0xa5, 0x10, 0x29, 0xfa, 0x0b, - 0x05, 0x00, 0x00, + // 482 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x94, 0xc1, 0x6e, 0xd3, 0x40, + 0x10, 0x86, 0xd9, 0xa4, 0x84, 0x66, 0x82, 0xda, 0x68, 0x0a, 0x6d, 0x14, 0x10, 0x32, 0x16, 0xa2, + 0xe6, 0x80, 0x0d, 0xe1, 0x02, 0x07, 0x0e, 0xcd, 0x05, 0x15, 0x50, 0x2a, 0x39, 0xa0, 0x4a, 0x1c, + 0xa8, 0x36, 0xcd, 0x60, 0x59, 0xa4, 0xbb, 0x2b, 0xef, 0xb6, 0x60, 0x21, 0x2e, 0x7d, 0x03, 0xc4, + 0x7b, 0x70, 0xe4, 0x00, 0xe2, 0x29, 0x78, 0x05, 0x1e, 0x04, 0xc5, 0xb1, 0x57, 0x6e, 0x6a, 0x21, + 0x68, 0x91, 0x72, 0xf2, 0xee, 0xec, 0xea, 0x9f, 0x4f, 0xfb, 0xff, 0x63, 0x78, 0x4c, 0xef, 0xf9, + 0x81, 0x9a, 0x90, 0x0e, 0x62, 0x61, 0x28, 0x11, 0x7c, 0x12, 0xa8, 0x44, 0x1a, 0x19, 0xe4, 0x75, + 0x35, 0x0a, 0x12, 0xd2, 0x4a, 0x0a, 0x4d, 0x7b, 0x23, 0x39, 0x4e, 0xf7, 0x34, 0x25, 0x47, 0xf1, + 0x3e, 0xf9, 0xd9, 0x2d, 0xf4, 0xa2, 0x44, 0xed, 0xfb, 0x11, 0x37, 0xf4, 0x8e, 0xa7, 0x7e, 0xa1, + 0xe5, 0x17, 0x5a, 0xbe, 0x55, 0xe9, 0x5e, 0x8f, 0xa4, 0x8c, 0x26, 0x14, 0x70, 0x15, 0x07, 0x5c, + 0x08, 0x69, 0xb8, 0x89, 0xa5, 0xd0, 0x33, 0x1d, 0xf7, 0x16, 0xac, 0x84, 0x79, 0x9b, 0xbe, 0x1c, + 0xa7, 0xdb, 0x02, 0x11, 0x96, 0xc6, 0xdc, 0xf0, 0x0e, 0x73, 0x98, 0xd7, 0x0c, 0xb3, 0xb5, 0xfb, + 0x89, 0xc1, 0x6a, 0xf9, 0xda, 0xce, 0xa1, 0xc1, 0xd7, 0xb0, 0x5c, 0x00, 0x76, 0x6a, 0x0e, 0xf3, + 0x5a, 0xbd, 0xbe, 0xff, 0xb7, 0x50, 0xfe, 0x9c, 0x98, 0xdd, 0x87, 0x56, 0xb3, 0x7b, 0x03, 0x96, + 0x8b, 0x6a, 0x25, 0xd3, 0xb7, 0x1a, 0x6c, 0x84, 0xa4, 0x88, 0x1b, 0x1a, 0xcf, 0xb3, 0xbd, 0x39, + 0xc1, 0x56, 0xf7, 0x5a, 0xbd, 0xa7, 0xff, 0xc2, 0x56, 0x29, 0x5a, 0xc5, 0xf8, 0x95, 0xfd, 0x19, + 0x12, 0xdf, 0xc2, 0x92, 0x49, 0x15, 0x75, 0xea, 0x0e, 0xf3, 0x56, 0x7a, 0xbb, 0xff, 0x0f, 0xc2, + 0x2e, 0x5e, 0xa4, 0x8a, 0xc2, 0xac, 0x89, 0x7b, 0x07, 0x2e, 0x97, 0xab, 0xd8, 0x82, 0x4b, 0x2f, + 0x07, 0xcf, 0x06, 0x3b, 0xbb, 0x83, 0xf6, 0x05, 0xbc, 0x08, 0x6c, 0xab, 0xcd, 0xa6, 0x9f, 0x7e, + 0xbb, 0xe6, 0xde, 0x3f, 0xfd, 0x76, 0x43, 0x93, 0xc4, 0x22, 0xd2, 0xb8, 0x0e, 0x8d, 0x23, 0x3e, + 0x39, 0x24, 0xdd, 0x61, 0x4e, 0xdd, 0x6b, 0x86, 0xf9, 0xae, 0x77, 0xdc, 0x80, 0xb5, 0x32, 0xcd, + 0x70, 0x96, 0x47, 0xfc, 0xc2, 0x60, 0xf5, 0x09, 0x99, 0xf2, 0x11, 0x3e, 0x3c, 0x5b, 0x12, 0xb6, + 0x45, 0xf7, 0xd1, 0x99, 0x33, 0xe4, 0xde, 0x3e, 0xfe, 0xf9, 0xeb, 0x73, 0xcd, 0xc1, 0x2b, 0x76, + 0x70, 0xa6, 0x73, 0x13, 0x7c, 0x98, 0x1a, 0xf1, 0x71, 0x64, 0x4d, 0xc3, 0xef, 0x0c, 0xf0, 0x79, + 0xac, 0xcb, 0xc4, 0x31, 0xe9, 0x73, 0x30, 0x6f, 0x9d, 0xdb, 0x56, 0xd7, 0xcb, 0xd8, 0x5d, 0x5c, + 0x2f, 0xb3, 0xc7, 0xa4, 0x2b, 0xe9, 0xd7, 0xca, 0xf4, 0x85, 0x6d, 0x0b, 0xc1, 0xcf, 0x9b, 0xbb, + 0x9b, 0x19, 0xfe, 0x4d, 0xdc, 0xb0, 0xf8, 0x7a, 0x76, 0x52, 0xf0, 0xe7, 0x21, 0xc2, 0x1f, 0x0c, + 0xae, 0xce, 0x85, 0x65, 0x68, 0x12, 0xe2, 0x07, 0x8b, 0x89, 0xcc, 0xdd, 0x8c, 0x7b, 0x13, 0xaf, + 0x9d, 0x8c, 0x8c, 0xce, 0x90, 0x4e, 0xbd, 0xfd, 0x3d, 0xd6, 0x6f, 0xbd, 0x6a, 0x5a, 0xb5, 0x51, + 0x23, 0xfb, 0x85, 0x3e, 0xf8, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x16, 0xc0, 0x87, 0xdb, 0xcb, 0x05, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -360,6 +362,7 @@ type ResponseBodyServiceClient interface { GetResponseBody(ctx context.Context, in *ResponseBodyIn, opts ...grpc.CallOption) (*ResponseBodyOut, error) ListResponseBodies(ctx context.Context, in *ResponseBodyIn, opts ...grpc.CallOption) (*RepeatedResponseBodyOut, error) ListResponseStrings(ctx context.Context, in *ResponseBodyIn, opts ...grpc.CallOption) (*RepeatedResponseStrings, error) + GetResponseBodyStream(ctx context.Context, in *ResponseBodyIn, opts ...grpc.CallOption) (ResponseBodyService_GetResponseBodyStreamClient, error) } type responseBodyServiceClient struct { @@ -397,11 +400,44 @@ func (c *responseBodyServiceClient) ListResponseStrings(ctx context.Context, in return out, nil } +func (c *responseBodyServiceClient) GetResponseBodyStream(ctx context.Context, in *ResponseBodyIn, opts ...grpc.CallOption) (ResponseBodyService_GetResponseBodyStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &_ResponseBodyService_serviceDesc.Streams[0], "/grpc.gateway.examples.internal.examplepb.ResponseBodyService/GetResponseBodyStream", opts...) + if err != nil { + return nil, err + } + x := &responseBodyServiceGetResponseBodyStreamClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ResponseBodyService_GetResponseBodyStreamClient interface { + Recv() (*ResponseBodyOut, error) + grpc.ClientStream +} + +type responseBodyServiceGetResponseBodyStreamClient struct { + grpc.ClientStream +} + +func (x *responseBodyServiceGetResponseBodyStreamClient) Recv() (*ResponseBodyOut, error) { + m := new(ResponseBodyOut) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // ResponseBodyServiceServer is the server API for ResponseBodyService service. type ResponseBodyServiceServer interface { GetResponseBody(context.Context, *ResponseBodyIn) (*ResponseBodyOut, error) ListResponseBodies(context.Context, *ResponseBodyIn) (*RepeatedResponseBodyOut, error) ListResponseStrings(context.Context, *ResponseBodyIn) (*RepeatedResponseStrings, error) + GetResponseBodyStream(*ResponseBodyIn, ResponseBodyService_GetResponseBodyStreamServer) error } // UnimplementedResponseBodyServiceServer can be embedded to have forward compatible implementations. @@ -417,6 +453,9 @@ func (*UnimplementedResponseBodyServiceServer) ListResponseBodies(ctx context.Co func (*UnimplementedResponseBodyServiceServer) ListResponseStrings(ctx context.Context, req *ResponseBodyIn) (*RepeatedResponseStrings, error) { return nil, status.Errorf(codes.Unimplemented, "method ListResponseStrings not implemented") } +func (*UnimplementedResponseBodyServiceServer) GetResponseBodyStream(req *ResponseBodyIn, srv ResponseBodyService_GetResponseBodyStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetResponseBodyStream not implemented") +} func RegisterResponseBodyServiceServer(s *grpc.Server, srv ResponseBodyServiceServer) { s.RegisterService(&_ResponseBodyService_serviceDesc, srv) @@ -476,6 +515,27 @@ func _ResponseBodyService_ListResponseStrings_Handler(srv interface{}, ctx conte return interceptor(ctx, in, info, handler) } +func _ResponseBodyService_GetResponseBodyStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ResponseBodyIn) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ResponseBodyServiceServer).GetResponseBodyStream(m, &responseBodyServiceGetResponseBodyStreamServer{stream}) +} + +type ResponseBodyService_GetResponseBodyStreamServer interface { + Send(*ResponseBodyOut) error + grpc.ServerStream +} + +type responseBodyServiceGetResponseBodyStreamServer struct { + grpc.ServerStream +} + +func (x *responseBodyServiceGetResponseBodyStreamServer) Send(m *ResponseBodyOut) error { + return x.ServerStream.SendMsg(m) +} + var _ResponseBodyService_serviceDesc = grpc.ServiceDesc{ ServiceName: "grpc.gateway.examples.internal.examplepb.ResponseBodyService", HandlerType: (*ResponseBodyServiceServer)(nil), @@ -493,6 +553,12 @@ var _ResponseBodyService_serviceDesc = grpc.ServiceDesc{ Handler: _ResponseBodyService_ListResponseStrings_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "GetResponseBodyStream", + Handler: _ResponseBodyService_GetResponseBodyStream_Handler, + ServerStreams: true, + }, + }, Metadata: "examples/internal/proto/examplepb/response_body_service.proto", } diff --git a/examples/internal/proto/examplepb/response_body_service.pb.gw.go b/examples/internal/proto/examplepb/response_body_service.pb.gw.go index 558cd0617cf..e07cf396798 100644 --- a/examples/internal/proto/examplepb/response_body_service.pb.gw.go +++ b/examples/internal/proto/examplepb/response_body_service.pb.gw.go @@ -193,6 +193,41 @@ func local_request_ResponseBodyService_ListResponseStrings_0(ctx context.Context } +func request_ResponseBodyService_GetResponseBodyStream_0(ctx context.Context, marshaler runtime.Marshaler, client ResponseBodyServiceClient, req *http.Request, pathParams map[string]string) (ResponseBodyService_GetResponseBodyStreamClient, runtime.ServerMetadata, error) { + var protoReq ResponseBodyIn + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["data"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "data") + } + + protoReq.Data, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "data", err) + } + + stream, err := client.GetResponseBodyStream(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + // RegisterResponseBodyServiceHandlerServer registers the http handlers for service ResponseBodyService to "mux". // UnaryRPC :call ResponseBodyServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -258,6 +293,13 @@ func RegisterResponseBodyServiceHandlerServer(ctx context.Context, mux *runtime. }) + mux.Handle("GET", pattern_ResponseBodyService_GetResponseBodyStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + return nil } @@ -359,6 +401,29 @@ func RegisterResponseBodyServiceHandlerClient(ctx context.Context, mux *runtime. }) + mux.Handle("GET", pattern_ResponseBodyService_GetResponseBodyStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ResponseBodyService_GetResponseBodyStream_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_ResponseBodyService_GetResponseBodyStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { + res, err := resp.Recv() + return response_ResponseBodyService_GetResponseBodyStream_0{res}, err + }, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -389,12 +454,23 @@ func (m response_ResponseBodyService_ListResponseStrings_0) XXX_ResponseBody() i return response.Values } +type response_ResponseBodyService_GetResponseBodyStream_0 struct { + proto.Message +} + +func (m response_ResponseBodyService_GetResponseBodyStream_0) XXX_ResponseBody() interface{} { + response := m.Message.(*ResponseBodyOut) + return response.Response +} + var ( pattern_ResponseBodyService_GetResponseBody_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"responsebody", "data"}, "", runtime.AssumeColonVerbOpt(true))) pattern_ResponseBodyService_ListResponseBodies_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"responsebodies", "data"}, "", runtime.AssumeColonVerbOpt(true))) pattern_ResponseBodyService_ListResponseStrings_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"responsestrings", "data"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_ResponseBodyService_GetResponseBodyStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"responsebody", "stream", "data"}, "", runtime.AssumeColonVerbOpt(true))) ) var ( @@ -403,4 +479,6 @@ var ( forward_ResponseBodyService_ListResponseBodies_0 = runtime.ForwardResponseMessage forward_ResponseBodyService_ListResponseStrings_0 = runtime.ForwardResponseMessage + + forward_ResponseBodyService_GetResponseBodyStream_0 = runtime.ForwardResponseStream ) diff --git a/examples/internal/proto/examplepb/response_body_service.proto b/examples/internal/proto/examplepb/response_body_service.proto index 2a04e786e95..a1081d55713 100644 --- a/examples/internal/proto/examplepb/response_body_service.proto +++ b/examples/internal/proto/examplepb/response_body_service.proto @@ -55,4 +55,11 @@ service ResponseBodyService { response_body : "values" }; } + + rpc GetResponseBodyStream(ResponseBodyIn) returns (stream ResponseBodyOut) { + option (google.api.http) = { + get : "/responsebody/stream/{data}" + response_body : "response" + }; + } } diff --git a/examples/internal/proto/examplepb/response_body_service.swagger.json b/examples/internal/proto/examplepb/response_body_service.swagger.json index 11143187def..1fad10c2042 100644 --- a/examples/internal/proto/examplepb/response_body_service.swagger.json +++ b/examples/internal/proto/examplepb/response_body_service.swagger.json @@ -44,6 +44,45 @@ ] } }, + "/responsebody/stream/{data}": { + "get": { + "operationId": "ResponseBodyService_GetResponseBodyStream", + "responses": { + "200": { + "description": "(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/examplepbResponseBodyOutResponse" + }, + "error": { + "$ref": "#/definitions/runtimeStreamError" + } + }, + "title": "Stream result of examplepbResponseBodyOut" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "data", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "ResponseBodyService" + ] + } + }, "/responsebody/{data}": { "get": { "operationId": "ResponseBodyService_GetResponseBody", @@ -203,6 +242,31 @@ } } } + }, + "runtimeStreamError": { + "type": "object", + "properties": { + "grpc_code": { + "type": "integer", + "format": "int32" + }, + "http_code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "http_status": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } } } } diff --git a/examples/internal/server/responsebody.go b/examples/internal/server/responsebody.go index 8df32ede5fa..803d5d102c0 100644 --- a/examples/internal/server/responsebody.go +++ b/examples/internal/server/responsebody.go @@ -42,3 +42,11 @@ func (s *responseBodyServer) ListResponseStrings(ctx context.Context, req *examp Values: []string{"hello", req.Data}, }, nil } + +func (s *responseBodyServer) GetResponseBodyStream(req *examples.ResponseBodyIn, stream examples.ResponseBodyService_GetResponseBodyStreamServer) error { + return stream.Send(&examples.ResponseBodyOut{ + Response: &examples.ResponseBodyOut_Response{ + Data: req.Data, + }, + }) +} diff --git a/protoc-gen-grpc-gateway/internal/gengateway/template.go b/protoc-gen-grpc-gateway/internal/gengateway/template.go index 1d3d3ca8f19..6dd5fcf5968 100644 --- a/protoc-gen-grpc-gateway/internal/gengateway/template.go +++ b/protoc-gen-grpc-gateway/internal/gengateway/template.go @@ -667,7 +667,14 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Client(ctx context.Context, return } {{if $m.GetServerStreaming}} + {{ if $b.ResponseBody }} + forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { + res, err := resp.Recv() + return response_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}{res}, err + }, mux.GetForwardResponseOptions()...) + {{ else }} forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + {{end}} {{else}} {{ if $b.ResponseBody }} forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(ctx, mux, outboundMarshaler, w, req, response_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}{resp}, mux.GetForwardResponseOptions()...) @@ -712,4 +719,4 @@ var ( {{end}} ) {{end}}`)) -) +) \ No newline at end of file diff --git a/runtime/handler.go b/runtime/handler.go index b894da86bf8..65a305e16c1 100644 --- a/runtime/handler.go +++ b/runtime/handler.go @@ -61,7 +61,18 @@ func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshal return } - buf, err := marshaler.Marshal(streamChunk(ctx, resp, mux.streamErrorHandler)) + var buf []byte + switch { + case resp == nil: + buf, err = marshaler.Marshal(errorChunk(streamError(ctx, mux.streamErrorHandler, errEmptyResponse))) + default: + if rb, ok := resp.(responseBody); ok { + buf, err = marshaler.Marshal(rb.XXX_ResponseBody()) + } else { + buf, err = marshaler.Marshal(map[string]proto.Message{"result": resp}) + } + } + if err != nil { grpclog.Infof("Failed to marshal response chunk: %v", err) handleForwardResponseStreamError(ctx, wroteHeader, marshaler, w, req, mux, err) @@ -184,15 +195,6 @@ func handleForwardResponseStreamError(ctx context.Context, wroteHeader bool, mar } } -// streamChunk returns a chunk in a response stream for the given result. The -// given errHandler is used to render an error chunk if result is nil. -func streamChunk(ctx context.Context, result proto.Message, errHandler StreamErrorHandlerFunc) map[string]proto.Message { - if result == nil { - return errorChunk(streamError(ctx, errHandler, errEmptyResponse)) - } - return map[string]proto.Message{"result": result} -} - // streamError returns the payload for the final message in a response stream // that represents the given err. func streamError(ctx context.Context, errHandler StreamErrorHandlerFunc, err error) *StreamError { diff --git a/runtime/handler_test.go b/runtime/handler_test.go index 912cb3acfa5..caec69bec38 100644 --- a/runtime/handler_test.go +++ b/runtime/handler_test.go @@ -17,15 +17,26 @@ import ( "google.golang.org/grpc/status" ) +type fakeReponseBodyWrapper struct { + proto.Message +} + +// XXX_ResponseBody returns id of SimpleMessage +func (r fakeReponseBodyWrapper) XXX_ResponseBody() interface{} { + resp := r.Message.(*pb.SimpleMessage) + return resp.Id +} + func TestForwardResponseStream(t *testing.T) { type msg struct { pb proto.Message err error } tests := []struct { - name string - msgs []msg - statusCode int + name string + msgs []msg + statusCode int + responseBody bool }{{ name: "encoding", msgs: []msg{ @@ -47,6 +58,22 @@ func TestForwardResponseStream(t *testing.T) { {nil, grpc.Errorf(codes.OutOfRange, "400")}, }, statusCode: http.StatusOK, + }, { + name: "response body stream case", + msgs: []msg{ + {fakeReponseBodyWrapper{&pb.SimpleMessage{Id: "One"}}, nil}, + {fakeReponseBodyWrapper{&pb.SimpleMessage{Id: "Two"}}, nil}, + }, + responseBody: true, + statusCode: http.StatusOK, + }, { + name: "response body stream error case", + msgs: []msg{ + {fakeReponseBodyWrapper{&pb.SimpleMessage{Id: "One"}}, nil}, + {nil, grpc.Errorf(codes.OutOfRange, "400")}, + }, + responseBody: true, + statusCode: http.StatusOK, }} newTestRecv := func(t *testing.T, msgs []msg) func() (proto.Message, error) { @@ -113,7 +140,21 @@ func TestForwardResponseStream(t *testing.T) { return } - b, err := marshaler.Marshal(map[string]proto.Message{"result": msg.pb}) + + var b []byte + + if tt.responseBody { + // responseBody interface is in runtime package and test is in runtime_test package. hence can't use responseBody direclty + // So type casting to fakeReponseBodyWrapper struct to verify the data. + rb, ok := msg.pb.(fakeReponseBodyWrapper) + if !ok { + t.Errorf("stream responseBody failed %v", err) + } + b, err = marshaler.Marshal(rb.XXX_ResponseBody()) + } else { + b, err = marshaler.Marshal(map[string]proto.Message{"result": msg.pb}) + } + if err != nil { t.Errorf("marshaler.Marshal() failed %v", err) } @@ -279,4 +320,4 @@ func TestForwardResponseMessage(t *testing.T) { } }) } -} +} \ No newline at end of file From f8053febc10650370d63b038d387d016b827e929 Mon Sep 17 00:00:00 2001 From: Anil Dasari Date: Tue, 14 Apr 2020 21:59:09 -0700 Subject: [PATCH 2/3] review comment: incorrect port value in fataf message --- examples/internal/integration/integration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/internal/integration/integration_test.go b/examples/internal/integration/integration_test.go index a044315295a..2a248204ef3 100644 --- a/examples/internal/integration/integration_test.go +++ b/examples/internal/integration/integration_test.go @@ -1557,7 +1557,7 @@ func testResponseStrings(t *testing.T, port int) { }() if err := waitForGateway(ctx, uint16(port)); err != nil { - t.Fatalf("waitForGateway(ctx, 8081) failed with %v; want success", err) + t.Fatalf("waitForGateway(ctx, %d) failed with %v; want success", port, err) } for i, spec := range []struct { @@ -1800,4 +1800,4 @@ func testNonStandardNames(t *testing.T, port int, method string, jsonBody string if got, want := string(body), jsonBody; got != want { t.Errorf("got %q; want %q", got, want) } -} \ No newline at end of file +} From 94a85096f03482e751c721dcb9a13b0b7835a865 Mon Sep 17 00:00:00 2001 From: Anil Dasari Date: Thu, 16 Apr 2020 15:54:15 -0700 Subject: [PATCH 3/3] review comments --- .../internal/integration/integration_test.go | 81 +++++++++++++++---- examples/internal/server/responsebody.go | 11 ++- runtime/handler.go | 7 +- runtime/handler_test.go | 9 ++- 4 files changed, 85 insertions(+), 23 deletions(-) diff --git a/examples/internal/integration/integration_test.go b/examples/internal/integration/integration_test.go index 2a248204ef3..def7703df8e 100644 --- a/examples/internal/integration/integration_test.go +++ b/examples/internal/integration/integration_test.go @@ -1,6 +1,7 @@ package integration_test import ( + "bufio" "bytes" "context" "encoding/base64" @@ -1477,25 +1478,20 @@ func TestResponseBody(t *testing.T) { func testResponseBody(t *testing.T, port int) { tests := []struct { - name string - url string - wantStatus int - wantResponse string + name string + url string + wantStatus int + wantBody string }{{ - name: "unary case", - url: "http://localhost:%d/responsebody/foo", - wantStatus: http.StatusOK, - wantResponse: `{"data":"foo"}`, - }, { - name: "stream case", - url: "http://localhost:%d/responsebody/stream/foo", - wantStatus: http.StatusOK, - wantResponse: `{"data":"foo"}`, + name: "unary case", + url: "http://localhost:%d/responsebody/foo", + wantStatus: http.StatusOK, + wantBody: `{"data":"foo"}`, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - apiURL := fmt.Sprintf("http://localhost:%d/responsebody/foo", port) + apiURL := fmt.Sprintf(tt.url, port) resp, err := http.Get(apiURL) if err != nil { t.Fatalf("http.Get(%q) failed with %v; want success", apiURL, err) @@ -1512,13 +1508,68 @@ func testResponseBody(t *testing.T, port int) { t.Logf("%s", buf) } - if got, want := string(buf), tt.wantResponse; got != want { + if got, want := string(buf), tt.wantBody; got != want { t.Errorf("response = %q; want %q", got, want) } }) } } +func TestResponseBodyStream(t *testing.T) { + tests := []struct { + name string + url string + wantStatus int + wantBody []string + }{{ + name: "stream case", + url: "http://localhost:%d/responsebody/stream/foo", + wantStatus: http.StatusOK, + wantBody: []string{`{"result":{"data":"first foo"}}`, `{"result":{"data":"second foo"}}`}, + }} + + port := 8088 + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + apiURL := fmt.Sprintf(tt.url, port) + resp, err := http.Get(apiURL) + if err != nil { + t.Fatalf("http.Get(%q) failed with %v; want success", apiURL, err) + } + + defer resp.Body.Close() + body, err := readAll(resp.Body) + if err != nil { + t.Fatalf("readAll(resp.Body) failed with %v; want success", err) + } + + if got, want := resp.StatusCode, tt.wantStatus; got != want { + t.Errorf("resp.StatusCode = %d; want %d", got, want) + } + + if !reflect.DeepEqual(tt.wantBody, body) { + t.Errorf("response = %v; want %v", body, tt.wantBody) + } + }) + } +} + +func readAll(body io.ReadCloser) ([]string, error) { + var b []string + reader := bufio.NewReader(body) + for { + l, err := reader.ReadBytes('\n') + switch { + case err == io.EOF: + return b, nil + case err != nil: + return nil, err + } + + b = append(b, string(bytes.TrimSpace(l))) + } +} + func testResponseBodies(t *testing.T, port int) { apiURL := fmt.Sprintf("http://localhost:%d/responsebodies/foo", port) resp, err := http.Get(apiURL) diff --git a/examples/internal/server/responsebody.go b/examples/internal/server/responsebody.go index 803d5d102c0..60fe1ac344a 100644 --- a/examples/internal/server/responsebody.go +++ b/examples/internal/server/responsebody.go @@ -2,6 +2,7 @@ package server import ( "context" + "fmt" examples "github.com/grpc-ecosystem/grpc-gateway/examples/internal/proto/examplepb" ) @@ -44,9 +45,17 @@ func (s *responseBodyServer) ListResponseStrings(ctx context.Context, req *examp } func (s *responseBodyServer) GetResponseBodyStream(req *examples.ResponseBodyIn, stream examples.ResponseBodyService_GetResponseBodyStreamServer) error { + if err := stream.Send(&examples.ResponseBodyOut{ + Response: &examples.ResponseBodyOut_Response{ + Data: fmt.Sprintf("first %s", req.Data), + }, + }); err != nil { + return err + } + return stream.Send(&examples.ResponseBodyOut{ Response: &examples.ResponseBodyOut_Response{ - Data: req.Data, + Data: fmt.Sprintf("second %s", req.Data), }, }) } diff --git a/runtime/handler.go b/runtime/handler.go index 65a305e16c1..2c62382ef08 100644 --- a/runtime/handler.go +++ b/runtime/handler.go @@ -66,11 +66,12 @@ func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshal case resp == nil: buf, err = marshaler.Marshal(errorChunk(streamError(ctx, mux.streamErrorHandler, errEmptyResponse))) default: + result := map[string]interface{}{"result": resp} if rb, ok := resp.(responseBody); ok { - buf, err = marshaler.Marshal(rb.XXX_ResponseBody()) - } else { - buf, err = marshaler.Marshal(map[string]proto.Message{"result": resp}) + result["result"] = rb.XXX_ResponseBody() } + + buf, err = marshaler.Marshal(result) } if err != nil { diff --git a/runtime/handler_test.go b/runtime/handler_test.go index caec69bec38..ea369add028 100644 --- a/runtime/handler_test.go +++ b/runtime/handler_test.go @@ -144,15 +144,16 @@ func TestForwardResponseStream(t *testing.T) { var b []byte if tt.responseBody { - // responseBody interface is in runtime package and test is in runtime_test package. hence can't use responseBody direclty + // responseBody interface is in runtime package and test is in runtime_test package. hence can't use responseBody directly // So type casting to fakeReponseBodyWrapper struct to verify the data. rb, ok := msg.pb.(fakeReponseBodyWrapper) if !ok { t.Errorf("stream responseBody failed %v", err) } - b, err = marshaler.Marshal(rb.XXX_ResponseBody()) + + b, err = marshaler.Marshal(map[string]interface{}{"result": rb.XXX_ResponseBody()}) } else { - b, err = marshaler.Marshal(map[string]proto.Message{"result": msg.pb}) + b, err = marshaler.Marshal(map[string]interface{}{"result": msg.pb}) } if err != nil { @@ -320,4 +321,4 @@ func TestForwardResponseMessage(t *testing.T) { } }) } -} \ No newline at end of file +}