diff --git a/spring-5/pom.xml b/spring-5/pom.xml
index eca36cc1d1..59bead4b73 100644
--- a/spring-5/pom.xml
+++ b/spring-5/pom.xml
@@ -35,6 +35,17 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+
+ io.projectreactor
+ reactor-core
+ 3.0.6.BUILD-SNAPSHOT
+
diff --git a/spring-5/src/main/java/com/baeldung/functional/Actor.java b/spring-5/src/main/java/com/baeldung/functional/Actor.java
new file mode 100644
index 0000000000..23c88b89e1
--- /dev/null
+++ b/spring-5/src/main/java/com/baeldung/functional/Actor.java
@@ -0,0 +1,23 @@
+package com.baeldung.functional;
+
+class Actor {
+ private String firstname;
+ private String lastname;
+
+ public Actor() {
+ }
+
+ public Actor(String firstname, String lastname) {
+ this.firstname = firstname;
+ this.lastname = lastname;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+}
diff --git a/spring-5/src/main/java/com/baeldung/functional/FormHandler.java b/spring-5/src/main/java/com/baeldung/functional/FormHandler.java
new file mode 100644
index 0000000000..c44db76f56
--- /dev/null
+++ b/spring-5/src/main/java/com/baeldung/functional/FormHandler.java
@@ -0,0 +1,50 @@
+package com.baeldung.functional;
+
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers;
+import static org.springframework.web.reactive.function.BodyExtractors.toFormData;
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.server.ServerResponse.ok;
+
+public class FormHandler {
+
+ Mono handleLogin(ServerRequest request) {
+ return request
+ .body(toFormData())
+ .map(MultiValueMap::toSingleValueMap)
+ .map(formData -> {
+ System.out.println("form data: " + formData.toString());
+ if ("baeldung".equals(formData.get("user")) && "you_know_what_to_do".equals(formData.get("token"))) {
+ return ok()
+ .body(Mono.just("welcome back!"), String.class)
+ .block();
+ }
+ return ServerResponse
+ .badRequest()
+ .build()
+ .block();
+ });
+ }
+
+ Mono handleUpload(ServerRequest request) {
+ return request
+ .body(toDataBuffers())
+ .collectList()
+ .map(dataBuffers -> {
+ AtomicLong atomicLong = new AtomicLong(0);
+ dataBuffers.forEach(d -> atomicLong.addAndGet(d
+ .asByteBuffer()
+ .array().length));
+ System.out.println("data length:" + atomicLong.get());
+ return ok()
+ .body(fromObject(atomicLong.toString()))
+ .block();
+ });
+ }
+}
diff --git a/spring-5/src/main/java/com/baeldung/functional/FunctionalWebApplication.java b/spring-5/src/main/java/com/baeldung/functional/FunctionalWebApplication.java
new file mode 100644
index 0000000000..573813b166
--- /dev/null
+++ b/spring-5/src/main/java/com/baeldung/functional/FunctionalWebApplication.java
@@ -0,0 +1,80 @@
+package com.baeldung.functional;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.startup.Tomcat;
+import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
+import org.springframework.boot.web.server.WebServer;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.WebHandler;
+import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
+import reactor.core.publisher.Flux;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.server.RequestPredicates.*;
+import static org.springframework.web.reactive.function.server.RouterFunctions.route;
+import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
+import static org.springframework.web.reactive.function.server.ServerResponse.ok;
+
+public class FunctionalWebApplication {
+
+ private static final Actor BRAD_PITT = new Actor("Brad", "Pitt");
+ private static final Actor TOM_HANKS = new Actor("Tom", "Hanks");
+ private static final List actors = new CopyOnWriteArrayList<>(Arrays.asList(BRAD_PITT, TOM_HANKS));
+
+ private RouterFunction routingFunction() {
+ FormHandler formHandler = new FormHandler();
+
+ RouterFunction restfulRouter = route(GET("/"), serverRequest -> ok().body(Flux.fromIterable(actors), Actor.class)).andRoute(POST("/"), serverRequest -> serverRequest
+ .bodyToMono(Actor.class)
+ .doOnNext(actors::add)
+ .then(ok().build()));
+
+ return route(GET("/test"), serverRequest -> ok().body(fromObject("helloworld")))
+ .andRoute(POST("/login"), formHandler::handleLogin)
+ .andRoute(POST("/upload"), formHandler::handleUpload)
+ .and(RouterFunctions.resources("/files/**", new ClassPathResource("files/")))
+ .andNest(path("/actor"), restfulRouter)
+ .filter((request, next) -> {
+ System.out.println("Before handler invocation: " + request.path());
+ return next.handle(request);
+ });
+ }
+
+ WebServer start() throws Exception {
+ WebHandler webHandler = toHttpHandler(routingFunction());
+ HttpHandler httpHandler = WebHttpHandlerBuilder
+ .webHandler(webHandler)
+ .prependFilter(new IndexRewriteFilter())
+ .build();
+
+ Tomcat tomcat = new Tomcat();
+ tomcat.setHostname("localhost");
+ tomcat.setPort(9090);
+ Context rootContext = tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+ ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
+ Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
+ rootContext.addServletMappingDecoded("/", "httpHandlerServlet");
+
+ TomcatWebServer server = new TomcatWebServer(tomcat);
+ server.start();
+ return server;
+
+ }
+
+ public static void main(String[] args) {
+ try {
+ new FunctionalWebApplication().start();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/spring-5/src/main/java/com/baeldung/functional/IndexRewriteFilter.java b/spring-5/src/main/java/com/baeldung/functional/IndexRewriteFilter.java
new file mode 100644
index 0000000000..3e19f81943
--- /dev/null
+++ b/spring-5/src/main/java/com/baeldung/functional/IndexRewriteFilter.java
@@ -0,0 +1,29 @@
+package com.baeldung.functional;
+
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+class IndexRewriteFilter implements WebFilter {
+
+ @Override
+ public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
+ ServerHttpRequest request = serverWebExchange.getRequest();
+ if (request
+ .getURI()
+ .getPath()
+ .equals("/")) {
+ return webFilterChain.filter(serverWebExchange
+ .mutate()
+ .request(builder -> builder
+ .method(request.getMethod())
+ .contextPath(request.getContextPath())
+ .path("/test"))
+ .build());
+ }
+ return webFilterChain.filter(serverWebExchange);
+ }
+
+}
diff --git a/spring-5/src/main/resources/files/hello.txt b/spring-5/src/main/resources/files/hello.txt
new file mode 100644
index 0000000000..b6fc4c620b
--- /dev/null
+++ b/spring-5/src/main/resources/files/hello.txt
@@ -0,0 +1 @@
+hello
\ No newline at end of file
diff --git a/spring-5/src/test/java/com/baeldung/functional/FunctionalWebApplicationIntegrationTest.java b/spring-5/src/test/java/com/baeldung/functional/FunctionalWebApplicationIntegrationTest.java
new file mode 100644
index 0000000000..bf28ed1e7d
--- /dev/null
+++ b/spring-5/src/test/java/com/baeldung/functional/FunctionalWebApplicationIntegrationTest.java
@@ -0,0 +1,154 @@
+package com.baeldung.functional;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.boot.web.server.WebServer;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.reactive.function.BodyInserters;
+
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.BodyInserters.fromResource;
+
+public class FunctionalWebApplicationIntegrationTest {
+
+ private static WebTestClient client;
+ private static WebServer server;
+
+ @BeforeClass
+ public static void setup() throws Exception {
+ server = new FunctionalWebApplication().start();
+ client = WebTestClient
+ .bindToServer()
+ .baseUrl("http://localhost:" + server.getPort())
+ .build();
+ }
+
+ @AfterClass
+ public static void destroy() {
+ server.stop();
+ }
+
+ @Test
+ public void givenRouter_whenGetTest_thenGotHelloWorld() throws Exception {
+ client
+ .get()
+ .uri("/test")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .value()
+ .isEqualTo("helloworld");
+ }
+
+ @Test
+ public void givenIndexFilter_whenRequestRoot_thenRewrittenToTest() throws Exception {
+ client
+ .get()
+ .uri("/")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .value()
+ .isEqualTo("helloworld");
+ }
+
+ @Test
+ public void givenLoginForm_whenPostValidToken_thenSuccess() throws Exception {
+ MultiValueMap formData = new LinkedMultiValueMap<>(1);
+ formData.add("user", "baeldung");
+ formData.add("token", "you_know_what_to_do");
+
+ client
+ .post()
+ .uri("/login")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .exchange(BodyInserters.fromFormData(formData))
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .value()
+ .isEqualTo("welcome back!");
+ }
+
+ @Test
+ public void givenLoginForm_whenRequestWithInvalidToken_thenFail() throws Exception {
+ MultiValueMap formData = new LinkedMultiValueMap<>(2);
+ formData.add("user", "baeldung");
+ formData.add("token", "try_again");
+
+ client
+ .post()
+ .uri("/login")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .exchange(BodyInserters.fromFormData(formData))
+ .expectStatus()
+ .isBadRequest();
+ }
+
+ @Test
+ public void givenUploadForm_whenRequestWithMultipartData_thenSuccess() throws Exception {
+ Resource resource = new ClassPathResource("/baeldung-weekly.png");
+ client
+ .post()
+ .uri("/upload")
+ .contentType(MediaType.MULTIPART_FORM_DATA)
+ .exchange(fromResource(resource))
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .value()
+ .isEqualTo(String.valueOf(resource.contentLength()));
+ }
+
+ @Test
+ public void givenActors_whenAddActor_thenAdded() throws Exception {
+ client
+ .get()
+ .uri("/actor")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(Actor.class)
+ .list()
+ .hasSize(2);
+
+ client
+ .post()
+ .uri("/actor")
+ .exchange(fromObject(new Actor("Clint", "Eastwood")))
+ .expectStatus()
+ .isOk();
+
+ client
+ .get()
+ .uri("/actor")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(Actor.class)
+ .list()
+ .hasSize(3);
+ }
+
+ @Test
+ public void givenResources_whenAccess_thenGot() throws Exception {
+ client
+ .get()
+ .uri("/files/hello.txt")
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .expectBody(String.class)
+ .value()
+ .isEqualTo("hello");
+ }
+
+}
diff --git a/spring-5/src/test/resources/baeldung-weekly.png b/spring-5/src/test/resources/baeldung-weekly.png
new file mode 100644
index 0000000000..5a27d61dae
Binary files /dev/null and b/spring-5/src/test/resources/baeldung-weekly.png differ