diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/FunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/FunctionInvoker.java index 52b65843a..69ff6cd6f 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/FunctionInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/FunctionInvoker.java @@ -85,23 +85,18 @@ public class FunctionInvoker extends AbstractSpringFunctionAdapterInitializer, Message> function = lookupFunction(); + Function, Message> function = lookupFunction(); - Message message = getInputType() == Void.class ? null - : MessageBuilder.withPayload(httpRequest.getReader()).copyHeaders(httpRequest.getHeaders()).build(); - Message result = function.apply(message); + Message message = getInputType() == Void.class ? null + : MessageBuilder.withPayload(httpRequest.getReader()).copyHeaders(httpRequest.getHeaders()).build(); + Message result = function.apply(message); - if (result != null) { - httpResponse.getWriter().write(new String(result.getPayload(), StandardCharsets.UTF_8)); - for (Entry header : result.getHeaders().entrySet()) { - httpResponse.appendHeader(header.getKey(), header.getValue().toString()); - } + if (result != null) { + httpResponse.getWriter().write(new String(result.getPayload(), StandardCharsets.UTF_8)); + for (Entry header : result.getHeaders().entrySet()) { + httpResponse.appendHeader(header.getKey(), header.getValue().toString()); } } - finally { - httpResponse.getWriter().close(); - } } /** diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/FunctionInvokerHttpTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/FunctionInvokerHttpTests.java index bbb4f9d28..11045366b 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/FunctionInvokerHttpTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/FunctionInvokerHttpTests.java @@ -83,9 +83,14 @@ public class FunctionInvokerHttpTests { HttpResponse response = Mockito.mock(HttpResponse.class); StringWriter writer = new StringWriter(); - when(response.getWriter()).thenReturn(new BufferedWriter(writer)); + BufferedWriter bufferedWriter = new BufferedWriter(writer); + when(response.getWriter()).thenReturn(bufferedWriter); handler.service(request, response); + + // Closing the writer is done by the Framework/caller. + bufferedWriter.close(); + if (expectedOutput != null) { assertThat(writer.toString()).isEqualTo(gson.toJson(expectedOutput)); } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/integration/FunctionInvokerIntegrationTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/integration/FunctionInvokerIntegrationTests.java index cf8e7ffce..e51d11440 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/integration/FunctionInvokerIntegrationTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/integration/FunctionInvokerIntegrationTests.java @@ -18,14 +18,20 @@ package org.springframework.cloud.function.adapter.gcp.integration; import java.io.IOException; import java.util.function.Function; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.cloud.function.adapter.gcp.integration.LocalServerTestSupport.verify; /** @@ -52,6 +58,40 @@ public class FunctionInvokerIntegrationTests { verify(CloudFunctionMain.class, "foobar", new Foo("Hi"), new Bar("Hi")); } + @Test + public void testErrorResponse() { + try (LocalServerTestSupport.ServerProcess serverProcess = + LocalServerTestSupport.startServer(ErrorFunction.class, "errorFunction")) { + + TestRestTemplate testRestTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + ResponseEntity response = testRestTemplate.postForEntity( + "http://localhost:" + serverProcess.getPort(), new HttpEntity<>("test", headers), + String.class); + + assertThat(response.getStatusCode().is5xxServerError()).isTrue(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * An example function which throws an error to test response code propagation. + */ + @Configuration + @Import({ ContextFunctionCatalogAutoConfiguration.class }) + static class ErrorFunction { + + @Bean + Supplier errorFunction() { + return () -> { + throw new RuntimeException(); + }; + } + } + @Configuration @Import({ ContextFunctionCatalogAutoConfiguration.class }) static class CloudFunctionMainSingular { diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/integration/LocalServerTestSupport.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/integration/LocalServerTestSupport.java index 63af7f8d9..246a886df 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/integration/LocalServerTestSupport.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcp/integration/LocalServerTestSupport.java @@ -84,7 +84,7 @@ final public class LocalServerTestSupport { } } - private static ServerProcess startServer(Class springApplicationMainClass, String function) + static ServerProcess startServer(Class springApplicationMainClass, String function) throws InterruptedException, IOException { int port = nextPort.getAndIncrement(); @@ -147,7 +147,7 @@ final public class LocalServerTestSupport { } } - private static class ServerProcess implements AutoCloseable { + static class ServerProcess implements AutoCloseable { private final Process process;