From cfbb68809247ea4aa9fe9c63f2c150dd3beb1ff0 Mon Sep 17 00:00:00 2001 From: yawkat Date: Fri, 16 Feb 2024 10:46:43 +0100 Subject: [PATCH] Do not override with 404 when returning response inside CompletableFuture Fixes #10419 --- .../http/server/tck/tests/BodyTest.java | 37 +++++++++++++++++++ .../micronaut/http/server/RouteExecutor.java | 4 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/http-server-tck/src/main/java/io/micronaut/http/server/tck/tests/BodyTest.java b/http-server-tck/src/main/java/io/micronaut/http/server/tck/tests/BodyTest.java index f2194559bbd..b9189aef67a 100644 --- a/http-server-tck/src/main/java/io/micronaut/http/server/tck/tests/BodyTest.java +++ b/http-server-tck/src/main/java/io/micronaut/http/server/tck/tests/BodyTest.java @@ -20,11 +20,13 @@ import io.micronaut.core.async.annotation.SingleResult; import io.micronaut.http.HttpHeaders; import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Body; import io.micronaut.http.annotation.Consumes; import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Post; import io.micronaut.http.annotation.Status; import io.micronaut.http.tck.AssertionUtils; @@ -32,9 +34,11 @@ import io.micronaut.http.tck.HttpResponseAssertion; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; import java.io.IOException; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import static io.micronaut.http.tck.TestScenario.asserts; @@ -142,6 +146,28 @@ void testRequestBodyFormDataNoBodyAnnotation() throws IOException { .build())); } + @Test + void testEmptyFuture() throws IOException { + asserts(SPEC_NAME, + HttpRequest.GET("/response-body/empty-future") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED), + (server, request) -> AssertionUtils.assertDoesNotThrow(server, request, + HttpResponseAssertion.builder() + .status(HttpStatus.OK) + .build())); + } + + @Test + void testEmptySingleResultPublisher() throws IOException { + asserts(SPEC_NAME, + HttpRequest.GET("/response-body/empty-single-result") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED), + (server, request) -> AssertionUtils.assertDoesNotThrow(server, request, + HttpResponseAssertion.builder() + .status(HttpStatus.OK) + .build())); + } + @Controller("/response-body") @Requires(property = "spec.name", value = SPEC_NAME) static class BodyController { @@ -195,6 +221,17 @@ Publisher postMany(@Body Publisher data) { String postBytes(@Body byte[] bytes) { return new String(bytes); } + + @Get(uri = "/empty-future") + CompletableFuture> emptyFuture() { + return CompletableFuture.completedFuture(HttpResponse.ok()); + } + + @Get(uri = "/empty-single-result") + @SingleResult + Publisher> emptySingleResult() { + return Mono.just(HttpResponse.ok()); + } } @Introspected diff --git a/http-server/src/main/java/io/micronaut/http/server/RouteExecutor.java b/http-server/src/main/java/io/micronaut/http/server/RouteExecutor.java index 2a05d0744ce..150ad6292d9 100644 --- a/http-server/src/main/java/io/micronaut/http/server/RouteExecutor.java +++ b/http-server/src/main/java/io/micronaut/http/server/RouteExecutor.java @@ -692,6 +692,7 @@ private CompletionStage> fromCompletionStage(@NonNull Htt return CompletableFuture.completedStage(newNotFoundError(request)); } } + boolean explicitResponse = false; if (asyncBody instanceof HttpResponse httpResponse) { mutableResponse = httpResponse.toMutableResponse(); final Argument bodyArgument = routeInfo.getReturnType() // CompletionStage @@ -706,6 +707,7 @@ private CompletionStage> fromCompletionStage(@NonNull Htt return mutableResponse.body(innerBody); }); } + explicitResponse = true; } else if (asyncBody instanceof HttpStatus status) { mutableResponse = forStatus(routeInfo, status); } else { @@ -713,7 +715,7 @@ private CompletionStage> fromCompletionStage(@NonNull Htt .body(asyncBody); } if (mutableResponse.body() == null) { - if (routeInfo.isVoid()) { + if (routeInfo.isVoid() || explicitResponse) { return CompletableFuture.completedStage(voidResponse(routeInfo)); } return CompletableFuture.completedStage(newNotFoundError(request));