diff --git a/pom.xml b/pom.xml index 2b6bfe12..5c5990fe 100644 --- a/pom.xml +++ b/pom.xml @@ -166,10 +166,8 @@ ${basedir}/target/coverage-reports/jacoco-unit.exec ${basedir}/target/coverage-reports/jacoco-unit.exec - **/properties/** - **/*Configuration.* - **/*Application.* - **/*Rate.* + **/test/** + **/tests/** diff --git a/spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/RateLimitPostFilter.java b/spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/RateLimitPostFilter.java index 73c10ead..a5964bfc 100644 --- a/spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/RateLimitPostFilter.java +++ b/spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/RateLimitPostFilter.java @@ -16,16 +16,13 @@ package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.filters; -import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST; -import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.Rate; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitKeyGenerator; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimiter; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties; import com.netflix.zuul.context.RequestContext; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.RouteLocator; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; @@ -75,7 +72,6 @@ private Long getRequestStartTime() { public Object run() { final RequestContext ctx = RequestContext.getCurrentContext(); - final HttpServletResponse response = ctx.getResponse(); final HttpServletRequest request = ctx.getRequest(); final Route route = route(); @@ -83,15 +79,7 @@ public Object run() { final Long requestTime = System.currentTimeMillis() - getRequestStartTime(); final String key = rateLimitKeyGenerator.key(request, route, policy); - final Rate rate = rateLimiter.consume(policy, key, requestTime); - - final Long quota = policy.getQuota(); - final Long remainingQuota = rate.getRemainingQuota(); - if (quota != null) { - RequestContextHolder.getRequestAttributes().setAttribute(REQUEST_START_TIME, System.currentTimeMillis(), SCOPE_REQUEST); - response.setHeader(QUOTA_HEADER, String.valueOf(quota)); - response.setHeader(REMAINING_QUOTA_HEADER, String.valueOf(MILLISECONDS.toSeconds(Math.max(remainingQuota, 0)))); - } + rateLimiter.consume(policy, key, requestTime); }); return null; diff --git a/spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/RateLimitPreFilter.java b/spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/RateLimitPreFilter.java index 85b4afc7..1211b5dc 100644 --- a/spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/RateLimitPreFilter.java +++ b/spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/RateLimitPreFilter.java @@ -16,6 +16,7 @@ package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.filters; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.Rate; @@ -85,6 +86,8 @@ public Object run() { final Long remainingQuota = rate.getRemainingQuota(); if (quota != null) { RequestContextHolder.getRequestAttributes().setAttribute(REQUEST_START_TIME, System.currentTimeMillis(), SCOPE_REQUEST); + response.setHeader(QUOTA_HEADER, String.valueOf(quota)); + response.setHeader(REMAINING_QUOTA_HEADER, String.valueOf(MILLISECONDS.toSeconds(Math.max(remainingQuota, 0)))); } response.setHeader(RESET_HEADER, String.valueOf(rate.getReset())); diff --git a/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/config/repository/ConsulRateLimiterTest.java b/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/config/repository/ConsulRateLimiterTest.java index 5e6c97c2..6dcbc414 100644 --- a/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/config/repository/ConsulRateLimiterTest.java +++ b/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/config/repository/ConsulRateLimiterTest.java @@ -1,23 +1,34 @@ package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.repository; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.ecwid.consul.v1.ConsulClient; import com.ecwid.consul.v1.Response; import com.ecwid.consul.v1.kv.model.GetValue; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Maps; +import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.Rate; +import java.io.IOException; import java.util.Base64; import java.util.Map; import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class ConsulRateLimiterTest extends BaseRateLimiterTest { @Mock private ConsulClient consulClient; + @Mock + private ObjectMapper objectMapper; @Before public void setUp() { @@ -39,4 +50,23 @@ public void setUp() { ObjectMapper objectMapper = new ObjectMapper(); target = new ConsulRateLimiter(consulClient, objectMapper); } + + @Test + public void testGetRateException() throws IOException { + when(objectMapper.readValue(anyString(), eq(Rate.class))).thenThrow(new IOException()); + ConsulRateLimiter consulRateLimiter = new ConsulRateLimiter(consulClient, objectMapper); + + Rate rate = consulRateLimiter.getRate(""); + assertThat(rate).isNull(); + } + + @Test + public void testSaveRateException() throws IOException { + JsonProcessingException jsonProcessingException = Mockito.mock(JsonProcessingException.class); + when(objectMapper.writeValueAsString(any(Rate.class))).thenThrow(jsonProcessingException); + ConsulRateLimiter consulRateLimiter = new ConsulRateLimiter(consulClient, objectMapper); + + consulRateLimiter.saveRate(null); + verifyZeroInteractions(consulClient); + } } \ No newline at end of file diff --git a/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/post/RateLimitPostFilterTest.java b/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/post/RateLimitPostFilterTest.java index 38c17fef..7e61d3ec 100644 --- a/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/post/RateLimitPostFilterTest.java +++ b/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/post/RateLimitPostFilterTest.java @@ -1,6 +1,11 @@ package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.filters.post; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST; @@ -87,10 +92,29 @@ public void testShouldFilterOnNullStartTime() { @Test public void testShouldFilter() { rateLimitProperties.setEnabled(true); - when(requestAttributes.getAttribute(RateLimitPreFilter.REQUEST_START_TIME, SCOPE_REQUEST)).thenReturn(5L); + when(requestAttributes.getAttribute(RateLimitPreFilter.REQUEST_START_TIME, SCOPE_REQUEST)).thenReturn(System.currentTimeMillis()); Policy defaultPolicy = new Policy(); rateLimitProperties.setDefaultPolicy(defaultPolicy); assertThat(target.shouldFilter()).isEqualTo(true); } + + @Test + public void testRunNoPolicy() { + target.run(); + verifyZeroInteractions(rateLimiter); + } + + @Test + public void testRun() { + rateLimitProperties.setEnabled(true); + when(requestAttributes.getAttribute(RateLimitPreFilter.REQUEST_START_TIME, SCOPE_REQUEST)).thenReturn(System.currentTimeMillis()); + Policy defaultPolicy = new Policy(); + defaultPolicy.setQuota(2L); + rateLimitProperties.setDefaultPolicy(defaultPolicy); + when(rateLimitKeyGenerator.key(any(), any(), any())).thenReturn("generatedKey"); + + target.run(); + verify(rateLimiter).consume(eq(defaultPolicy), eq("generatedKey"), anyLong()); + } } \ No newline at end of file diff --git a/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/pre/BaseRateLimitPreFilterTest.java b/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/pre/BaseRateLimitPreFilterTest.java index 5bef7baf..b17ba406 100644 --- a/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/pre/BaseRateLimitPreFilterTest.java +++ b/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/pre/BaseRateLimitPreFilterTest.java @@ -21,11 +21,15 @@ import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.RouteLocator; import org.springframework.cloud.netflix.zuul.metrics.EmptyCounterFactory; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.util.UrlPathHelper; /** @@ -36,6 +40,9 @@ public abstract class BaseRateLimitPreFilterTest { RateLimitPreFilter filter; + @Mock + RequestAttributes requestAttributes; + MockHttpServletRequest request; MockHttpServletResponse response; @@ -56,6 +63,7 @@ private RateLimitProperties properties() { Policy policy = new Policy(); policy.setLimit(2L); + policy.setQuota(2L); policy.setRefreshInterval(2L); policy.setType(asList(Policy.Type.ORIGIN, Policy.Type.URL, Policy.Type.USER)); @@ -76,6 +84,7 @@ void setRateLimiter(RateLimiter rateLimiter) { @Before public void setUp() { + MockitoAnnotations.initMocks(this); CounterFactory.initialize(new EmptyCounterFactory()); this.request = new MockHttpServletRequest(); this.response = new MockHttpServletResponse(); @@ -84,6 +93,7 @@ public void setUp() { this.filter = new RateLimitPreFilter(this.properties(), this.routeLocator(), urlPathHelper, this.rateLimiter, this.rateLimitKeyGenerator); this.context = new RequestContext(); RequestContext.testSetCurrentContext(this.context); + RequestContextHolder.setRequestAttributes(requestAttributes); this.context.clear(); this.context.setRequest(this.request); this.context.setResponse(this.response); diff --git a/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/pre/ConsulRateLimitPreFilterTest.java b/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/pre/ConsulRateLimitPreFilterTest.java index 79ba8dbb..8e4decc3 100644 --- a/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/pre/ConsulRateLimitPreFilterTest.java +++ b/spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/pre/ConsulRateLimitPreFilterTest.java @@ -29,7 +29,7 @@ public class ConsulRateLimitPreFilterTest extends BaseRateLimitPreFilterTest { private ObjectMapper objectMapper = new ObjectMapper(); private Rate rate(long remaining) { - return new Rate("key", remaining, null, 100L, new Date(System.currentTimeMillis() + SECONDS.toMillis(2))); + return new Rate("key", remaining, 2000L, 100L, new Date(System.currentTimeMillis() + SECONDS.toMillis(2))); } @Before diff --git a/spring-cloud-zuul-ratelimit-tests/consul/src/main/java/com/marcosbarbero/tests/ConsulApplication.java b/spring-cloud-zuul-ratelimit-tests/consul/src/main/java/com/marcosbarbero/tests/ConsulApplication.java index 45973c4f..dcbcb378 100644 --- a/spring-cloud-zuul-ratelimit-tests/consul/src/main/java/com/marcosbarbero/tests/ConsulApplication.java +++ b/spring-cloud-zuul-ratelimit-tests/consul/src/main/java/com/marcosbarbero/tests/ConsulApplication.java @@ -12,7 +12,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** @@ -28,7 +27,6 @@ public static void main(String... args) { } @RestController - @RequestMapping("/services") public class ServiceController { public static final String RESPONSE_BODY = "ResponseBody"; @@ -52,6 +50,12 @@ public ResponseEntity serviceC() { public ResponseEntity serviceD(@PathVariable String paramName) { return ResponseEntity.ok(RESPONSE_BODY + " " + paramName); } + + @GetMapping("/serviceE") + public ResponseEntity serviceE() throws InterruptedException { + Thread.sleep(1100); + return ResponseEntity.ok(RESPONSE_BODY); + } } diff --git a/spring-cloud-zuul-ratelimit-tests/consul/src/main/resources/application.yml b/spring-cloud-zuul-ratelimit-tests/consul/src/main/resources/application.yml index 897774fb..ff542cdc 100644 --- a/spring-cloud-zuul-ratelimit-tests/consul/src/main/resources/application.yml +++ b/spring-cloud-zuul-ratelimit-tests/consul/src/main/resources/application.yml @@ -2,17 +2,20 @@ zuul: routes: serviceA: path: /serviceA - url: forward:/services + url: forward:/ serviceB: path: /serviceB - url: forward:/services + url: forward:/ serviceC: path: /serviceC - url: forward:/services + url: forward:/ serviceD: strip-prefix: false path: /serviceD/** - url: forward:/services + url: forward:/ + serviceE: + path: /serviceE + url: forward:/ ratelimit: enabled: true repository: CONSUL @@ -32,4 +35,9 @@ zuul: refresh-interval: 60 type: - url + serviceE: + quota: 1 + refresh-interval: 60 + type: + - origin strip-prefix: true diff --git a/spring-cloud-zuul-ratelimit-tests/consul/src/test/java/com/marcosbarbero/tests/it/ConsulApplicationTestIT.java b/spring-cloud-zuul-ratelimit-tests/consul/src/test/java/com/marcosbarbero/tests/it/ConsulApplicationTestIT.java index 4e6a73e1..e1a38c31 100644 --- a/spring-cloud-zuul-ratelimit-tests/consul/src/test/java/com/marcosbarbero/tests/it/ConsulApplicationTestIT.java +++ b/spring-cloud-zuul-ratelimit-tests/consul/src/test/java/com/marcosbarbero/tests/it/ConsulApplicationTestIT.java @@ -48,7 +48,7 @@ public void testConsulRateLimiter() { public void testNotExceedingCapacityRequest() { ResponseEntity response = this.restTemplate.getForEntity("/serviceA", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } @@ -56,7 +56,7 @@ public void testNotExceedingCapacityRequest() { public void testExceedingCapacity() throws InterruptedException { ResponseEntity response = this.restTemplate.getForEntity("/serviceB", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); for (int i = 0; i < 2; i++) { @@ -70,7 +70,7 @@ public void testExceedingCapacity() throws InterruptedException { response = this.restTemplate.getForEntity("/serviceB", String.class); headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } @@ -78,7 +78,7 @@ public void testExceedingCapacity() throws InterruptedException { public void testNoRateLimit() { ResponseEntity response = this.restTemplate.getForEntity("/serviceC", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, true); + assertHeaders(headers, true, false); assertEquals(OK, response.getStatusCode()); } @@ -93,25 +93,49 @@ public void testMultipleUrls() { ResponseEntity response = this.restTemplate.getForEntity("/serviceD/" + randomPath, String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } } - private void assertHeaders(HttpHeaders headers, boolean nullable) { + @Test + public void testExceedingQuotaCapacityRequest() { + ResponseEntity response = this.restTemplate.getForEntity("/serviceE", String.class); + HttpHeaders headers = response.getHeaders(); + assertHeaders(headers, false, true); + assertEquals(OK, response.getStatusCode()); + + response = this.restTemplate.getForEntity("/serviceE", String.class); + headers = response.getHeaders(); + assertHeaders(headers, false, true); + assertEquals(TOO_MANY_REQUESTS, response.getStatusCode()); + } + + private void assertHeaders(HttpHeaders headers, boolean nullable, boolean quotaHeaders) { + String quota = headers.getFirst(RateLimitPreFilter.QUOTA_HEADER); + String remainingQuota = headers.getFirst(RateLimitPreFilter.REMAINING_QUOTA_HEADER); String limit = headers.getFirst(RateLimitPreFilter.LIMIT_HEADER); String remaining = headers.getFirst(RateLimitPreFilter.REMAINING_HEADER); String reset = headers.getFirst(RateLimitPreFilter.RESET_HEADER); if (nullable) { - assertNull(limit); - assertNull(remaining); + if (quotaHeaders) { + assertNull(quota); + assertNull(remainingQuota); + } else { + assertNull(limit); + assertNull(remaining); + } assertNull(reset); } else { - assertNotNull(limit); - assertNotNull(remaining); + if (quotaHeaders) { + assertNotNull(quota); + assertNotNull(remainingQuota); + } else { + assertNotNull(limit); + assertNotNull(remaining); + } assertNotNull(reset); } } - } diff --git a/spring-cloud-zuul-ratelimit-tests/inmemory/src/main/java/com/marcosbarbero/tests/InMemoryApplication.java b/spring-cloud-zuul-ratelimit-tests/inmemory/src/main/java/com/marcosbarbero/tests/InMemoryApplication.java index 5e9401f6..a53681ff 100644 --- a/spring-cloud-zuul-ratelimit-tests/inmemory/src/main/java/com/marcosbarbero/tests/InMemoryApplication.java +++ b/spring-cloud-zuul-ratelimit-tests/inmemory/src/main/java/com/marcosbarbero/tests/InMemoryApplication.java @@ -6,7 +6,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** @@ -23,7 +22,6 @@ public static void main(String... args) { @RestController - @RequestMapping("/services") public class ServiceController { public static final String RESPONSE_BODY = "ResponseBody"; @@ -47,5 +45,11 @@ public ResponseEntity serviceC() { public ResponseEntity serviceD(@PathVariable String paramName) { return ResponseEntity.ok(RESPONSE_BODY + " " + paramName); } + + @GetMapping("/serviceE") + public ResponseEntity serviceE() throws InterruptedException { + Thread.sleep(1100); + return ResponseEntity.ok(RESPONSE_BODY); + } } } diff --git a/spring-cloud-zuul-ratelimit-tests/inmemory/src/main/resources/application.yml b/spring-cloud-zuul-ratelimit-tests/inmemory/src/main/resources/application.yml index e0ba1c6c..95e22dae 100644 --- a/spring-cloud-zuul-ratelimit-tests/inmemory/src/main/resources/application.yml +++ b/spring-cloud-zuul-ratelimit-tests/inmemory/src/main/resources/application.yml @@ -2,17 +2,20 @@ zuul: routes: serviceA: path: /serviceA - url: forward:/services + url: forward:/ serviceB: path: /serviceB - url: forward:/services + url: forward:/ serviceC: path: /serviceC - url: forward:/services + url: forward:/ serviceD: strip-prefix: false path: /serviceD/** - url: forward:/services + url: forward:/ + serviceE: + path: /serviceE + url: forward:/ ratelimit: enabled: true policies: @@ -31,4 +34,9 @@ zuul: refresh-interval: 60 type: - url + serviceE: + quota: 1 + refresh-interval: 60 + type: + - origin strip-prefix: true diff --git a/spring-cloud-zuul-ratelimit-tests/inmemory/src/test/java/com/marcosbarbero/tests/it/InMemoryApplicationTestIT.java b/spring-cloud-zuul-ratelimit-tests/inmemory/src/test/java/com/marcosbarbero/tests/it/InMemoryApplicationTestIT.java index 9031a7d6..1c498924 100644 --- a/spring-cloud-zuul-ratelimit-tests/inmemory/src/test/java/com/marcosbarbero/tests/it/InMemoryApplicationTestIT.java +++ b/spring-cloud-zuul-ratelimit-tests/inmemory/src/test/java/com/marcosbarbero/tests/it/InMemoryApplicationTestIT.java @@ -57,7 +57,7 @@ public void testKeyPrefixDefaultValue() { public void testNotExceedingCapacityRequest() { ResponseEntity response = this.restTemplate.getForEntity("/serviceA", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } @@ -65,7 +65,7 @@ public void testNotExceedingCapacityRequest() { public void testExceedingCapacity() throws InterruptedException { ResponseEntity response = this.restTemplate.getForEntity("/serviceB", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); for (int i = 0; i < 2; i++) { @@ -79,7 +79,7 @@ public void testExceedingCapacity() throws InterruptedException { response = this.restTemplate.getForEntity("/serviceB", String.class); headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } @@ -87,7 +87,7 @@ public void testExceedingCapacity() throws InterruptedException { public void testNoRateLimit() { ResponseEntity response = this.restTemplate.getForEntity("/serviceC", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, true); + assertHeaders(headers, true, false); assertEquals(OK, response.getStatusCode()); } @@ -103,25 +103,49 @@ public void testMultipleUrls() { ResponseEntity response = this.restTemplate.getForEntity("/serviceD/" + randomPath, String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } } - private void assertHeaders(HttpHeaders headers, boolean nullable) { + @Test + public void testExceedingQuotaCapacityRequest() { + ResponseEntity response = this.restTemplate.getForEntity("/serviceE", String.class); + HttpHeaders headers = response.getHeaders(); + assertHeaders(headers, false, true); + assertEquals(OK, response.getStatusCode()); + + response = this.restTemplate.getForEntity("/serviceE", String.class); + headers = response.getHeaders(); + assertHeaders(headers, false, true); + assertEquals(TOO_MANY_REQUESTS, response.getStatusCode()); + } + + private void assertHeaders(HttpHeaders headers, boolean nullable, boolean quotaHeaders) { + String quota = headers.getFirst(RateLimitPreFilter.QUOTA_HEADER); + String remainingQuota = headers.getFirst(RateLimitPreFilter.REMAINING_QUOTA_HEADER); String limit = headers.getFirst(RateLimitPreFilter.LIMIT_HEADER); String remaining = headers.getFirst(RateLimitPreFilter.REMAINING_HEADER); String reset = headers.getFirst(RateLimitPreFilter.RESET_HEADER); - if (!nullable) { - assertNotNull(limit); - assertNotNull(remaining); - assertNotNull(reset); - } else { - assertNull(limit); - assertNull(remaining); + if (nullable) { + if (quotaHeaders) { + assertNull(quota); + assertNull(remainingQuota); + } else { + assertNull(limit); + assertNull(remaining); + } assertNull(reset); + } else { + if (quotaHeaders) { + assertNotNull(quota); + assertNotNull(remainingQuota); + } else { + assertNotNull(limit); + assertNotNull(remaining); + } + assertNotNull(reset); } } - } diff --git a/spring-cloud-zuul-ratelimit-tests/redis/src/main/java/com/marcosbarbero/tests/RedisApplication.java b/spring-cloud-zuul-ratelimit-tests/redis/src/main/java/com/marcosbarbero/tests/RedisApplication.java index 2a87f429..eb216d27 100644 --- a/spring-cloud-zuul-ratelimit-tests/redis/src/main/java/com/marcosbarbero/tests/RedisApplication.java +++ b/spring-cloud-zuul-ratelimit-tests/redis/src/main/java/com/marcosbarbero/tests/RedisApplication.java @@ -6,7 +6,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** @@ -22,7 +21,6 @@ public static void main(String... args) { } @RestController - @RequestMapping("/services") public class ServiceController { public static final String RESPONSE_BODY = "ResponseBody"; @@ -46,5 +44,11 @@ public ResponseEntity serviceC() { public ResponseEntity serviceD(@PathVariable String paramName) { return ResponseEntity.ok(RESPONSE_BODY + " " + paramName); } + + @GetMapping("/serviceE") + public ResponseEntity serviceE() throws InterruptedException { + Thread.sleep(1100); + return ResponseEntity.ok(RESPONSE_BODY); + } } } diff --git a/spring-cloud-zuul-ratelimit-tests/redis/src/main/resources/application.yml b/spring-cloud-zuul-ratelimit-tests/redis/src/main/resources/application.yml index 8e4ff447..70bb2b25 100644 --- a/spring-cloud-zuul-ratelimit-tests/redis/src/main/resources/application.yml +++ b/spring-cloud-zuul-ratelimit-tests/redis/src/main/resources/application.yml @@ -2,17 +2,20 @@ zuul: routes: serviceA: path: /serviceA - url: forward:/services + url: forward:/ serviceB: path: /serviceB - url: forward:/services + url: forward:/ serviceC: path: /serviceC - url: forward:/services + url: forward:/ serviceD: strip-prefix: false path: /serviceD/** - url: forward:/services + url: forward:/ + serviceE: + path: /serviceE + url: forward:/ ratelimit: enabled: true repository: REDIS @@ -32,4 +35,9 @@ zuul: refresh-interval: 60 type: - url + serviceE: + quota: 1 + refresh-interval: 60 + type: + - origin strip-prefix: true diff --git a/spring-cloud-zuul-ratelimit-tests/redis/src/test/java/com/marcosbarbero/tests/it/RedisApplicationTestIT.java b/spring-cloud-zuul-ratelimit-tests/redis/src/test/java/com/marcosbarbero/tests/it/RedisApplicationTestIT.java index 1078ea37..63c441ae 100644 --- a/spring-cloud-zuul-ratelimit-tests/redis/src/test/java/com/marcosbarbero/tests/it/RedisApplicationTestIT.java +++ b/spring-cloud-zuul-ratelimit-tests/redis/src/test/java/com/marcosbarbero/tests/it/RedisApplicationTestIT.java @@ -48,7 +48,7 @@ public void testRedisRateLimiter() { public void testNotExceedingCapacityRequest() { ResponseEntity response = this.restTemplate.getForEntity("/serviceA", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } @@ -56,7 +56,7 @@ public void testNotExceedingCapacityRequest() { public void testExceedingCapacity() throws InterruptedException { ResponseEntity response = this.restTemplate.getForEntity("/serviceB", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); for (int i = 0; i < 2; i++) { @@ -70,7 +70,7 @@ public void testExceedingCapacity() throws InterruptedException { response = this.restTemplate.getForEntity("/serviceB", String.class); headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } @@ -78,7 +78,7 @@ public void testExceedingCapacity() throws InterruptedException { public void testNoRateLimit() { ResponseEntity response = this.restTemplate.getForEntity("/serviceC", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, true); + assertHeaders(headers, true, false); assertEquals(OK, response.getStatusCode()); } @@ -93,25 +93,49 @@ public void testMultipleUrls() { ResponseEntity response = this.restTemplate.getForEntity("/serviceD/" + randomPath, String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } } - private void assertHeaders(HttpHeaders headers, boolean nullable) { + @Test + public void testExceedingQuotaCapacityRequest() { + ResponseEntity response = this.restTemplate.getForEntity("/serviceE", String.class); + HttpHeaders headers = response.getHeaders(); + assertHeaders(headers, false, true); + assertEquals(OK, response.getStatusCode()); + + response = this.restTemplate.getForEntity("/serviceE", String.class); + headers = response.getHeaders(); + assertHeaders(headers, false, true); + assertEquals(TOO_MANY_REQUESTS, response.getStatusCode()); + } + + private void assertHeaders(HttpHeaders headers, boolean nullable, boolean quotaHeaders) { + String quota = headers.getFirst(RateLimitPreFilter.QUOTA_HEADER); + String remainingQuota = headers.getFirst(RateLimitPreFilter.REMAINING_QUOTA_HEADER); String limit = headers.getFirst(RateLimitPreFilter.LIMIT_HEADER); String remaining = headers.getFirst(RateLimitPreFilter.REMAINING_HEADER); String reset = headers.getFirst(RateLimitPreFilter.RESET_HEADER); if (nullable) { - assertNull(limit); - assertNull(remaining); + if (quotaHeaders) { + assertNull(quota); + assertNull(remainingQuota); + } else { + assertNull(limit); + assertNull(remaining); + } assertNull(reset); } else { - assertNotNull(limit); - assertNotNull(remaining); + if (quotaHeaders) { + assertNotNull(quota); + assertNotNull(remainingQuota); + } else { + assertNotNull(limit); + assertNotNull(remaining); + } assertNotNull(reset); } } - } diff --git a/spring-cloud-zuul-ratelimit-tests/springdata/src/main/java/com/marcosbarbero/tests/SpringDataApplication.java b/spring-cloud-zuul-ratelimit-tests/springdata/src/main/java/com/marcosbarbero/tests/SpringDataApplication.java index d58dbfd0..d621acf2 100644 --- a/spring-cloud-zuul-ratelimit-tests/springdata/src/main/java/com/marcosbarbero/tests/SpringDataApplication.java +++ b/spring-cloud-zuul-ratelimit-tests/springdata/src/main/java/com/marcosbarbero/tests/SpringDataApplication.java @@ -6,7 +6,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** @@ -23,7 +22,6 @@ public static void main(String... args) { @RestController - @RequestMapping("/services") public class ServiceController { public static final String RESPONSE_BODY = "ResponseBody"; @@ -47,5 +45,11 @@ public ResponseEntity serviceC() { public ResponseEntity serviceD(@PathVariable String paramName) { return ResponseEntity.ok(RESPONSE_BODY + " " + paramName); } + + @GetMapping("/serviceE") + public ResponseEntity serviceE() throws InterruptedException { + Thread.sleep(1100); + return ResponseEntity.ok(RESPONSE_BODY); + } } } diff --git a/spring-cloud-zuul-ratelimit-tests/springdata/src/main/resources/application.yml b/spring-cloud-zuul-ratelimit-tests/springdata/src/main/resources/application.yml index cf8a4c33..01b34cda 100644 --- a/spring-cloud-zuul-ratelimit-tests/springdata/src/main/resources/application.yml +++ b/spring-cloud-zuul-ratelimit-tests/springdata/src/main/resources/application.yml @@ -26,17 +26,20 @@ zuul: routes: serviceA: path: /serviceA - url: forward:/services + url: forward:/ serviceB: path: /serviceB - url: forward:/services + url: forward:/ serviceC: path: /serviceC - url: forward:/services + url: forward:/ serviceD: strip-prefix: false path: /serviceD/** - url: forward:/services + url: forward:/ + serviceE: + path: /serviceE + url: forward:/ ratelimit: enabled: true repository: JPA @@ -56,4 +59,9 @@ zuul: refresh-interval: 60 type: - url + serviceE: + quota: 1 + refresh-interval: 60 + type: + - origin strip-prefix: true diff --git a/spring-cloud-zuul-ratelimit-tests/springdata/src/test/java/com/marcosbarbero/tests/it/SpringDataApplicationTestIT.java b/spring-cloud-zuul-ratelimit-tests/springdata/src/test/java/com/marcosbarbero/tests/it/SpringDataApplicationTestIT.java index 9880c409..ce2ed670 100644 --- a/spring-cloud-zuul-ratelimit-tests/springdata/src/test/java/com/marcosbarbero/tests/it/SpringDataApplicationTestIT.java +++ b/spring-cloud-zuul-ratelimit-tests/springdata/src/test/java/com/marcosbarbero/tests/it/SpringDataApplicationTestIT.java @@ -56,7 +56,7 @@ public void testKeyPrefixDefaultValue() { public void testNotExceedingCapacityRequest() { ResponseEntity response = this.restTemplate.getForEntity("/serviceA", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } @@ -64,7 +64,7 @@ public void testNotExceedingCapacityRequest() { public void testExceedingCapacity() throws InterruptedException { ResponseEntity response = this.restTemplate.getForEntity("/serviceB", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); for (int i = 0; i < 2; i++) { @@ -78,7 +78,7 @@ public void testExceedingCapacity() throws InterruptedException { response = this.restTemplate.getForEntity("/serviceB", String.class); headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } @@ -86,7 +86,7 @@ public void testExceedingCapacity() throws InterruptedException { public void testNoRateLimit() { ResponseEntity response = this.restTemplate.getForEntity("/serviceC", String.class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, true); + assertHeaders(headers, true, false); assertEquals(OK, response.getStatusCode()); } @@ -103,25 +103,49 @@ public void testMultipleUrls() { ResponseEntity response = this.restTemplate.getForEntity("/serviceD/" + randomPath, String .class); HttpHeaders headers = response.getHeaders(); - assertHeaders(headers, false); + assertHeaders(headers, false, false); assertEquals(OK, response.getStatusCode()); } } - private void assertHeaders(HttpHeaders headers, boolean nullable) { + @Test + public void testExceedingQuotaCapacityRequest() { + ResponseEntity response = this.restTemplate.getForEntity("/serviceE", String.class); + HttpHeaders headers = response.getHeaders(); + assertHeaders(headers, false, true); + assertEquals(OK, response.getStatusCode()); + + response = this.restTemplate.getForEntity("/serviceE", String.class); + headers = response.getHeaders(); + assertHeaders(headers, false, true); + assertEquals(TOO_MANY_REQUESTS, response.getStatusCode()); + } + + private void assertHeaders(HttpHeaders headers, boolean nullable, boolean quotaHeaders) { + String quota = headers.getFirst(RateLimitPreFilter.QUOTA_HEADER); + String remainingQuota = headers.getFirst(RateLimitPreFilter.REMAINING_QUOTA_HEADER); String limit = headers.getFirst(RateLimitPreFilter.LIMIT_HEADER); String remaining = headers.getFirst(RateLimitPreFilter.REMAINING_HEADER); String reset = headers.getFirst(RateLimitPreFilter.RESET_HEADER); - if (!nullable) { - assertNotNull(limit); - assertNotNull(remaining); - assertNotNull(reset); - } else { - assertNull(limit); - assertNull(remaining); + if (nullable) { + if (quotaHeaders) { + assertNull(quota); + assertNull(remainingQuota); + } else { + assertNull(limit); + assertNull(remaining); + } assertNull(reset); + } else { + if (quotaHeaders) { + assertNotNull(quota); + assertNotNull(remainingQuota); + } else { + assertNotNull(limit); + assertNotNull(remaining); + } + assertNotNull(reset); } } - }