diff --git a/spring-reactive-modules/pom.xml b/spring-reactive-modules/pom.xml index a8a9d6de8a..2618ea67aa 100644 --- a/spring-reactive-modules/pom.xml +++ b/spring-reactive-modules/pom.xml @@ -20,6 +20,7 @@ spring-reactive-data spring-reactive-2 spring-reactive-3 + spring-reactive-4 spring-reactive-client spring-reactive-client-2 spring-reactive-filters diff --git a/spring-reactive-modules/spring-reactive-4/pom.xml b/spring-reactive-modules/spring-reactive-4/pom.xml new file mode 100644 index 0000000000..7fb099ec1e --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + spring-reactive-4 + 0.0.1-SNAPSHOT + spring-reactive-4 + jar + spring sample project about new features + + + com.baeldung + parent-boot-3 + 0.0.1-SNAPSHOT + ../../parent-boot-3 + + + + 3.4.2 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.projectreactor + reactor-test + test + + + + io.netty + netty-all + + + + org.wiremock + wiremock-standalone + ${wiremock-standalone.version} + test + + + + diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/SpringReactiveApplication.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/SpringReactiveApplication.java new file mode 100644 index 0000000000..21fbfdac3e --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/SpringReactiveApplication.java @@ -0,0 +1,13 @@ +package com.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringReactiveApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringReactiveApplication.class, args); + } + +} diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/controller/TraceController.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/controller/TraceController.java new file mode 100644 index 0000000000..c6a564e6d6 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/controller/TraceController.java @@ -0,0 +1,21 @@ +package com.baeldung.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +import reactor.core.publisher.Mono; + +@RestController +public class TraceController { + + @GetMapping(value = "/trace-annotated") + public Mono trace(@RequestHeader(name = "traceId") final String traceId) { + return Mono.just("TraceId: ".concat(traceId)); + } + + @GetMapping(value = "/trace-exceptional") + public Mono traceExceptional() { + return Mono.just("Traced"); + } +} diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/ExceptionalTraceFilter.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/ExceptionalTraceFilter.java new file mode 100644 index 0000000000..b721d10109 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/ExceptionalTraceFilter.java @@ -0,0 +1,25 @@ +package com.baeldung.filters; + +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; + +import reactor.core.publisher.Mono; + +@Component +public class ExceptionalTraceFilter implements WebFilter { + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + if (exchange.getRequest() + .getPath() + .toString() + .equals("/trace-exceptional")) { + exchange.getRequest() + .getHeaders() + .add("traceId", "TRACE-ID"); + } + return chain.filter(exchange); + } +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/TraceHandlerFilterFunction.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/TraceHandlerFilterFunction.java new file mode 100644 index 0000000000..708e4dc509 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/TraceHandlerFilterFunction.java @@ -0,0 +1,20 @@ +package com.baeldung.filters; + +import org.springframework.web.reactive.function.server.HandlerFilterFunction; +import org.springframework.web.reactive.function.server.HandlerFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; + +import reactor.core.publisher.Mono; + +public class TraceHandlerFilterFunction implements HandlerFilterFunction { + + @Override + public Mono filter(ServerRequest request, HandlerFunction handlerFunction) { + + ServerRequest serverRequest = ServerRequest.from(request) + .header("traceId", "FUNCTIONAL-TRACE-ID") + .build(); + return handlerFunction.handle(serverRequest); + } +} diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/TraceWebFilter.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/TraceWebFilter.java new file mode 100644 index 0000000000..fcb5570db6 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/TraceWebFilter.java @@ -0,0 +1,20 @@ +package com.baeldung.filters; + +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; + +import reactor.core.publisher.Mono; + +@Component +public class TraceWebFilter implements WebFilter { + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + exchange.getRequest() + .mutate() + .header("traceId", "ANNOTATED-TRACE-ID"); + return chain.filter(exchange); + } +} diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/WebClientFilters.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/WebClientFilters.java new file mode 100644 index 0000000000..1748d4d103 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/filters/WebClientFilters.java @@ -0,0 +1,18 @@ +package com.baeldung.filters; + +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; + +public final class WebClientFilters { + + public static ExchangeFilterFunction modifyRequestHeaders(final MultiValueMap changedMap) { + return (request, next) -> { + final ClientRequest clientRequest = ClientRequest.from(request) + .header("traceId", "TRACE-ID") + .build(); + changedMap.addAll(clientRequest.headers()); + return next.exchange(clientRequest); + }; + } +} diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/handler/TraceRouterHandler.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/handler/TraceRouterHandler.java new file mode 100644 index 0000000000..7285a564c3 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/handler/TraceRouterHandler.java @@ -0,0 +1,22 @@ +package com.baeldung.handler; + +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; + +import reactor.core.publisher.Mono; + +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +@Component +public class TraceRouterHandler { + + public Mono handle(final ServerRequest serverRequest) { + + final String traceId = serverRequest.headers() + .firstHeader("traceId"); + assert traceId != null; + final Mono body = Mono.just("TraceId: ".concat(traceId)); + return ok().body(body, String.class); + } +} diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/router/TraceRouter.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/router/TraceRouter.java new file mode 100644 index 0000000000..cd25477965 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/router/TraceRouter.java @@ -0,0 +1,27 @@ +package com.baeldung.router; + +import com.baeldung.filters.TraceHandlerFilterFunction; +import com.baeldung.handler.TraceRouterHandler; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; + +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; + +@Configuration +public class TraceRouter { + + @Bean + public RouterFunction routes(TraceRouterHandler routerHandler) { + return route(GET("/trace-functional-filter"), routerHandler::handle).filter(new TraceHandlerFilterFunction()) + .and(route().GET("/trace-functional-before", routerHandler::handle) + .before(request -> ServerRequest.from(request) + .header("traceId", "FUNCTIONAL-TRACE-ID") + .build()) + .build()); + } +} diff --git a/spring-reactive-modules/spring-reactive-4/src/main/resources/application.properties b/spring-reactive-modules/spring-reactive-4/src/main/resources/application.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-reactive-modules/spring-reactive-4/src/main/resources/logback.xml b/spring-reactive-modules/spring-reactive-4/src/main/resources/logback.xml new file mode 100644 index 0000000000..26beb6d5b4 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/controller/TraceControllerIntegrationTest.java b/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/controller/TraceControllerIntegrationTest.java new file mode 100644 index 0000000000..eba9ba10a3 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/controller/TraceControllerIntegrationTest.java @@ -0,0 +1,50 @@ +package com.baeldung.controller; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.reactive.server.EntityExchangeResult; +import org.springframework.test.web.reactive.server.WebTestClient; + +@ExtendWith(SpringExtension.class) +@WebFluxTest(controllers = TraceController.class) +public class TraceControllerIntegrationTest { + + @Autowired + private WebTestClient webTestClient; + + @Test + void whenCallTraceAnnotatedEndpoint_thenResponseContainsTraceId() { + EntityExchangeResult result = webTestClient.get() + .uri("/trace-annotated") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .returnResult(); + + final String body = "TraceId: ANNOTATED-TRACE-ID"; + assertEquals(result.getResponseBody(), body); + + } + + @Test + void whenCallTraceExceptionalEndpoint_thenThrowsException() { + EntityExchangeResult result = webTestClient.get() + .uri("/trace-exceptional") + .exchange() + .expectStatus() + .is5xxServerError() + .expectBody(Map.class) + .returnResult(); + + assertNotNull(result.getResponseBody()); + } +} diff --git a/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/filters/WebClientFilterUnitTest.java b/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/filters/WebClientFilterUnitTest.java new file mode 100644 index 0000000000..319f1e6f75 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/filters/WebClientFilterUnitTest.java @@ -0,0 +1,55 @@ +package com.baeldung.filters; + +import static com.baeldung.filters.WebClientFilters.modifyRequestHeaders; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.client.WebClient; + +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; + +@WireMockTest +public class WebClientFilterUnitTest { + + @RegisterExtension + static WireMockExtension extension = WireMockExtension.newInstance() + .options(wireMockConfig().dynamicPort() + .dynamicHttpsPort()) + .build(); + + @Test + void whenCallEndpoint_thenRequestHeadersModified() { + extension.stubFor(get("/test").willReturn(aResponse().withStatus(200) + .withBody("SUCCESS"))); + + final MultiValueMap map = new LinkedMultiValueMap<>(); + + WebClient webClient = WebClient.builder() + .filter(modifyRequestHeaders(map)) + .build(); + String actual = sendGetRequest(webClient); + + final String body = "SUCCESS"; + Assertions.assertEquals(actual, body); + Assertions.assertEquals("TRACE-ID", map.getFirst("traceId")); + } + + private String sendGetRequest(final WebClient webClient) { + return webClient.get() + .uri(getUrl()) + .retrieve() + .bodyToMono(String.class) + .block(); + } + + private String getUrl() { + return "http://localhost:" + extension.getPort() + "/test"; + } +} diff --git a/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/handler/TraceRouteHandlerIntegrationTest.java b/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/handler/TraceRouteHandlerIntegrationTest.java new file mode 100644 index 0000000000..ee30dfd3a2 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/handler/TraceRouteHandlerIntegrationTest.java @@ -0,0 +1,60 @@ +package com.baeldung.handler; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.reactive.server.EntityExchangeResult; +import org.springframework.test.web.reactive.server.WebTestClient; + +import com.baeldung.router.TraceRouter; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = { TraceRouter.class, TraceRouterHandler.class }) +@WebFluxTest +public class TraceRouteHandlerIntegrationTest { + + @Autowired + private ApplicationContext context; + private WebTestClient webTestClient; + + @BeforeEach + void setUp() { + webTestClient = WebTestClient.bindToApplicationContext(context) + .build(); + } + + @Test + void whenCallTraceFunctionalFilterEndpoint_thenResponseContainsTraceId() { + EntityExchangeResult result = webTestClient.get() + .uri("/trace-functional-filter") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .returnResult(); + + final String body = "TraceId: FUNCTIONAL-TRACE-ID"; + assertEquals(result.getResponseBody(), body); + } + + @Test + void whenCallTraceFunctionalBeforeEndpoint_thenResponseContainsTraceId() { + EntityExchangeResult result = webTestClient.get() + .uri("/trace-functional-before") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .returnResult(); + + final String body = "TraceId: FUNCTIONAL-TRACE-ID"; + assertEquals(result.getResponseBody(), body); + } +}