diff --git a/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc b/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc index eb174ac75eaf..6b9960218fa2 100644 --- a/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc +++ b/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc @@ -400,9 +400,17 @@ Http::FilterHeadersStatus JsonTranscoderFilter::encodeHeaders(Http::HeaderMap& h response_headers_ = &headers; if (end_stream) { + + if (method_->server_streaming()) { + // When there is no body in a streaming response, a empty JSON array is + // returned by default. Set the content type correctly. + headers.insertContentType().value().setReference(Http::Headers::get().ContentTypeValues.Json); + } + // In gRPC wire protocol, headers frame with end_stream is a trailers-only response. // The return value from encodeTrailers is ignored since it is always continue. encodeTrailers(headers); + return Http::FilterHeadersStatus::Continue; } diff --git a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc index 18f6c1d1a7dd..b4110b5b9acf 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc @@ -430,6 +430,8 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, BindingAndBody) { TEST_P(GrpcJsonTranscoderIntegrationTest, ServerStreamingGet) { HttpIntegrationTest::initialize(); + + // 1: Normal streaming get testTranscoding( Http::TestHeaderMapImpl{ {":method", "GET"}, {":path", "/shelves/1/books"}, {":authority", "host"}}, @@ -439,6 +441,22 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, ServerStreamingGet) { Status(), Http::TestHeaderMapImpl{{":status", "200"}, {"content-type", "application/json"}}, R"([{"id":"1","author":"Neal Stephenson","title":"Readme"})" R"(,{"id":"2","author":"George R.R. Martin","title":"A Game of Thrones"}])"); + + // 2: Empty response (trailers only) from streaming backend. + // Response type is a valid JSON, so content type should be application/json. + // Regression test for github.com/envoyproxy/envoy#5011 + testTranscoding( + Http::TestHeaderMapImpl{ + {":method", "GET"}, {":path", "/shelves/2/books"}, {":authority", "host"}}, + "", {"shelf: 2"}, {}, Status(), + Http::TestHeaderMapImpl{{":status", "200"}, {"content-type", "application/json"}}, "[]"); + + // 3: Empty response (trailers only) from streaming backend, with a gRPC error. + testTranscoding( + Http::TestHeaderMapImpl{ + {":method", "GET"}, {":path", "/shelves/37/books"}, {":authority", "host"}}, + "", {"shelf: 37"}, {}, Status(Code::NOT_FOUND, "Shelf 37 not found"), + Http::TestHeaderMapImpl{{":status", "200"}, {"content-type", "application/json"}}, "[]"); } TEST_P(GrpcJsonTranscoderIntegrationTest, StreamingPost) { @@ -553,6 +571,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, DeepStruct) { // The valid deep struct is parsed successfully. // Since we didn't set the response, it return 503. + // Response body is empty (not a valid JSON), so content type should be application/grpc. testTranscoding( Http::TestHeaderMapImpl{ {":method", "POST"}, {":path", "/echoStruct"}, {":authority", "host"}},