diff --git a/pom.xml b/pom.xml index 037f4c649..68f68f908 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 1.8 - 3.0.0.RELEASE + 3.0.2.RELEASE 1.1.0.BUILD-SNAPSHOT @@ -39,7 +39,7 @@ spring-cloud-function-compiler spring-cloud-function-core spring-cloud-function-stream - + spring-cloud-function-web diff --git a/spring-cloud-function-web/.jdk8 b/spring-cloud-function-web/.jdk8 new file mode 100644 index 000000000..e69de29bb diff --git a/spring-cloud-function-web/pom.xml b/spring-cloud-function-web/pom.xml new file mode 100644 index 000000000..b736cfbd0 --- /dev/null +++ b/spring-cloud-function-web/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + spring-cloud-function-web + jar + Spring Cloud Function Web Support + Spring Cloud Function Web Support + + + org.springframework.cloud + spring-cloud-function-parent + 1.0.0.BUILD-SNAPSHOT + + + + 1.8 + + + + + org.springframework + spring-web-reactive + 5.0.0.BUILD-SNAPSHOT + + + org.springframework + spring-web + 5.0.0.BUILD-SNAPSHOT + + + io.projectreactor.ipc + reactor-netty + 0.5.1.RELEASE + + + org.springframework.cloud + spring-cloud-function-core + ${project.version} + + + io.projectreactor + reactor-core + + + org.springframework.boot + spring-boot-starter-logging + + + org.springframework.boot + spring-boot-starter + + + + diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionConfigurationProperties.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionConfigurationProperties.java new file mode 100644 index 000000000..15398b84e --- /dev/null +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionConfigurationProperties.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016 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 + * + * http://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.web; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Mark Fisher + */ +@ConfigurationProperties(prefix = "function") +public class FunctionConfigurationProperties { + + private String name; + + private String code; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } +} diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RestApplication.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RestApplication.java new file mode 100644 index 000000000..3ccfab4af --- /dev/null +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RestApplication.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 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 + * + * http://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.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Mark Fisher + */ +@SpringBootApplication +public class RestApplication { + + public static void main(String[] args) { + SpringApplication.run(RestApplication.class, args); + } +} diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RestConfiguration.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RestConfiguration.java new file mode 100644 index 000000000..f75d7a38a --- /dev/null +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RestConfiguration.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016 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 + * + * http://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.web; + +import static org.springframework.http.MediaType.TEXT_PLAIN; +import static org.springframework.web.reactive.function.BodyExtractors.toFlux; +import static org.springframework.web.reactive.function.BodyInserters.fromPublisher; +import static org.springframework.web.reactive.function.RequestPredicates.contentType; +import static org.springframework.web.reactive.function.RequestPredicates.POST; + +import java.util.concurrent.Executors; +import java.util.function.Function; + +import org.reactivestreams.Publisher; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.function.registry.FunctionRegistry; +import org.springframework.cloud.function.registry.InMemoryFunctionRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; +import org.springframework.web.reactive.function.Request; +import org.springframework.web.reactive.function.Response; +import org.springframework.web.reactive.function.RouterFunction; +import org.springframework.web.reactive.function.RouterFunctions; + +import reactor.core.publisher.Flux; +import reactor.ipc.netty.http.HttpServer; + +/** + * @author Mark Fisher + */ +@Configuration +@EnableConfigurationProperties({ FunctionConfigurationProperties.class, WebConfigurationProperties.class }) +public class RestConfiguration { + + @Autowired + private FunctionConfigurationProperties functionProperties; + + @Autowired + private WebConfigurationProperties webProperties; + + @Bean + public FunctionRegistry registry() { + FunctionRegistry registry = new InMemoryFunctionRegistry(); + registry.register(functionProperties.getName(), functionProperties.getCode()); + return registry; + } + + @Bean + public HttpHandler httpHandler(FunctionRegistry registry) { + Function, Flux> function = registry.lookup(functionProperties.getName()); + FunctionInvokingHandler handler = new FunctionInvokingHandler(function); + RouterFunction> route = RouterFunctions.route( + POST(webProperties.getPath()).and(contentType(TEXT_PLAIN)), handler::handleText); + return RouterFunctions.toHttpHandler(route); + } + + @Bean + public LifecycleAwareHttpServer httpServer(HttpHandler handler) { + return new LifecycleAwareHttpServer(handler, webProperties.getPort()); + } + + private static class FunctionInvokingHandler { + + private final Function, Flux> function; + + private FunctionInvokingHandler(Function, Flux> function) { + this.function = function; + } + + private Response> handleText(Request request) { + Flux input = request.body(toFlux(String.class)); + Publisher output = this.function.apply(input); + return Response.ok().body(fromPublisher(output, String.class)); + } + } + + private static class LifecycleAwareHttpServer implements InitializingBean, DisposableBean { + + private final HttpHandler handler; + + private final int port; + + private volatile HttpServer server; + + private LifecycleAwareHttpServer(HttpHandler handler, int port) { + this.handler = handler; + this.port = port; + } + + @Override + public void afterPropertiesSet() { + ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(this.handler); + final HttpServer server = HttpServer.create("localhost", port); + this.server = server; + Executors.newSingleThreadExecutor().submit(new Runnable() { + + @Override + public void run() { + try { + server.startAndAwait(adapter); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }); + } + + @Override + public void destroy() { + if (this.server != null) { + this.server.shutdown(); + } + } + } +} diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/WebConfigurationProperties.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/WebConfigurationProperties.java new file mode 100644 index 000000000..9227949e6 --- /dev/null +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/WebConfigurationProperties.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016 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 + * + * http://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.web; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Mark Fisher + */ +@ConfigurationProperties(prefix = "web") +public class WebConfigurationProperties { + + private int port = 8080; + + private String path; + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } +}