Skip to content

Commit

Permalink
Correctly handle coroutine with ResponseEntity
Browse files Browse the repository at this point in the history
ResponseEntityResultHandler nests correctly, only once for the ResponseEntity,
when there is a Mono adapted from a Kotlin Continuation.

Closes gh-27292
  • Loading branch information
rstoyanchev committed Oct 13, 2021
1 parent 1e3996e commit 0436dd0
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
*/
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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -23,6 +23,7 @@

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;
Expand Down Expand Up @@ -108,6 +109,7 @@ private boolean isSupportedType(@Nullable Class<?> clazz) {


@Override
@SuppressWarnings("ConstantConditions")
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {

Mono<?> returnValueMono;
Expand All @@ -118,7 +120,9 @@ public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result)
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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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() {

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -151,4 +168,8 @@ class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() {
}

}


class FooContainer<T>(val value: T)

}

0 comments on commit 0436dd0

Please sign in to comment.