diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 415bc5c874c..2dcde3f4fce 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -112,6 +112,7 @@ org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguratio org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\ +org.springframework.boot.autoconfigure.webflux.ReactiveWebServerAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableReactiveWebServer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableReactiveWebServer.java new file mode 100644 index 00000000000..d61682fb8b6 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableReactiveWebServer.java @@ -0,0 +1,146 @@ +/* + * Copyright 2012-2017 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.boot.context.embedded; + +import java.net.InetAddress; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.boot.web.servlet.ErrorPage; +import org.springframework.util.Assert; + +/** + * Abstract base class for {@link ConfigurableReactiveWebServer} implementations. + * + * @author Brian Clozel + * @since 2.0.0 + */ +public class AbstractConfigurableReactiveWebServer implements ConfigurableReactiveWebServer { + + private int port = 8080; + + private Set errorPages = new LinkedHashSet(); + + private InetAddress address; + + private Ssl ssl; + + private SslStoreProvider sslStoreProvider; + + private Compression compression; + + private String serverHeader; + + /** + * Create a new {@link AbstractConfigurableReactiveWebServer} instance. + */ + public AbstractConfigurableReactiveWebServer() { + } + + /** + * Create a new {@link AbstractConfigurableReactiveWebServer} instance with the + * specified port. + * @param port the port number for the reactive web server + */ + public AbstractConfigurableReactiveWebServer(int port) { + this.port = port; + } + + @Override + public void setAddress(InetAddress address) { + this.address = address; + } + + /** + * Return the address that the reactive web server binds to. + * @return the address + */ + public InetAddress getAddress() { + return this.address; + } + + @Override + public void setPort(int port) { + this.port = port; + } + + /** + * The port that the reactive web server listens on. + * @return the port + */ + public int getPort() { + return this.port; + } + + @Override + public void setErrorPages(Set errorPages) { + Assert.notNull(errorPages, "ErrorPages must not be null"); + this.errorPages = new LinkedHashSet(errorPages); + } + + @Override + public void addErrorPages(ErrorPage... errorPages) { + Assert.notNull(errorPages, "ErrorPages must not be null"); + this.errorPages.addAll(Arrays.asList(errorPages)); + } + + /** + * Return a mutable set of {@link ErrorPage ErrorPages} that will be used when + * handling exceptions. + * @return the error pages + */ + public Set getErrorPages() { + return this.errorPages; + } + + @Override + public void setSsl(Ssl ssl) { + this.ssl = ssl; + } + + public Ssl getSsl() { + return this.ssl; + } + + @Override + public void setSslStoreProvider(SslStoreProvider sslStoreProvider) { + this.sslStoreProvider = sslStoreProvider; + } + + public SslStoreProvider getSslStoreProvider() { + return this.sslStoreProvider; + } + + public Compression getCompression() { + return this.compression; + } + + @Override + public void setCompression(Compression compression) { + this.compression = compression; + } + + public String getServerHeader() { + return this.serverHeader; + } + + @Override + public void setServerHeader(String serverHeader) { + this.serverHeader = serverHeader; + } +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractReactiveWebServerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractReactiveWebServerFactory.java new file mode 100644 index 00000000000..002a5431d48 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractReactiveWebServerFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2017 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.boot.context.embedded; + +/** + * Abstract base class for {@link ReactiveWebServerFactory} implementations. + * + * @author Brian Clozel + * @since 2.0.0 + */ +public abstract class AbstractReactiveWebServerFactory + extends AbstractConfigurableReactiveWebServer + implements ReactiveWebServerFactory { + + public AbstractReactiveWebServerFactory() { + } + + public AbstractReactiveWebServerFactory(int port) { + super(port); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java index 6d2d6bd0042..3bb474d6dbc 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java @@ -17,16 +17,12 @@ package org.springframework.boot.context.embedded; import java.io.File; -import java.net.InetAddress; import java.nio.charset.Charset; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.concurrent.TimeUnit; -import org.springframework.boot.web.servlet.ErrorPage; -import org.springframework.boot.web.servlet.ErrorPageRegistry; import org.springframework.boot.web.servlet.ServletContextInitializer; /** @@ -41,7 +37,7 @@ import org.springframework.boot.web.servlet.ServletContextInitializer; * @see EmbeddedServletContainerFactory * @see EmbeddedServletContainerCustomizer */ -public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry { +public interface ConfigurableEmbeddedServletContainer extends ConfigurableEmbeddedWebServer { /** * Sets the context path for the embedded servlet container. The context should start @@ -59,14 +55,6 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry */ void setDisplayName(String displayName); - /** - * Sets the port that the embedded servlet container should listen on. If not - * specified port '8080' will be used. Use port -1 to disable auto-start (i.e start - * the web application context but not have it listen to any port). - * @param port the port to set - */ - void setPort(int port); - /** * The session timeout in seconds (default 30 minutes). If 0 or negative then sessions * never expire. @@ -94,12 +82,6 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry */ void setSessionStoreDir(File sessionStoreDir); - /** - * Sets the specific network address that the server should bind to. - * @param address the address to set (defaults to {@code null}) - */ - void setAddress(InetAddress address); - /** * Set if the DefaultServlet should be registered. Defaults to {@code true} so that * files from the {@link #setDocumentRoot(File) document root} will be served. @@ -107,12 +89,6 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry */ void setRegisterDefaultServlet(boolean registerDefaultServlet); - /** - * Sets the error pages that will be used when handling exceptions. - * @param errorPages the error pages - */ - void setErrorPages(Set errorPages); - /** * Sets the mime-type mappings. * @param mimeMappings the mime type mappings (defaults to @@ -146,38 +122,12 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry */ void addInitializers(ServletContextInitializer... initializers); - /** - * Sets the SSL configuration that will be applied to the container's default - * connector. - * @param ssl the SSL configuration - */ - void setSsl(Ssl ssl); - - /** - * Sets a provider that will be used to obtain SSL stores. - * @param sslStoreProvider the SSL store provider - */ - void setSslStoreProvider(SslStoreProvider sslStoreProvider); - /** * Sets the configuration that will be applied to the container's JSP servlet. * @param jsp the JSP servlet configuration */ void setJsp(Jsp jsp); - /** - * Sets the compression configuration that will be applied to the container's default - * connector. - * @param compression the compression configuration - */ - void setCompression(Compression compression); - - /** - * Sets the server header value. - * @param serverHeader the server header value - */ - void setServerHeader(String serverHeader); - /** * Sets the Locale to Charset mappings. * @param localeCharsetMappings the Locale to Charset mappings diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedWebServer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedWebServer.java new file mode 100644 index 00000000000..63013d8e26d --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedWebServer.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2017 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.boot.context.embedded; + +import java.net.InetAddress; +import java.util.Set; + +import org.springframework.boot.web.servlet.ErrorPage; +import org.springframework.boot.web.servlet.ErrorPageRegistry; + +/** + * Interface that regroups common customizations to + * embedded server factories such as {@link EmbeddedServletContainerFactory} + * and {@link ReactiveWebServerFactory}. + * @author Brian Clozel + * @since 2.0.0 + */ +public interface ConfigurableEmbeddedWebServer extends ErrorPageRegistry { + + /** + * Sets the port that the embedded servlet container should listen on. If not + * specified port '8080' will be used. Use port -1 to disable auto-start (i.e start + * the web application context but not have it listen to any port). + * @param port the port to set + */ + void setPort(int port); + + /** + * Sets the specific network address that the server should bind to. + * @param address the address to set (defaults to {@code null}) + */ + void setAddress(InetAddress address); + + /** + * Sets the error pages that will be used when handling exceptions. + * @param errorPages the error pages + */ + void setErrorPages(Set errorPages); + + /** + * Sets the SSL configuration that will be applied to the container's default + * connector. + * @param ssl the SSL configuration + */ + void setSsl(Ssl ssl); + + /** + * Sets a provider that will be used to obtain SSL stores. + * @param sslStoreProvider the SSL store provider + */ + void setSslStoreProvider(SslStoreProvider sslStoreProvider); + + /** + * Sets the compression configuration that will be applied to the container's default + * connector. + * @param compression the compression configuration + */ + void setCompression(Compression compression); + + /** + * Sets the server header value. + * @param serverHeader the server header value + */ + void setServerHeader(String serverHeader); + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableReactiveWebServer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableReactiveWebServer.java new file mode 100644 index 00000000000..716711cd61f --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableReactiveWebServer.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2017 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.boot.context.embedded; + +/** + * Interface that represents customizations to a {@link ReactiveWebServerFactory}. + * + * @author Brian Clozel + * @since 2.0.0 + */ +public interface ConfigurableReactiveWebServer extends ConfigurableEmbeddedWebServer { + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerCustomizer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerCustomizer.java new file mode 100644 index 00000000000..6dcc3f4a224 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerCustomizer.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2017 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.boot.context.embedded; + +/** + * Strategy interface for customizing auto-configured embedded reactive servers. Any + * beans of this type will get a callback with the server factory before the server + * itself is started, so you can set the port, address, error pages etc. + * @author Brian Clozel + * @since 2.0.0 + */ +@FunctionalInterface +public interface ReactiveWebServerCustomizer { + + /** + * Customize the specified {@link ConfigurableReactiveWebServer}. + * @param server the server to customize + */ + void customize(ConfigurableReactiveWebServer server); +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerCustomizerBeanPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerCustomizerBeanPostProcessor.java new file mode 100644 index 00000000000..36693a8066a --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerCustomizerBeanPostProcessor.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2017 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.boot.context.embedded; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; + +/** + * {@link BeanPostProcessor} that applies all {@link ReactiveWebServerCustomizer}s + * from the bean factory to {@link ConfigurableReactiveWebServer} beans. + * + * + * @author Brian Clozel + */ +public class ReactiveWebServerCustomizerBeanPostProcessor + implements BeanPostProcessor, ApplicationContextAware { + + private ApplicationContext applicationContext; + + private List customizers; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof ConfigurableReactiveWebServer) { + postProcessBeforeInitialization((ConfigurableReactiveWebServer) bean); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + private void postProcessBeforeInitialization( + ConfigurableReactiveWebServer bean) { + for (ReactiveWebServerCustomizer customizer : getCustomizers()) { + customizer.customize(bean); + } + } + + private Collection getCustomizers() { + if (this.customizers == null) { + // Look up does not include the parent context + this.customizers = new ArrayList( + this.applicationContext + .getBeansOfType(ReactiveWebServerCustomizer.class, + false, false) + .values()); + Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE); + this.customizers = Collections.unmodifiableList(this.customizers); + } + return this.customizers; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerFactory.java new file mode 100644 index 00000000000..6162d07e373 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2017 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.boot.context.embedded; + +import java.util.Map; + +import org.springframework.context.ApplicationContext; +import org.springframework.http.server.reactive.HttpHandler; + +/** + * Factory interface that can be used to create reactive {@link EmbeddedWebServer}s. + * + * @author Brian Clozel + * @since 2.0.0 + * @see EmbeddedWebServer + */ +public interface ReactiveWebServerFactory { + + /** + * Gets a new fully configured but paused {@link EmbeddedWebServer} instance. + * Clients should not be able to connect to the returned server until + * {@link EmbeddedWebServer#start()} is called (which happens when the + * {@link ApplicationContext} has been fully refreshed). + * @param httpHandler the HTTP handler in charge of processing requests + * @return a fully configured and started {@link EmbeddedWebServer} + * @see EmbeddedWebServer#stop() + */ + EmbeddedWebServer getReactiveHttpServer(HttpHandler httpHandler); + + /** + * Register a map of {@link HttpHandler}s, each to a specific context path. + * + * @param handlerMap a map of context paths and the associated {@code HttpHandler} + * @return a fully configured and started {@link EmbeddedWebServer} + * @see EmbeddedWebServer#stop() + */ + EmbeddedWebServer getReactiveHttpServer(Map handlerMap); +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractReactiveWebServerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractReactiveWebServerFactoryTests.java new file mode 100644 index 00000000000..00286869aaf --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractReactiveWebServerFactoryTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2012-2017 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.boot.context.embedded; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.boot.testutil.InternalOutputCapture; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.SocketUtils; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Base for testing classes that extends {@link AbstractReactiveWebServerFactory}. + * + * @author Brian Clozel + */ +public abstract class AbstractReactiveWebServerFactoryTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Rule + public InternalOutputCapture output = new InternalOutputCapture(); + + protected EmbeddedWebServer webServer; + + + @After + public void tearDown() { + if (this.webServer != null) { + try { + this.webServer.stop(); + } + catch (Exception ex) { + // Ignore + } + } + } + + protected abstract AbstractReactiveWebServerFactory getFactory(); + + @Test + public void startStopServer() { + this.webServer = getFactory().getReactiveHttpServer(new EchoHandler()); + this.webServer.start(); + Mono result = getWebClient() + .post().uri("/test") + .contentType(MediaType.TEXT_PLAIN) + .exchange(BodyInserters.fromObject("Hello World")) + .then(response -> response.bodyToMono(String.class)); + assertThat(result.block()).isEqualTo("Hello World"); + + this.webServer.stop(); + Mono response = getWebClient() + .post().uri("/test") + .contentType(MediaType.TEXT_PLAIN) + .exchange(BodyInserters.fromObject("Hello World")); + StepVerifier.create(response) + .expectError() + .verify(); + } + + @Test + public void specificPort() throws Exception { + AbstractReactiveWebServerFactory factory = getFactory(); + int specificPort = SocketUtils.findAvailableTcpPort(41000); + factory.setPort(specificPort); + this.webServer = factory.getReactiveHttpServer(new EchoHandler()); + this.webServer.start(); + + Mono result = WebClient.create("http://localhost:" + specificPort) + .post().uri("/test") + .contentType(MediaType.TEXT_PLAIN) + .exchange(BodyInserters.fromObject("Hello World")) + .then(response -> response.bodyToMono(String.class)); + + assertThat(result.block()).isEqualTo("Hello World"); + assertThat(this.webServer.getPort()).isEqualTo(specificPort); + } + + protected WebClient getWebClient() { + return WebClient.create("http://localhost:" + this.webServer.getPort()); + } + + protected static class EchoHandler implements HttpHandler { + @Override + public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { + response.setStatusCode(HttpStatus.OK); + return response.writeWith(request.getBody()); + } + } +}