diff --git a/docs/src/main/asciidoc/adapters/gcp-intro.adoc b/docs/src/main/asciidoc/adapters/gcp-intro.adoc index 101af96d5..d95221a2a 100644 --- a/docs/src/main/asciidoc/adapters/gcp-intro.adoc +++ b/docs/src/main/asciidoc/adapters/gcp-intro.adoc @@ -36,7 +36,7 @@ Start by adding the Maven plugin provided as part of the Google Functions Framew function-maven-plugin 0.9.1 - org.springframework.cloud.function.adapter.gcloud.GcfSpringBootHttpRequestHandler + org.springframework.cloud.function.adapter.gcloud.FunctionInvoker 8080 @@ -118,7 +118,7 @@ From the project base directory run the following command to deploy. ---- gcloud alpha functions deploy function-sample-gcp \ ---entry-point org.springframework.cloud.function.adapter.gcloud.GcfSpringBootHttpRequestHandler \ +--entry-point org.springframework.cloud.function.adapter.gcloud.FunctionInvoker \ --runtime java11 \ --trigger-http \ --source deploy \ diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml index 3992f19fd..2c2bd5734 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml @@ -18,7 +18,6 @@ UTF-8 UTF-8 1.0.0-alpha-2-rc3 - 2.8.5 @@ -31,17 +30,11 @@ com.google.code.gson gson - ${gson.version} org.springframework.cloud spring-cloud-function-context - - org.springframework.cloud - spring-cloud-starter-function-web - - org.springframework.boot diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/FunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/FunctionInvoker.java new file mode 100644 index 000000000..9d2d087bf --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/FunctionInvoker.java @@ -0,0 +1,89 @@ +/* + * Copyright 2020-2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.function.adapter.gcloud; + +import java.io.BufferedReader; +import java.nio.charset.StandardCharsets; +import java.util.Map.Entry; +import java.util.function.Function; + +import com.google.cloud.functions.HttpFunction; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; + +import org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.util.Assert; +import org.springframework.util.MimeTypeUtils; + +/** + * Implementation of {@link HttpFunction} for Google Cloud Function (GCF). + * This is the Spring Cloud Function adapter for GCF HTTP function. + * + * @author Dmitry Solomakha + * @author Mike Eltsufin + * @author Oleg Zhurakousky + * + * @since 3.0.4 + */ +public class FunctionInvoker + extends AbstractSpringFunctionAdapterInitializer implements HttpFunction { + + public FunctionInvoker() { + super(); + } + + public FunctionInvoker(Class configurationClass) { + super(configurationClass); + System.setProperty("spring.http.converters.preferred-json-mapper", "gson"); + Thread.currentThread() //TODO investigate if it is necessary + .setContextClassLoader(FunctionInvoker.class.getClassLoader()); + initialize(null); + } + + /** + * The implementation of a GCF {@link HttpFunction} that will be used as the entry point from GCF. + */ + @Override + public void service(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception { + try { + String functionName = System.getenv().containsKey("spring.cloud.function.definition") + ? System.getenv("spring.cloud.function.definition") : ""; + + Function, Message> function = + this.catalog.lookup(functionName, MimeTypeUtils.APPLICATION_JSON.toString()); + Assert.notNull(function, "'function' with name '" + functionName + "' must not be null"); + + 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()); + } + } + } + finally { + httpResponse.getWriter().close(); + } + } +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandler.java deleted file mode 100644 index 37b05b29d..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandler.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2020-2020 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.function.adapter.gcloud; - -import com.google.cloud.functions.HttpFunction; -import com.google.cloud.functions.HttpRequest; -import com.google.cloud.functions.HttpResponse; -import com.google.gson.Gson; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer; - -/** - * Implementation of {@link HttpFunction} for Google Cloud Function (GCF). - * This is the Spring Cloud Function adapter for GCF HTTP function. - * - * @author Dmitry Solomakha - * @author Mike Eltsufin - */ -public class GcfSpringBootHttpRequestHandler - extends AbstractSpringFunctionAdapterInitializer implements HttpFunction { - - private final Gson gson = new Gson(); - - public GcfSpringBootHttpRequestHandler() { - super(); - } - - public GcfSpringBootHttpRequestHandler(Class configurationClass) { - super(configurationClass); - } - - /** - * The implementation of a GCF {@link HttpFunction} that will be used as the entrypoint from GCF. - */ - @Override - public void service(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception { - Thread.currentThread() - .setContextClassLoader(GcfSpringBootHttpRequestHandler.class.getClassLoader()); - - initialize(httpRequest); - - Publisher input; - if (getInputType() == Void.class) { - input = Mono.empty(); - } - else { - input = Mono.just(gson.fromJson(httpRequest.getReader(), getInputType())); - } - - Publisher output = this.apply(input); - - Object result = this.result(input, output); - - httpResponse.getWriter().write(gson.toJson(result)); - httpResponse.getWriter().close(); - } -} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/FunctionInvokerTests.java similarity index 78% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerTests.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/FunctionInvokerTests.java index 92292ac53..3ea8ed9fc 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/FunctionInvokerTests.java @@ -34,6 +34,8 @@ import org.springframework.cloud.function.context.config.ContextFunctionCatalogA import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -44,7 +46,7 @@ import static org.mockito.Mockito.when; * @author Dmitry Solomakha * @author Mike Eltsufin */ -public class GcfSpringBootHttpRequestHandlerTests { +public class FunctionInvokerTests { private static final Gson gson = new Gson(); @@ -71,21 +73,23 @@ public class GcfSpringBootHttpRequestHandlerTests { } private void testFunction(Class configurationClass, I input, O expectedOutput) throws Exception { - GcfSpringBootHttpRequestHandler handler = new GcfSpringBootHttpRequestHandler(configurationClass); + try (FunctionInvoker handler = new FunctionInvoker(configurationClass);) { - HttpRequest request = Mockito.mock(HttpRequest.class); + HttpRequest request = Mockito.mock(HttpRequest.class); - if (input != null) { - when(request.getReader()).thenReturn(new BufferedReader(new StringReader(gson.toJson(input)))); + if (input != null) { + when(request.getReader()).thenReturn(new BufferedReader(new StringReader(gson.toJson(input)))); + } + + HttpResponse response = Mockito.mock(HttpResponse.class); + StringWriter writer = new StringWriter(); + when(response.getWriter()).thenReturn(new BufferedWriter(writer)); + + handler.service(request, response); + if (expectedOutput != null) { + assertThat(writer.toString()).isEqualTo(gson.toJson(expectedOutput)); + } } - - HttpResponse response = Mockito.mock(HttpResponse.class); - StringWriter writer = new StringWriter(); - when(response.getWriter()).thenReturn(new BufferedWriter(writer)); - - handler.service(request, response); - - assertThat(writer.toString()).isEqualTo(gson.toJson(expectedOutput)); } @Configuration @@ -110,8 +114,11 @@ public class GcfSpringBootHttpRequestHandlerTests { @Import({ ContextFunctionCatalogAutoConfiguration.class }) protected static class JsonInputOutputFunction { @Bean - public Function function() { - return (in) -> new OutgoingResponse("Thank you for sending the message: " + in.message); + public Function> function() { + return (in) -> { + return MessageBuilder.withPayload(new OutgoingResponse("Thank you for sending the message: " + in.message)) + .setHeader("foo", "bar").build(); + }; } } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java index 494777067..eb385b905 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java @@ -33,7 +33,6 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.cloud.function.context.catalog.FunctionInspector; @@ -79,7 +78,7 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo private FunctionInspector inspector; @Autowired(required = false) - private FunctionCatalog catalog; + protected FunctionCatalog catalog; private ConfigurableApplicationContext context; @@ -266,7 +265,7 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo new FunctionRegistration(context.getBean(name), name); Type type = FunctionContextUtils. - findType(name, (ConfigurableListableBeanFactory) this.context.getBeanFactory()); + findType(name, this.context.getBeanFactory()); this.functionRegistration = functionRegistration.type(new FunctionType(type)).wrap();