Skip to content

Commit 3fecf35

Browse files
authored
feat: accept empty content body in HttpJsonTranscodingService (#6325)
Motivation HttpJsonTranscodingService currently fails with 400 Bad Request when handling a request with Content-Type: application/json and no body, even if the corresponding google.api.http rule uses body: "*". This behavior is inconsistent with common implementations such as Envoy and grpc-gateway, which treat empty bodies as {} in such cases. ⸻ Modifications - **HttpJsonTranscodingService** - Added a fallback to use an empty JSON object when body empty ( content-length: 0 ) - Treat **empty request bodies** as `{}` when **all** of the following are true: - **Content-Type**: `application/json` - **google.api.http rule**: `body: "*"` ⸻ Result - Closes #6319. - Users can now send requests with Content-Type: application/json and an empty body without receiving a 400 Bad Request error. - Ensures better compatibility with gRPC transcoding conventions from other ecosystems
1 parent ae45261 commit 3fecf35

2 files changed

Lines changed: 32 additions & 12 deletions

File tree

grpc/src/main/java/com/linecorp/armeria/server/grpc/HttpJsonTranscodingService.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -742,19 +742,27 @@ private static HttpData convertToJson(ServiceRequestContext ctx,
742742

743743
@Nullable
744744
private static JsonNode getBodyContent(AggregatedHttpRequest request) {
745-
@Nullable
746-
final MediaType contentType = request.contentType();
747-
if (contentType == null || !contentType.isJson()) {
748-
if (request.content().isEmpty()) {
749-
return null;
745+
@Nullable final MediaType contentType = request.contentType();
746+
final HttpData bodyContent = request.content();
747+
final boolean hasBodyContent = !bodyContent.isEmpty();
748+
749+
if (contentType != null && contentType.isJson()) {
750+
if (!hasBodyContent) {
751+
return mapper.createObjectNode();
752+
}
753+
754+
try {
755+
return mapper.readTree(bodyContent.toStringUtf8());
756+
} catch (JsonProcessingException e) {
757+
throw new IllegalArgumentException("Failed to parse JSON request.", e);
750758
}
751-
throw new IllegalArgumentException("Missing or invalid content-type in JSON request.");
752759
}
753-
try {
754-
return mapper.readTree(request.contentUtf8());
755-
} catch (JsonProcessingException e) {
756-
throw new IllegalArgumentException("Failed to parse JSON request.", e);
760+
761+
if (hasBodyContent) {
762+
throw new IllegalArgumentException("Missing or invalid content-type in JSON request.");
757763
}
764+
765+
return null;
758766
}
759767

760768
@VisibleForTesting

grpc/src/test/java/com/linecorp/armeria/it/grpc/HttpJsonTranscodingTest.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -917,15 +917,27 @@ void shouldDenyNonJsonContentType() {
917917
}
918918

919919
@Test
920-
void shouldDenyEmptyJson() {
920+
void shouldAcceptEmptyJson() {
921921
final String emptyJson = "";
922922
final RequestHeaders headers = RequestHeaders.builder()
923923
.method(HttpMethod.POST)
924924
.path("/v1/echo/response_body/repeated")
925925
.contentType(MediaType.JSON)
926926
.build();
927927
final AggregatedHttpResponse response = webClient.execute(headers, emptyJson).aggregate().join();
928-
assertThat(response.status()).isEqualTo(HttpStatus.BAD_REQUEST);
928+
assertThat(response.status()).isEqualTo(HttpStatus.OK);
929+
}
930+
931+
@Test
932+
void shouldAcceptEmptyContentBody() {
933+
final HttpData emptyContentBody = HttpData.empty();
934+
final RequestHeaders headers = RequestHeaders.builder()
935+
.method(HttpMethod.POST)
936+
.path("/v1/echo/response_body/repeated")
937+
.contentType(MediaType.JSON)
938+
.build();
939+
final AggregatedHttpResponse response = webClient.execute(headers, emptyContentBody).aggregate().join();
940+
assertThat(response.status()).isEqualTo(HttpStatus.OK);
929941
}
930942

931943
@Test

0 commit comments

Comments
 (0)