diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java index cbdd2f851f..45e1e17974 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java @@ -52,7 +52,7 @@ import org.springframework.web.server.ServerWebExchange; */ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHandlerSupport { - private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow"; + protected static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow"; private final List> messageWriters; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java index 3ad66a9bfc..26b59a4bd4 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.Set; import reactor.core.publisher.Mono; +import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; @@ -108,6 +109,7 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand @Override + @SuppressWarnings("ConstantConditions") public Mono handleResult(ServerWebExchange exchange, HandlerResult result) { Mono returnValueMono; @@ -118,7 +120,9 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand if (adapter != null) { Assert.isTrue(!adapter.isMultiValue(), "Only a single ResponseEntity supported"); returnValueMono = Mono.from(adapter.toPublisher(result.getReturnValue())); - bodyParameter = actualParameter.nested().nested(); + boolean isContinuation = (KotlinDetector.isSuspendingFunction(actualParameter.getMethod()) && + !COROUTINES_FLOW_CLASS_NAME.equals(actualParameter.getParameterType().getName())); + bodyParameter = (isContinuation ? actualParameter.nested() : actualParameter.nested().nested()); } else { returnValueMono = Mono.justOrEmpty(result.getReturnValue()); diff --git a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/method/annotation/CoroutinesIntegrationTests.kt b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/method/annotation/CoroutinesIntegrationTests.kt index af2502c09f..7a43168c7b 100644 --- a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/method/annotation/CoroutinesIntegrationTests.kt +++ b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/method/annotation/CoroutinesIntegrationTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,18 +22,20 @@ import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -import org.assertj.core.api.Assertions.* +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.springframework.context.ApplicationContext import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus -import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpServer +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController import org.springframework.web.client.HttpServerErrorException import org.springframework.web.reactive.config.EnableWebFlux +import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpServer class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() { @@ -63,6 +65,15 @@ class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() { assertThat(entity.body).isEqualTo("foo") } + @ParameterizedHttpServerTest // gh-27292 + fun `Suspending ResponseEntity handler method`(httpServer: HttpServer) { + startServer(httpServer) + + val entity = performGet("/suspend-response-entity", HttpHeaders.EMPTY, String::class.java) + assertThat(entity.statusCode).isEqualTo(HttpStatus.OK) + assertThat(entity.body).isEqualTo("{\"value\":\"foo\"}") + } + @ParameterizedHttpServerTest fun `Handler method returning Flow`(httpServer: HttpServer) { startServer(httpServer) @@ -119,6 +130,12 @@ class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() { "foo" } + @GetMapping("/suspend-response-entity") + suspend fun suspendingResponseEntityEndpoint(): ResponseEntity> { + delay(1) + return ResponseEntity.ok(FooContainer("foo")) + } + @GetMapping("/flow") fun flowEndpoint()= flow { emit("foo") @@ -151,4 +168,8 @@ class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() { } } + + + class FooContainer(val value: T) + }