From 6aacfe37c4a8e16d258e8ad0be499b95527410d8 Mon Sep 17 00:00:00 2001 From: Cavero Barca Date: Tue, 21 Apr 2020 15:29:47 +0200 Subject: [PATCH 1/4] Add WebFilter Factories examples configured through yaml and Java DSL --- spring-cloud/spring-cloud-gateway/pom.xml | 12 ++- .../WebFilterGatewayApplication.java | 15 +++ .../config/ModifyBodyRouteConfig.java | 49 ++++++++++ .../main/resources/application-webfilters.yml | 93 +++++++++++++++++++ 4 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java create mode 100644 spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/ModifyBodyRouteConfig.java create mode 100644 spring-cloud/spring-cloud-gateway/src/main/resources/application-webfilters.yml diff --git a/spring-cloud/spring-cloud-gateway/pom.xml b/spring-cloud/spring-cloud-gateway/pom.xml index 0f62c031cf..ada32ae85f 100644 --- a/spring-cloud/spring-cloud-gateway/pom.xml +++ b/spring-cloud/spring-cloud-gateway/pom.xml @@ -49,6 +49,12 @@ spring-cloud-starter-gateway + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-reactor-resilience4j + + org.hibernate hibernate-validator-cdi @@ -84,10 +90,8 @@ - Greenwich.SR3 - - - 2.1.9.RELEASE + Hoxton.SR3 + 2.2.6.RELEASE 6.0.2.Final diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java new file mode 100644 index 0000000000..3b45e9013f --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java @@ -0,0 +1,15 @@ +package com.baeldung.springcloudgateway.webfilters; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; + +@SpringBootApplication +public class WebFilterGatewayApplication { + + public static void main(String[] args) { + new SpringApplicationBuilder(WebFilterGatewayApplication.class) + .profiles("webfilters") + .run(args); + } + +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/ModifyBodyRouteConfig.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/ModifyBodyRouteConfig.java new file mode 100644 index 0000000000..ccc789f287 --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/ModifyBodyRouteConfig.java @@ -0,0 +1,49 @@ +package com.baeldung.springcloudgateway.webfilters.config; + +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; + +import reactor.core.publisher.Mono; + +@Configuration +public class ModifyBodyRouteConfig { + + @Bean + public RouteLocator routes(RouteLocatorBuilder builder) { + return builder.routes() + .route("modify_request_body", r -> r.path("/post") + .filters(f -> f.modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, + (exchange, s) -> Mono.just(new Hello(s.toUpperCase())))).uri("https://httpbin.org")) + .build(); + } + + @Bean + public RouteLocator responseRoutes(RouteLocatorBuilder builder) { + return builder.routes() + .route("modify_response_body", r -> r.path("/put/**") + .filters(f -> f.modifyResponseBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, + (exchange, s) -> Mono.just(new Hello("New Body")))).uri("https://httpbin.org")) + .build(); + } + + static class Hello { + String message; + + public Hello() { } + + public Hello(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } +} diff --git a/spring-cloud/spring-cloud-gateway/src/main/resources/application-webfilters.yml b/spring-cloud/spring-cloud-gateway/src/main/resources/application-webfilters.yml new file mode 100644 index 0000000000..9b3ec64f96 --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/main/resources/application-webfilters.yml @@ -0,0 +1,93 @@ +spring: + cloud: + gateway: + routes: + - id: request_header_route + uri: https://httpbin.org + predicates: + - Path=/get/** + filters: + - AddRequestHeader=My-Header-Good,Good + - AddRequestHeader=My-Header-Remove,Remove + - AddRequestParameter=var, good + - AddRequestParameter=var2, remove + - MapRequestHeader=My-Header-Good, My-Header-Bad + - MapRequestHeader=My-Header-Set, My-Header-Bad + - SetRequestHeader=My-Header-Set, Set + - RemoveRequestHeader=My-Header-Remove + - RemoveRequestParameter=var2 + - PreserveHostHeader + + - id: response_header_route + uri: https://httpbin.org + predicates: + - Path=/header/post/** + filters: + - AddResponseHeader=My-Header-Good,Good + - AddResponseHeader=My-Header-Set,Good + - AddResponseHeader=My-Header-Rewrite, password=12345678 + - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin + - AddResponseHeader=My-Header-Remove,Remove + - SetResponseHeader=My-Header-Set, Set + - RemoveResponseHeader=My-Header-Remove + - RewriteResponseHeader=My-Header-Rewrite, password=[^&]+, password=*** + - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, , + - StripPrefix=1 + + - id: path_route + uri: https://httpbin.org + predicates: + - Path=/new/post/** + filters: + - RewritePath=/new(?/?.*), $\{segment} + - SetPath=/post + + - id: redirect_route + uri: https://httpbin.org + predicates: + - Path=/fake/post/** + filters: + - RedirectTo=302, https://httpbin.org + + - id: status_route + uri: https://httpbin.org + predicates: + - Path=/delete/** + filters: + - SetStatus=401 + + - id: size_route + uri: https://httpbin.org + predicates: + - Path=/anything + filters: + - name: RequestSize + args: + maxSize: 5000000 + + - id: retry_test + uri: https://httpbin.org + predicates: + - Path=/status/502 + filters: + - name: Retry + args: + retries: 3 + statuses: BAD_GATEWAY + methods: GET,POST + backoff: + firstBackoff: 10ms + maxBackoff: 50ms + factor: 2 + basedOnPreviousValue: false + + - id: circuitbreaker_route + uri: https://httpbin.org + predicates: + - Path=/status/504 + filters: + - name: CircuitBreaker + args: + name: myCircuitBreaker + fallbackUri: forward:/anything + - RewritePath=/status/504, /anything \ No newline at end of file From 84d65a91b848f7f154aa1ea94d7d7bfc405af0ff Mon Sep 17 00:00:00 2001 From: Cavero Barca Date: Tue, 21 Apr 2020 15:33:39 +0200 Subject: [PATCH 2/4] Add testing to the WebFilter Factories Spring Boot project --- .../WebFilterFactoriesLiveTest.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/WebFilterFactoriesLiveTest.java diff --git a/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/WebFilterFactoriesLiveTest.java b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/WebFilterFactoriesLiveTest.java new file mode 100644 index 0000000000..3efd60fb27 --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/WebFilterFactoriesLiveTest.java @@ -0,0 +1,139 @@ +package com.baeldung.springcloudgateway.webfilters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.assertj.core.api.Condition; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@ActiveProfiles("webfilters") +public class WebFilterFactoriesLiveTest { + + @LocalServerPort + String port; + + @Autowired + private WebTestClient client; + + @Autowired + private TestRestTemplate restTemplate; + + @BeforeEach + public void configureClient() { + client = WebTestClient.bindToServer() + .baseUrl("http://localhost:" + port) + .build(); + } + + @Test + public void whenCallGetThroughGateway_thenAllHTTPRequestHeadersParametersAreSet() throws JSONException { + String url = "http://localhost:" + port + "/get"; + ResponseEntity response = restTemplate.getForEntity(url, String.class); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + JSONObject json = new JSONObject(response.getBody()); + JSONObject headers = json.getJSONObject("headers"); + assertThat(headers.getString("My-Header-Good")).isEqualTo("Good"); + assertThat(headers.getString("My-Header-Bad")).isEqualTo("Good"); + assertThat(headers.getString("My-Header-Set")).isEqualTo("Set"); + assertTrue(headers.isNull("My-Header-Remove")); + JSONObject vars = json.getJSONObject("args"); + assertThat(vars.getString("var")).isEqualTo("good"); + } + + @Test + public void whenCallHeaderPostThroughGateway_thenAllHTTPResponseHeadersAreSet() { + ResponseSpec response = client.post() + .uri("/header/post") + .exchange(); + + response.expectStatus() + .isOk() + .expectHeader() + .valueEquals("My-Header-Rewrite", "password=***") + .expectHeader() + .valueEquals("My-Header-Set", "Set") + .expectHeader() + .valueEquals("My-Header-Good", "Good") + .expectHeader() + .doesNotExist("My-Header-Remove"); + } + + @Test + public void whenCallPostThroughGateway_thenBodyIsRetrieved() throws JSONException { + String url = "http://localhost:" + port + "/post"; + + HttpEntity entity = new HttpEntity<>("content", new HttpHeaders()); + + ResponseEntity response = restTemplate.exchange(url, + HttpMethod.POST, entity, String.class); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + JSONObject json = new JSONObject(response.getBody()); + JSONObject data = json.getJSONObject("json"); + assertThat(data.getString("message")).isEqualTo("CONTENT"); + } + + + @Test + public void whenCallPutThroughGateway_thenBodyIsRetrieved() throws JSONException { + String url = "http://localhost:" + port + "/put"; + + HttpEntity entity = new HttpEntity<>("CONTENT", new HttpHeaders()); + + ResponseEntity response = restTemplate.exchange(url, + HttpMethod.PUT, entity, String.class); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + JSONObject json = new JSONObject(response.getBody()); + assertThat(json.getString("message")).isEqualTo("New Body"); + } + + @Test + public void whenCallDeleteThroughGateway_thenIsUnauthorizedCodeIsSet() { + ResponseSpec response = client.delete() + .uri("/delete") + .exchange(); + + response.expectStatus() + .isUnauthorized(); + } + + @Test + public void whenCallFakePostThroughGateway_thenIsUnauthorizedCodeIsSet() { + ResponseSpec response = client.post() + .uri("/fake/post") + .exchange(); + + response.expectStatus() + .is3xxRedirection(); + } + + @Test + public void whenCallStatus504ThroughGateway_thenCircuitBreakerIsExecuted() throws JSONException { + String url = "http://localhost:" + port + "/status/504"; + ResponseEntity response = restTemplate.getForEntity(url, String.class); + + JSONObject json = new JSONObject(response.getBody()); + assertThat(json.getString("url")).contains("anything"); + } +} From d202cc973843d384fdd56507cdac6e612722f04b Mon Sep 17 00:00:00 2001 From: Cavero Barca Date: Sat, 25 Apr 2020 01:49:20 +0200 Subject: [PATCH 3/4] Use a two-space indent when continuing a line --- .../WebFilterGatewayApplication.java | 4 +- .../config/ModifyBodyRouteConfig.java | 16 ++--- .../WebFilterFactoriesLiveTest.java | 69 +++++++++---------- 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java index 3b45e9013f..852e5cadba 100644 --- a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java +++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java @@ -8,8 +8,8 @@ public class WebFilterGatewayApplication { public static void main(String[] args) { new SpringApplicationBuilder(WebFilterGatewayApplication.class) - .profiles("webfilters") - .run(args); + .profiles("webfilters") + .run(args); } } \ No newline at end of file diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/ModifyBodyRouteConfig.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/ModifyBodyRouteConfig.java index ccc789f287..7b6188b66a 100644 --- a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/ModifyBodyRouteConfig.java +++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/ModifyBodyRouteConfig.java @@ -14,19 +14,19 @@ public class ModifyBodyRouteConfig { @Bean public RouteLocator routes(RouteLocatorBuilder builder) { return builder.routes() - .route("modify_request_body", r -> r.path("/post") - .filters(f -> f.modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, - (exchange, s) -> Mono.just(new Hello(s.toUpperCase())))).uri("https://httpbin.org")) - .build(); + .route("modify_request_body", r -> r.path("/post") + .filters(f -> f.modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, + (exchange, s) -> Mono.just(new Hello(s.toUpperCase())))).uri("https://httpbin.org")) + .build(); } @Bean public RouteLocator responseRoutes(RouteLocatorBuilder builder) { return builder.routes() - .route("modify_response_body", r -> r.path("/put/**") - .filters(f -> f.modifyResponseBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, - (exchange, s) -> Mono.just(new Hello("New Body")))).uri("https://httpbin.org")) - .build(); + .route("modify_response_body", r -> r.path("/put/**") + .filters(f -> f.modifyResponseBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, + (exchange, s) -> Mono.just(new Hello("New Body")))).uri("https://httpbin.org")) + .build(); } static class Hello { diff --git a/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/WebFilterFactoriesLiveTest.java b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/WebFilterFactoriesLiveTest.java index 3efd60fb27..67e00a42fc 100644 --- a/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/WebFilterFactoriesLiveTest.java +++ b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/WebFilterFactoriesLiveTest.java @@ -33,23 +33,23 @@ public class WebFilterFactoriesLiveTest { @Autowired private WebTestClient client; - + @Autowired private TestRestTemplate restTemplate; @BeforeEach public void configureClient() { client = WebTestClient.bindToServer() - .baseUrl("http://localhost:" + port) - .build(); + .baseUrl("http://localhost:" + port) + .build(); } @Test public void whenCallGetThroughGateway_thenAllHTTPRequestHeadersParametersAreSet() throws JSONException { - String url = "http://localhost:" + port + "/get"; + String url = "http://localhost:" + port + "/get"; ResponseEntity response = restTemplate.getForEntity(url, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - + JSONObject json = new JSONObject(response.getBody()); JSONObject headers = json.getJSONObject("headers"); assertThat(headers.getString("My-Header-Good")).isEqualTo("Good"); @@ -63,76 +63,73 @@ public class WebFilterFactoriesLiveTest { @Test public void whenCallHeaderPostThroughGateway_thenAllHTTPResponseHeadersAreSet() { ResponseSpec response = client.post() - .uri("/header/post") - .exchange(); + .uri("/header/post") + .exchange(); response.expectStatus() - .isOk() - .expectHeader() - .valueEquals("My-Header-Rewrite", "password=***") - .expectHeader() - .valueEquals("My-Header-Set", "Set") - .expectHeader() - .valueEquals("My-Header-Good", "Good") - .expectHeader() - .doesNotExist("My-Header-Remove"); + .isOk() + .expectHeader() + .valueEquals("My-Header-Rewrite", "password=***") + .expectHeader() + .valueEquals("My-Header-Set", "Set") + .expectHeader() + .valueEquals("My-Header-Good", "Good") + .expectHeader() + .doesNotExist("My-Header-Remove"); } @Test public void whenCallPostThroughGateway_thenBodyIsRetrieved() throws JSONException { - String url = "http://localhost:" + port + "/post"; + String url = "http://localhost:" + port + "/post"; HttpEntity entity = new HttpEntity<>("content", new HttpHeaders()); - - ResponseEntity response = restTemplate.exchange(url, - HttpMethod.POST, entity, String.class); + + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - + JSONObject json = new JSONObject(response.getBody()); JSONObject data = json.getJSONObject("json"); assertThat(data.getString("message")).isEqualTo("CONTENT"); } - @Test public void whenCallPutThroughGateway_thenBodyIsRetrieved() throws JSONException { - String url = "http://localhost:" + port + "/put"; + String url = "http://localhost:" + port + "/put"; HttpEntity entity = new HttpEntity<>("CONTENT", new HttpHeaders()); - - ResponseEntity response = restTemplate.exchange(url, - HttpMethod.PUT, entity, String.class); + + ResponseEntity response = restTemplate.exchange(url, HttpMethod.PUT, entity, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - + JSONObject json = new JSONObject(response.getBody()); assertThat(json.getString("message")).isEqualTo("New Body"); } - + @Test public void whenCallDeleteThroughGateway_thenIsUnauthorizedCodeIsSet() { ResponseSpec response = client.delete() - .uri("/delete") - .exchange(); + .uri("/delete") + .exchange(); response.expectStatus() - .isUnauthorized(); + .isUnauthorized(); } @Test public void whenCallFakePostThroughGateway_thenIsUnauthorizedCodeIsSet() { ResponseSpec response = client.post() - .uri("/fake/post") - .exchange(); + .uri("/fake/post") + .exchange(); response.expectStatus() - .is3xxRedirection(); + .is3xxRedirection(); } @Test public void whenCallStatus504ThroughGateway_thenCircuitBreakerIsExecuted() throws JSONException { - String url = "http://localhost:" + port + "/status/504"; + String url = "http://localhost:" + port + "/status/504"; ResponseEntity response = restTemplate.getForEntity(url, String.class); - + JSONObject json = new JSONObject(response.getBody()); assertThat(json.getString("url")).contains("anything"); } From d900a5c185402f73a87b1c6b23e604107a29a4cc Mon Sep 17 00:00:00 2001 From: Cavero Barca Date: Mon, 27 Apr 2020 01:22:33 +0200 Subject: [PATCH 4/4] Add RedisRateLimiter to webfilter package --- spring-cloud/spring-cloud-gateway/pom.xml | 25 ++++++-- .../RequestRateLimiterResolverConfig.java | 16 +++++ .../main/resources/application-webfilters.yml | 21 ++++-- .../RedisWebFilterFactoriesLiveTest.java | 64 +++++++++++++++++++ .../src/test/resources/logback-test.xml | 8 +++ 5 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/RequestRateLimiterResolverConfig.java create mode 100644 spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/RedisWebFilterFactoriesLiveTest.java diff --git a/spring-cloud/spring-cloud-gateway/pom.xml b/spring-cloud/spring-cloud-gateway/pom.xml index ada32ae85f..c692eed7ec 100644 --- a/spring-cloud/spring-cloud-gateway/pom.xml +++ b/spring-cloud/spring-cloud-gateway/pom.xml @@ -1,7 +1,7 @@ + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 spring-cloud-gateway spring-cloud-gateway @@ -54,7 +54,21 @@ org.springframework.cloud spring-cloud-starter-circuitbreaker-reactor-resilience4j - + + + + org.springframework.boot + spring-boot-starter-data-redis-reactive + + + + + it.ozimov + embedded-redis + ${redis.version} + test + + org.hibernate hibernate-validator-cdi @@ -75,8 +89,8 @@ test - org.springframework.boot - spring-boot-devtools + org.springframework.boot + spring-boot-devtools @@ -93,6 +107,7 @@ Hoxton.SR3 2.2.6.RELEASE 6.0.2.Final + 0.7.2 diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/RequestRateLimiterResolverConfig.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/RequestRateLimiterResolverConfig.java new file mode 100644 index 0000000000..f80a742fa6 --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/config/RequestRateLimiterResolverConfig.java @@ -0,0 +1,16 @@ +package com.baeldung.springcloudgateway.webfilters.config; + +import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import reactor.core.publisher.Mono; + +@Configuration +public class RequestRateLimiterResolverConfig { + + @Bean + KeyResolver userKeyResolver() { + return exchange -> Mono.just("1"); + } +} diff --git a/spring-cloud/spring-cloud-gateway/src/main/resources/application-webfilters.yml b/spring-cloud/spring-cloud-gateway/src/main/resources/application-webfilters.yml index 9b3ec64f96..3348cbbba0 100644 --- a/spring-cloud/spring-cloud-gateway/src/main/resources/application-webfilters.yml +++ b/spring-cloud/spring-cloud-gateway/src/main/resources/application-webfilters.yml @@ -1,4 +1,12 @@ +logging: + level: + org.springframework.cloud.gateway: INFO + reactor.netty.http.client: INFO + spring: + redis: + host: localhost + port: 6379 cloud: gateway: routes: @@ -81,13 +89,14 @@ spring: factor: 2 basedOnPreviousValue: false - - id: circuitbreaker_route + - id: request_rate_limiter uri: https://httpbin.org predicates: - - Path=/status/504 + - Path=/redis/get/** filters: - - name: CircuitBreaker + - StripPrefix=1 + - name: RequestRateLimiter args: - name: myCircuitBreaker - fallbackUri: forward:/anything - - RewritePath=/status/504, /anything \ No newline at end of file + redis-rate-limiter.replenishRate: 10 + redis-rate-limiter.burstCapacity: 5 + key-resolver: "#{@userKeyResolver}" \ No newline at end of file diff --git a/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/RedisWebFilterFactoriesLiveTest.java b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/RedisWebFilterFactoriesLiveTest.java new file mode 100644 index 0000000000..a28eb68775 --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/webfilters/RedisWebFilterFactoriesLiveTest.java @@ -0,0 +1,64 @@ +package com.baeldung.springcloudgateway.webfilters; + +import org.junit.After; +import org.junit.Before; +import org.junit.jupiter.api.RepeatedTest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; + +import redis.embedded.RedisServer; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@ActiveProfiles("webfilters") +@TestConfiguration +public class RedisWebFilterFactoriesLiveTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(RedisWebFilterFactoriesLiveTest.class); + + private RedisServer redisServer; + + public RedisWebFilterFactoriesLiveTest() { + } + + @Before + public void postConstruct() { + this.redisServer = new RedisServer(6379); + redisServer.start(); + } + + @LocalServerPort + String port; + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + TestRestTemplate template; + + @RepeatedTest(25) + public void whenCallRedisGetThroughGateway_thenOKStatusOrIsReceived() { + String url = "http://localhost:" + port + "/redis/get"; + + ResponseEntity r = restTemplate.getForEntity(url, String.class); + // assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + LOGGER.info("Received: status->{}, reason->{}, remaining->{}", + r.getStatusCodeValue(), r.getStatusCode().getReasonPhrase(), + r.getHeaders().get("X-RateLimit-Remaining")); + } + + @After + public void preDestroy() { + redisServer.stop(); + } + +} diff --git a/spring-cloud/spring-cloud-gateway/src/test/resources/logback-test.xml b/spring-cloud/spring-cloud-gateway/src/test/resources/logback-test.xml index 6fcdb6317f..6980d119b1 100644 --- a/spring-cloud/spring-cloud-gateway/src/test/resources/logback-test.xml +++ b/spring-cloud/spring-cloud-gateway/src/test/resources/logback-test.xml @@ -3,7 +3,15 @@ + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + \ No newline at end of file