From 1d784c199d7241d78036c63b432947b775c38410 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 31 Mar 2020 14:14:33 +0200 Subject: [PATCH] GH-467 Added initial support for GET method to FunctionEndpointInitializer At the moment support is rudimentary as it does not include any type conversion, but neither does POST so it is consistent. Given that BeanFactoryAwareFunctionRegistry provides all that functionality already perhaps the proper fix with regard to converter would be to use it or part of it in place of InMemoryFunctionCatalog currently used. Resolves #467 --- .../function/FunctionEndpointInitializer.java | 34 +++++++++++++++---- .../FunctionEndpointInitializerTests.java | 30 ++++++++++++++++ 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java index 3912b2c98..52780ac53 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java @@ -20,6 +20,7 @@ import java.lang.management.ManagementFactory; import java.time.Duration; import java.util.Set; import java.util.function.Function; +import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -43,6 +44,7 @@ import org.springframework.cloud.function.web.BasicStringConverter; import org.springframework.cloud.function.web.RequestProcessor; import org.springframework.cloud.function.web.RequestProcessor.FunctionWrapper; import org.springframework.cloud.function.web.StringConverter; +import org.springframework.cloud.function.web.constants.WebRequestConstants; import org.springframework.cloud.function.web.util.FunctionWebUtils; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; @@ -61,10 +63,12 @@ import org.springframework.web.reactive.function.server.HandlerStrategies; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.WebExceptionHandler; import org.springframework.web.server.adapter.HttpWebHandlerAdapter; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.POST; import static org.springframework.web.reactive.function.server.RouterFunctions.route; import static org.springframework.web.reactive.function.server.ServerResponse.status; @@ -209,9 +213,8 @@ class FunctionEndpointFactory { this.handler = handler; } - @SuppressWarnings("unchecked") - private Function, Flux> extract(ServerRequest request) { - Function, Flux> function; + private Object extract(ServerRequest request) { + Object function; if (handler != null) { logger.info("Configured function: " + handler); Set names = this.functionCatalog.getNames(Function.class); @@ -219,7 +222,7 @@ class FunctionEndpointFactory { function = this.functionCatalog.lookup(Function.class, handler); } else { - function = (Function, Flux>) FunctionWebUtils.findFunction(request.method(), functionCatalog, + function = FunctionWebUtils.findFunction(request.method(), functionCatalog, request.attributes(), request.path()); } return function; @@ -228,7 +231,7 @@ class FunctionEndpointFactory { @SuppressWarnings({ "unchecked" }) public RouterFunction functionEndpoints() { return route(POST("/**"), request -> { - Function, Flux> function = extract(request); + Function, Flux> function = (Function, Flux>) extract(request); Class outputType = (Class) this.inspector.getOutputType(function); FunctionWrapper wrapper = RequestProcessor.wrapper(function, null, null); Mono> stream = request.bodyToMono(String.class) @@ -237,7 +240,26 @@ class FunctionEndpointFactory { return status(entity.getStatusCode()).headers(headers -> headers.addAll(entity.getHeaders())) .body(Mono.just((T) entity.getBody()), outputType); }); + }) + .andRoute(GET("/**"), request -> { + Object functionComponent = extract(request); + if (functionComponent instanceof Supplier || functionComponent instanceof Function) { + Class outputType = (Class) this.inspector.getOutputType(functionComponent); + if (functionComponent instanceof Supplier) { + Supplier> supplier = (Supplier>) functionComponent; + FunctionWrapper wrapper = RequestProcessor.wrapper(null, null, supplier); + return ServerResponse.ok().body(wrapper.supplier().get(), outputType); + } + else if (functionComponent instanceof Function) { + Function, Flux> function = (Function, Flux>) functionComponent; + FunctionWrapper wrapper = RequestProcessor.wrapper(function, null, null); + wrapper.headers(request.headers().asHttpHeaders()); + String argument = (String) request.attribute(WebRequestConstants.ARGUMENT).get(); + wrapper.argument(Flux.just(argument)); + return ServerResponse.ok().body(wrapper.function().apply(wrapper.argument()), outputType); + } + } + throw new UnsupportedOperationException("Consumer is not supported for GET"); }); } - } diff --git a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializerTests.java b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializerTests.java index 48d204c3a..8f47ab6b4 100644 --- a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializerTests.java +++ b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializerTests.java @@ -18,6 +18,7 @@ package org.springframework.cloud.function.web.function; import java.net.URI; import java.util.function.Function; +import java.util.function.Supplier; import org.junit.After; import org.junit.Before; @@ -90,11 +91,37 @@ public class FunctionEndpointInitializerTests { assertThat(response.getBody()).isEqualTo("desserts"); } + @Test + public void testGetWithtFunction() throws Exception { + FunctionalSpringApplication.run(ApplicationConfiguration.class); + TestRestTemplate testRestTemplate = new TestRestTemplate(); + String port = System.getProperty("server.port"); + Thread.sleep(200); + ResponseEntity response = testRestTemplate + .getForEntity(new URI("http://localhost:" + port + "/reverse/stressed"), String.class); + assertThat(response.getBody()).isEqualTo("desserts"); + } + + @Test + public void testGetWithtSupplier() throws Exception { + FunctionalSpringApplication.run(ApplicationConfiguration.class); + TestRestTemplate testRestTemplate = new TestRestTemplate(); + String port = System.getProperty("server.port"); + Thread.sleep(200); + ResponseEntity response = testRestTemplate + .getForEntity(new URI("http://localhost:" + port + "/supplier"), String.class); + assertThat(response.getBody()).isEqualTo("Jim Lahey"); + } + @SpringBootConfiguration protected static class ApplicationConfiguration implements ApplicationContextInitializer { + public Supplier supplier() { + return () -> "Jim Lahey"; + } + public Function uppercase() { return s -> s.toUpperCase(); } @@ -118,6 +145,9 @@ public class FunctionEndpointInitializerTests { applicationContext.registerBean("lowercase", FunctionRegistration.class, () -> new FunctionRegistration<>(lowercase()) .type(FunctionType.from(String.class).to(String.class))); + applicationContext.registerBean("supplier", FunctionRegistration.class, + () -> new FunctionRegistration<>(supplier()) + .type(FunctionType.supplier(String.class))); } }