BAEL-7125: Global Exception Handling with Spring Cloud Gateway (#15566)
* BAEL-7125: Global Exception Handling with Spring Cloud Gateway * Fix name * Fix condition
This commit is contained in:
parent
a94bf971f5
commit
99832d0d4d
|
@ -36,6 +36,8 @@
|
|||
|
||||
<properties>
|
||||
<spring.version>6.1.2</spring.version>
|
||||
<spring-cloud.version>2023.0.0</spring-cloud.version>
|
||||
<spring-boot.version>3.2.1</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -8,6 +8,7 @@
|
|||
<artifactId>parent-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>parent-modules</name>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<dependencies>
|
||||
|
@ -410,6 +411,7 @@
|
|||
<module>parent-spring-6</module>
|
||||
|
||||
<module>spring-4</module>
|
||||
<module>spring-6</module>
|
||||
|
||||
<module>spring-cloud-modules</module>
|
||||
<!-- <module>spring-cloud-cli</module> --> <!-- Not a maven project -->
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-spring-6</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../../parent-spring-6</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>api-gateway</artifactId>
|
||||
<name>api-gateway</name>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<source.encoding>UTF-8</source.encoding>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<logback.version>1.4.14</logback.version>
|
||||
<slf4j.version>2.0.9</slf4j.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
|
||||
<scope>test</scope>
|
||||
</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>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<!-- Import dependency management from Spring Boot -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,70 @@
|
|||
package com.baeldung.errorhandling;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
|
||||
import org.springframework.boot.web.error.ErrorAttributeOptions;
|
||||
import org.springframework.boot.web.reactive.error.ErrorAttributes;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Order(Integer.MIN_VALUE)
|
||||
class CustomGlobalExceptionHandler extends AbstractErrorWebExceptionHandler {
|
||||
|
||||
public CustomGlobalExceptionHandler(final ErrorAttributes errorAttributes,
|
||||
final WebProperties.Resources resources,
|
||||
final ApplicationContext applicationContext,
|
||||
final ServerCodecConfigurer configurer) {
|
||||
super(errorAttributes, resources, applicationContext);
|
||||
setMessageReaders(configurer.getReaders());
|
||||
setMessageWriters(configurer.getWriters());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
|
||||
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
|
||||
|
||||
ErrorAttributeOptions options = ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE);
|
||||
Map<String, Object> errorPropertiesMap = getErrorAttributes(request, options);
|
||||
Throwable throwable = getError(request);
|
||||
HttpStatusCode httpStatus = determineHttpStatus(throwable);
|
||||
|
||||
errorPropertiesMap.put("status", httpStatus.value());
|
||||
errorPropertiesMap.remove("error");
|
||||
|
||||
return ServerResponse.status(httpStatus)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.body(BodyInserters.fromObject(errorPropertiesMap));
|
||||
}
|
||||
|
||||
private HttpStatusCode determineHttpStatus(Throwable throwable) {
|
||||
|
||||
if (throwable instanceof ResponseStatusException) {
|
||||
return ((ResponseStatusException) throwable).getStatusCode();
|
||||
} else if (throwable instanceof CustomRequestAuthException) {
|
||||
return HttpStatus.UNAUTHORIZED;
|
||||
} else if (throwable instanceof RateLimitRequestException) {
|
||||
return HttpStatus.TOO_MANY_REQUESTS;
|
||||
} else {
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.baeldung.errorhandling;
|
||||
|
||||
public class CustomRequestAuthException extends RuntimeException {
|
||||
public CustomRequestAuthException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package com.baeldung.errorhandling;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
|
||||
@Bean
|
||||
public WebProperties.Resources resources() {
|
||||
return new WebProperties.Resources();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouteLocator customRouteLocator(RouteLocatorBuilder builder,
|
||||
@Value("${httpbin}") String httpbin,
|
||||
MyCustomFilter myCustomFilter) {
|
||||
return builder.routes()
|
||||
.route("error_404", r -> r.path("/test/error_404")
|
||||
.filters(f -> f.filter(myCustomFilter).filter((exchange, chain) -> switchRequestUri(exchange, chain, "error_404", "404")))
|
||||
.uri(httpbin + "/status/404"))
|
||||
.route("error_500", r -> r.path("/test/error_500")
|
||||
.filters(f -> f.filter(myCustomFilter).filter((exchange, chain) -> switchRequestUri(exchange, chain, "error_500", "500")))
|
||||
.uri(httpbin+"/status/500"))
|
||||
.route("error_400", r -> r.path("/test/error_400")
|
||||
.filters(f -> f.filter(myCustomFilter).filter((exchange, chain) -> switchRequestUri(exchange, chain, "error_400", "400")))
|
||||
.uri(httpbin+"/status/400"))
|
||||
.route("error_409", r -> r.path("/test/error_409")
|
||||
.filters(f -> f.filter(myCustomFilter).filter((exchange, chain) -> switchRequestUri(exchange, chain, "error_409", "409")))
|
||||
.uri(httpbin+"/status/409"))
|
||||
.route("custom_rate_limit", r -> r.path("/test/custom_rate_limit").filters(f -> f.filter(myCustomFilter)).uri(httpbin+"/uuid"))
|
||||
.route("custom_auth", r -> r.path("/test/custom_auth").filters(f -> f.filter(myCustomFilter)).uri(httpbin+"/api/custom_auth"))
|
||||
.route("anything", r -> r.path("/test/anything")
|
||||
.filters(f -> f.changeRequestUri((exchange) -> Optional.of(UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
|
||||
.host("httpbin.org")
|
||||
.port(80)
|
||||
.replacePath("/anything").build().toUri())))
|
||||
.uri("http://httpbin.org"))
|
||||
.build();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private Mono<Void> switchRequestUri(ServerWebExchange exchange,
|
||||
GatewayFilterChain chain,
|
||||
String externalUri,
|
||||
String internalUri) {
|
||||
ServerHttpRequest req = exchange.getRequest();
|
||||
addOriginalRequestUrl(exchange, req.getURI());
|
||||
String path = req.getURI().getRawPath();
|
||||
String newPath = path.replaceAll("/test/" + externalUri, "/status/" + internalUri);
|
||||
ServerHttpRequest request = req.mutate().path(newPath).build();
|
||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());
|
||||
return chain.filter(exchange.mutate().request(request).build());
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Main.class, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.baeldung.errorhandling;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class MyCustomFilter implements GatewayFilter {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
|
||||
if (isAuthRoute(exchange) && !isAuthorization(exchange)) {
|
||||
throw new CustomRequestAuthException("Not authorized");
|
||||
}
|
||||
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
private static boolean isAuthorization(ServerWebExchange exchange) {
|
||||
return exchange.getRequest().getHeaders().containsKey("Authorization");
|
||||
}
|
||||
|
||||
private static boolean isAuthRoute(ServerWebExchange exchange) {
|
||||
return exchange.getRequest().getURI().getPath().equals("/test/custom_auth");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.baeldung.errorhandling;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
class MyGlobalFilter implements GlobalFilter {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
|
||||
if (hasReachedRateLimit(exchange)) {
|
||||
throw new RateLimitRequestException("Too many requests");
|
||||
}
|
||||
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
private boolean hasReachedRateLimit(ServerWebExchange exchange) {
|
||||
// Simulates the rate limit being reached
|
||||
return exchange.getRequest().getURI().getPath().contains("/test/custom_rate_limit") && (!exchange.getRequest().getHeaders().containsKey("X-RateLimit-Remaining") ||
|
||||
Integer.parseInt(exchange.getRequest().getHeaders().getFirst("X-RateLimit-Remaining")) <= 0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.baeldung.errorhandling;
|
||||
|
||||
public class RateLimitRequestException extends RuntimeException {
|
||||
public RateLimitRequestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
spring.application.name: api-gateway
|
||||
spring.main.web-application-type=reactive
|
||||
eureka.instance.hostname=localhost
|
||||
spring.cloud.discovery.enabled=true
|
||||
#eureka.instance.prefer-ip-address=true
|
||||
httpbin=http://httpbin.org
|
||||
|
||||
#### DEBUG ####
|
||||
#logging.level.org.springframework.cloud.gateway=DEBUG
|
||||
#spring.cloud.gateway.httpclient.wiretap=true
|
||||
#spring.cloud.gateway.httpserver.wiretap=true
|
||||
#logging.level.reactor.netty=DEBUG
|
|
@ -0,0 +1,13 @@
|
|||
<configuration>
|
||||
|
||||
<appender name="out" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %kvp%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="out"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
|
@ -0,0 +1,174 @@
|
|||
package com.baeldung.errorhandling;
|
||||
|
||||
import com.github.tomakehurst.wiremock.http.Body;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.post;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
|
||||
|
||||
@SpringBootTest(
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
properties = {"httpbin=http://localhost:${wiremock.server.port}/api"}
|
||||
)
|
||||
@AutoConfigureWireMock(port = 0)
|
||||
class RouteUnitTest {
|
||||
|
||||
@Value("${httpbin}")
|
||||
private String httpbin;
|
||||
|
||||
@Value(value="${local.server.port}")
|
||||
private int port;
|
||||
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
webTestClient = WebTestClient
|
||||
.bindToServer()
|
||||
.baseUrl("http://localhost:" + port + "/test")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenRouteResponseStatusCodeIs404_thenApiGatewayShouldHandleResponse() {
|
||||
stubFor(post(urlEqualTo("/status/404"))
|
||||
.willReturn(
|
||||
aResponse()
|
||||
.withStatus(404)
|
||||
.withResponseBody(Body.fromJsonBytes("{\"code\": 404, \"reason\": \"Not Found\"}".getBytes(StandardCharsets.UTF_8)))
|
||||
));
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/error_404")
|
||||
.accept(MediaType.valueOf("application/json"))
|
||||
.contentType(MediaType.valueOf("application/json"))
|
||||
.exchange()
|
||||
.expectStatus().isNotFound()
|
||||
.expectBody()
|
||||
.jsonPath("$.code").isEqualTo(404)
|
||||
.jsonPath("$.reason").isEqualTo("Not Found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenRouteResponseStatusCodeIs400_thenApiGatewayShouldHandleResponse() {
|
||||
stubFor(post(urlEqualTo("/status/400"))
|
||||
.willReturn(
|
||||
aResponse()
|
||||
.withStatus(400)
|
||||
.withResponseBody(Body.fromJsonBytes("{\"code\": 400, \"reason\": \"Bad Request\"}".getBytes(StandardCharsets.UTF_8)))
|
||||
));
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/error_400")
|
||||
.accept(MediaType.valueOf("application/json"))
|
||||
.contentType(MediaType.valueOf("application/json"))
|
||||
.exchange()
|
||||
.expectStatus().isBadRequest()
|
||||
.expectBody()
|
||||
.jsonPath("$.code").isEqualTo(400)
|
||||
.jsonPath("$.reason").isEqualTo("Bad Request");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenRouteResponseStatusCodeIs409_thenApiGatewayShouldHandleResponse() {
|
||||
stubFor(post(urlEqualTo("/status/409"))
|
||||
.willReturn(
|
||||
aResponse()
|
||||
.withStatus(409)
|
||||
.withResponseBody(Body.fromJsonBytes("{\"code\": 409, \"reason\": \"Conflict\"}".getBytes(StandardCharsets.UTF_8)))
|
||||
));
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/error_409")
|
||||
.accept(MediaType.valueOf("application/json"))
|
||||
.contentType(MediaType.valueOf("application/json"))
|
||||
.exchange()
|
||||
.expectStatus().isEqualTo(409)
|
||||
.expectBody()
|
||||
.jsonPath("$.code").isEqualTo(409)
|
||||
.jsonPath("$.reason").isEqualTo("Conflict");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenRouteResponseStatusCodeIs500_thenApiGatewayShouldHandleResponse() {
|
||||
stubFor(post(urlEqualTo("/status/500"))
|
||||
.willReturn(
|
||||
aResponse()
|
||||
.withStatus(500)
|
||||
.withResponseBody(Body.fromJsonBytes("{\"code\": 500, \"reason\": \"Error\"}".getBytes(StandardCharsets.UTF_8)))
|
||||
));
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/error_500")
|
||||
.accept(MediaType.valueOf("application/json"))
|
||||
.contentType(MediaType.valueOf("application/json"))
|
||||
.exchange()
|
||||
.expectStatus().isEqualTo(500)
|
||||
.expectBody()
|
||||
.jsonPath("$.code").isEqualTo(500)
|
||||
.jsonPath("$.reason").isEqualTo("Error");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenRouteAnythingRequest_thenApiGatewayShouldHandleResponse() {
|
||||
webTestClient.post()
|
||||
.uri("/anything")
|
||||
.contentType(MediaType.valueOf("application/json"))
|
||||
.body(BodyInserters.fromValue("{\"code\": 500, \"reason\": \"Error\"}".getBytes(StandardCharsets.UTF_8)))
|
||||
.accept(MediaType.valueOf("application/json"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.jsonPath("$.data").isEqualTo("{\"code\": 500, \"reason\": \"Error\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenRouteCustomRequestAuth_thenApiGatewayShouldHandleResponse() {
|
||||
webTestClient.post()
|
||||
.uri("/custom_auth")
|
||||
.contentType(MediaType.valueOf("application/json"))
|
||||
.body(BodyInserters.fromValue("{\"test\": \"test\", \"message\": \"test\"}".getBytes(StandardCharsets.UTF_8)))
|
||||
.accept(MediaType.valueOf("application/json"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isUnauthorized()
|
||||
.expectBody()
|
||||
.jsonPath("$.path").isNotEmpty()
|
||||
.jsonPath("$.message").isEqualTo("Not authorized")
|
||||
.jsonPath("$.status").isEqualTo(401)
|
||||
.jsonPath("$.requestId").isNotEmpty()
|
||||
.jsonPath("$.timestamp").isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenRouteCustomRequestRateLimit_thenApiGatewayShouldHandleResponse() {
|
||||
webTestClient.post()
|
||||
.uri("/custom_rate_limit")
|
||||
.contentType(MediaType.valueOf("application/json"))
|
||||
.body(BodyInserters.fromValue("{\"test\": \"test\", \"message\": \"test\"}".getBytes(StandardCharsets.UTF_8)))
|
||||
.accept(MediaType.valueOf("application/json"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isEqualTo(429)
|
||||
.expectBody()
|
||||
.jsonPath("$.path").isNotEmpty()
|
||||
.jsonPath("$.message").isEqualTo("Too many requests")
|
||||
.jsonPath("$.status").isEqualTo(429)
|
||||
.jsonPath("$.requestId").isNotEmpty()
|
||||
.jsonPath("$.timestamp").isNotEmpty();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-spring-6</artifactId>
|
||||
<relativePath>../parent-spring-6</relativePath>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>spring-6</artifactId>
|
||||
<name>spring-6</name>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>api-gateway</module>
|
||||
</modules>
|
||||
</project>
|
Loading…
Reference in New Issue