Skip to content

Commit a3e23cd

Browse files
committed
Fix unwrapping logic for ResponseEntity<Flux>
This commit makes sure that the response returned by coroutine handler methods that return ResponseEntity<Flux> is unwrapped correctly. Closes gh-27809
1 parent af977c0 commit a3e23cd

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Set;
2222

2323
import org.reactivestreams.Publisher;
24+
import reactor.core.publisher.Flux;
2425
import reactor.core.publisher.Mono;
2526

2627
import org.springframework.core.KotlinDetector;
@@ -130,7 +131,8 @@ protected Mono<Void> writeBody(@Nullable Object body, MethodParameter bodyParame
130131
if (adapter != null) {
131132
publisher = adapter.toPublisher(body);
132133
boolean isUnwrapped = KotlinDetector.isSuspendingFunction(bodyParameter.getMethod()) &&
133-
!COROUTINES_FLOW_CLASS_NAME.equals(bodyType.toClass().getName());
134+
!COROUTINES_FLOW_CLASS_NAME.equals(bodyType.toClass().getName()) &&
135+
!Flux.class.equals(bodyType.toClass());
134136
ResolvableType genericType = isUnwrapped ? bodyType : bodyType.getGeneric();
135137
elementType = getElementType(adapter, genericType);
136138
actualElementType = elementType;

spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/method/annotation/CoroutinesIntegrationTests.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import org.springframework.web.bind.annotation.RestController
3636
import org.springframework.web.client.HttpServerErrorException
3737
import org.springframework.web.reactive.config.EnableWebFlux
3838
import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpServer
39+
import reactor.core.publisher.Flux
40+
import java.time.Duration
3941

4042
class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() {
4143

@@ -110,6 +112,25 @@ class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() {
110112
}
111113
}
112114

115+
@ParameterizedHttpServerTest
116+
fun `Suspending handler method returning ResponseEntity of Flux `(httpServer: HttpServer) {
117+
startServer(httpServer)
118+
119+
val entity = performGet<String>("/entity-flux", HttpHeaders.EMPTY, String::class.java)
120+
assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
121+
assertThat(entity.body).isEqualTo("01234")
122+
}
123+
124+
@ParameterizedHttpServerTest
125+
fun `Suspending handler method returning ResponseEntity of Flow`(httpServer: HttpServer) {
126+
startServer(httpServer)
127+
128+
val entity = performGet<String>("/entity-flow", HttpHeaders.EMPTY, String::class.java)
129+
assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
130+
assertThat(entity.body).isEqualTo("foobar")
131+
}
132+
133+
113134
@Configuration
114135
@EnableWebFlux
115136
@ComponentScan(resourcePattern = "**/CoroutinesIntegrationTests*")
@@ -167,6 +188,25 @@ class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() {
167188
throw IllegalStateException()
168189
}
169190

191+
@GetMapping("/entity-flux")
192+
suspend fun entityFlux() : ResponseEntity<Flux<String>> {
193+
val strings = Flux.interval(Duration.ofMillis(100)).take(5)
194+
.map { l -> l.toString() }
195+
delay(1)
196+
return ResponseEntity.ok().body(strings)
197+
}
198+
199+
@GetMapping("/entity-flow")
200+
suspend fun entityFlow() : ResponseEntity<Flow<String>> {
201+
val strings = flow {
202+
emit("foo")
203+
delay(1)
204+
emit("bar")
205+
delay(1)
206+
}
207+
return ResponseEntity.ok().body(strings)
208+
}
209+
170210
}
171211

172212

0 commit comments

Comments
 (0)