[BAEL-7546] - How to intercept a request and add headers in WebFlux (#16291)

* [BAEL-7546] - How to intercept a request and add headers in WebFlux
* Added the Implementation Code.

* [BAEL-7546] - How to intercept a request and add headers in WebFlux
* Added the JUnit Tests.

* [BAEL-7546] - How to intercept a request and add headers in WebFlux
* Updated parent module with the new module reference.

* [BAEL-7546] - How to intercept a request and add headers in WebFlux
* CICD Build Fix for PMD Violation

* [BAEL-7546] - How to intercept a request and add headers in WebFlux
* CICD Build Fix for PMD Violation

* [BAEL-7546] - How to intercept a request and add headers in WebFlux
* Use four space indents in the pom file.

* [BAEL-7546] - How to intercept a request and add headers in WebFlux
* Use 2 space indents line continuation.

* [BAEL-7546] - How to intercept a request and add headers in WebFlux
* Add test case for Unmodifiable Header map scenario.

* [BAEL-7546] - How to intercept a request and add headers in WebFlux
* Indentation fixes.
This commit is contained in:
Ruchira Madhushan Rajapaksha 2024-04-14 10:01:07 +08:00 committed by GitHub
parent f18a5c4472
commit 9219e0216e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 398 additions and 0 deletions

View File

@ -20,6 +20,7 @@
<module>spring-reactive-data</module>
<module>spring-reactive-2</module>
<module>spring-reactive-3</module>
<module>spring-reactive-4</module>
<module>spring-reactive-client</module>
<module>spring-reactive-client-2</module>
<module>spring-reactive-filters</module>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-reactive-4</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-reactive-4</name>
<packaging>jar</packaging>
<description>spring sample project about new features</description>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-3</relativePath>
</parent>
<properties>
<wiremock-standalone.version>3.4.2</wiremock-standalone.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>${wiremock-standalone.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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);
}
}

View File

@ -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<String> trace(@RequestHeader(name = "traceId") final String traceId) {
return Mono.just("TraceId: ".concat(traceId));
}
@GetMapping(value = "/trace-exceptional")
public Mono<String> traceExceptional() {
return Mono.just("Traced");
}
}

View File

@ -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<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (exchange.getRequest()
.getPath()
.toString()
.equals("/trace-exceptional")) {
exchange.getRequest()
.getHeaders()
.add("traceId", "TRACE-ID");
}
return chain.filter(exchange);
}
}

View File

@ -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<ServerResponse, ServerResponse> {
@Override
public Mono<ServerResponse> filter(ServerRequest request, HandlerFunction<ServerResponse> handlerFunction) {
ServerRequest serverRequest = ServerRequest.from(request)
.header("traceId", "FUNCTIONAL-TRACE-ID")
.build();
return handlerFunction.handle(serverRequest);
}
}

View File

@ -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<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
exchange.getRequest()
.mutate()
.header("traceId", "ANNOTATED-TRACE-ID");
return chain.filter(exchange);
}
}

View File

@ -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<String, String> changedMap) {
return (request, next) -> {
final ClientRequest clientRequest = ClientRequest.from(request)
.header("traceId", "TRACE-ID")
.build();
changedMap.addAll(clientRequest.headers());
return next.exchange(clientRequest);
};
}
}

View File

@ -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<ServerResponse> handle(final ServerRequest serverRequest) {
final String traceId = serverRequest.headers()
.firstHeader("traceId");
assert traceId != null;
final Mono<String> body = Mono.just("TraceId: ".concat(traceId));
return ok().body(body, String.class);
}
}

View File

@ -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<ServerResponse> 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());
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

View File

@ -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<String> 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<Map> result = webTestClient.get()
.uri("/trace-exceptional")
.exchange()
.expectStatus()
.is5xxServerError()
.expectBody(Map.class)
.returnResult();
assertNotNull(result.getResponseBody());
}
}

View File

@ -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<String, String> 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";
}
}

View File

@ -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<String> 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<String> 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);
}
}