Correctly handle coroutine with ResponseEntity
ResponseEntityResultHandler nests correctly, only once for the ResponseEntity, when there is a Mono adapted from a Kotlin Continuation. Closes gh-27292
This commit is contained in:
@@ -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<HttpMessageWriter<?>> messageWriters;
|
||||
|
||||
|
||||
@@ -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<Void> 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());
|
||||
|
||||
@@ -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<String>("/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<FooContainer<String>> {
|
||||
delay(1)
|
||||
return ResponseEntity.ok(FooContainer("foo"))
|
||||
}
|
||||
|
||||
@GetMapping("/flow")
|
||||
fun flowEndpoint()= flow {
|
||||
emit("foo")
|
||||
@@ -151,4 +168,8 @@ class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class FooContainer<T>(val value: T)
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user