Merge pull request #197 from SkyeBeFreeman/main

feat:add rate limit of unirate.
pull/201/head
lepdou 3 years ago committed by GitHub
commit 70786309ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,10 +1,10 @@
# Change Log # Change Log
--- ---
- [Feature: Support parse ratelimit rule expression labels.](https://github.com/Tencent/spring-cloud-tencent/pull/183) - [Feature: Support parse ratelimit rule expression labels.](https://github.com/Tencent/spring-cloud-tencent/pull/183)
- [Feature: Router support request label.](https://github.com/Tencent/spring-cloud-tencent/pull/165) - [Feature: Router support request label.](https://github.com/Tencent/spring-cloud-tencent/pull/165)
- [Feature: Support router expression label](https://github.com/Tencent/spring-cloud-tencent/pull/190) - [Feature: Support router expression label](https://github.com/Tencent/spring-cloud-tencent/pull/190)
- [Add metadata transfer example.](https://github.com/Tencent/spring-cloud-tencent/pull/184) - [Add metadata transfer example.](https://github.com/Tencent/spring-cloud-tencent/pull/184)
- [Feature: Support metadata router.](https://github.com/Tencent/spring-cloud-tencent/pull/191) - [Feature: Support metadata router.](https://github.com/Tencent/spring-cloud-tencent/pull/191)
- [Feature: Misc optimize metadata router.](https://github.com/Tencent/spring-cloud-tencent/pull/192) - [Feature: Misc optimize metadata router.](https://github.com/Tencent/spring-cloud-tencent/pull/192)
- [feat:add rate limit of unirate.](https://github.com/Tencent/spring-cloud-tencent/pull/197)

@ -0,0 +1,75 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.ratelimit.config;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisConfigModifier;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Autoconfiguration of rate limit at bootstrap phase.
*
* @author Haotian Zhang
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
@ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true)
public class PolarisRateLimitBootstrapConfiguration {
@Bean
public PolarisRateLimitProperties polarisRateLimitProperties() {
return new PolarisRateLimitProperties();
}
@Bean
public RateLimitConfigModifier rateLimitConfigModifier(PolarisRateLimitProperties polarisRateLimitProperties) {
return new RateLimitConfigModifier(polarisRateLimitProperties);
}
/**
* Config modifier for rate limit.
*
* @author Haotian Zhang
*/
public static class RateLimitConfigModifier implements PolarisConfigModifier {
private PolarisRateLimitProperties polarisRateLimitProperties;
public RateLimitConfigModifier(PolarisRateLimitProperties polarisRateLimitProperties) {
this.polarisRateLimitProperties = polarisRateLimitProperties;
}
@Override
public void modify(ConfigurationImpl configuration) {
// Update MaxQueuingTime.
configuration.getProvider().getRateLimit()
.setMaxQueuingTime(polarisRateLimitProperties.getMaxQueuingTime());
}
@Override
public int getOrder() {
return ContextConstant.ModifierOrder.CIRCUIT_BREAKER_ORDER;
}
}
}

@ -19,6 +19,7 @@
package com.tencent.cloud.polaris.ratelimit.config; package com.tencent.cloud.polaris.ratelimit.config;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -30,6 +31,7 @@ import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.factory.LimitAPIFactory; import com.tencent.polaris.ratelimit.factory.LimitAPIFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
@ -45,18 +47,15 @@ import static javax.servlet.DispatcherType.INCLUDE;
import static javax.servlet.DispatcherType.REQUEST; import static javax.servlet.DispatcherType.REQUEST;
/** /**
* Configuration of rate limit.
*
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@Configuration @Configuration
@ConditionalOnPolarisEnabled @ConditionalOnPolarisEnabled
@ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", @AutoConfigureAfter(PolarisContextAutoConfiguration.class)
matchIfMissing = true) @ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true)
public class RateLimitConfiguration { public class PolarisRateLimitConfiguration {
@Bean
public PolarisRateLimitProperties polarisRateLimitProperties() {
return new PolarisRateLimitProperties();
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean

@ -44,6 +44,11 @@ public class PolarisRateLimitProperties {
*/ */
private int rejectHttpCode = HttpStatus.TOO_MANY_REQUESTS.value(); private int rejectHttpCode = HttpStatus.TOO_MANY_REQUESTS.value();
/**
* Max queuing time when using unirate.
*/
private long maxQueuingTime = 1000L;
public String getRejectRequestTips() { public String getRejectRequestTips() {
return rejectRequestTips; return rejectRequestTips;
} }
@ -68,4 +73,11 @@ public class PolarisRateLimitProperties {
this.rejectHttpCode = rejectHttpCode; this.rejectHttpCode = rejectHttpCode;
} }
public long getMaxQueuingTime() {
return maxQueuingTime;
}
public void setMaxQueuingTime(long maxQueuingTime) {
this.maxQueuingTime = maxQueuingTime;
}
} }

@ -59,8 +59,7 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB
*/ */
public class QuotaCheckReactiveFilter implements WebFilter, Ordered { public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
private static final Logger LOG = LoggerFactory private static final Logger LOG = LoggerFactory.getLogger(QuotaCheckReactiveFilter.class);
.getLogger(QuotaCheckReactiveFilter.class);
private final LimitAPI limitAPI; private final LimitAPI limitAPI;
@ -111,6 +110,10 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
.write(rejectTips.getBytes(StandardCharsets.UTF_8)); .write(rejectTips.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer)); return response.writeWith(Mono.just(dataBuffer));
} }
// Unirate
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultOk && quotaResponse.getWaitMs() > 0) {
Thread.sleep(quotaResponse.getWaitMs());
}
} }
catch (Throwable t) { catch (Throwable t) {
// An exception occurs in the rate limiting API call, // An exception occurs in the rate limiting API call,
@ -147,8 +150,7 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
return labelResolver.resolve(exchange); return labelResolver.resolve(exchange);
} }
catch (Throwable e) { catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}", LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e);
labelResolver.getClass().getName(), e);
} }
} }
return Maps.newHashMap(); return Maps.newHashMap();

@ -102,6 +102,10 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
response.getWriter().write(rejectTips); response.getWriter().write(rejectTips);
return; return;
} }
// Unirate
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultOk && quotaResponse.getWaitMs() > 0) {
Thread.sleep(quotaResponse.getWaitMs());
}
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }

@ -33,15 +33,13 @@ import org.springframework.util.StringUtils;
*/ */
public final class RateLimitUtils { public final class RateLimitUtils {
private static final Logger LOG = LoggerFactory private static final Logger LOG = LoggerFactory.getLogger(RateLimitUtils.class);
.getLogger(RateLimitUtils.class);
private RateLimitUtils() { private RateLimitUtils() {
} }
public static String getRejectTips( public static String getRejectTips(PolarisRateLimitProperties polarisRateLimitProperties) {
PolarisRateLimitProperties polarisRateLimitProperties) {
String tips = polarisRateLimitProperties.getRejectRequestTips(); String tips = polarisRateLimitProperties.getRejectRequestTips();
if (!StringUtils.isEmpty(tips)) { if (!StringUtils.isEmpty(tips)) {

@ -23,6 +23,12 @@
"type": "java.lang.Integer", "type": "java.lang.Integer",
"defaultValue": "429", "defaultValue": "429",
"description": "Custom http code when reject request." "description": "Custom http code when reject request."
},
{
"name": "spring.cloud.polaris.ratelimit.maxQueuingTime",
"type": "java.lang.Long",
"defaultValue": "1000",
"description": "Max queuing time when using unirate."
} }
] ]
} }

@ -1,2 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.ratelimit.config.RateLimitConfiguration com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitBootstrapConfiguration

@ -0,0 +1,101 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.ratelimit;
import java.util.Set;
import com.google.protobuf.StringValue;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RateLimitProto;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link RateLimitRuleLabelResolver}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class RateLimitRuleLabelResolverTest {
private ServiceRuleManager serviceRuleManager;
private RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
@Before
public void setUp() {
serviceRuleManager = mock(ServiceRuleManager.class);
when(serviceRuleManager.getServiceRateLimitRule(any(), anyString())).thenAnswer(invocationOnMock -> {
String serviceName = invocationOnMock.getArgument(1).toString();
if (serviceName.equals("TestApp1")) {
return null;
}
else if (serviceName.equals("TestApp2")) {
return RateLimitProto.RateLimit.newBuilder().build();
}
else if (serviceName.equals("TestApp3")) {
RateLimitProto.Rule rule = RateLimitProto.Rule.newBuilder().build();
return RateLimitProto.RateLimit.newBuilder().addRules(rule).build();
}
else {
ModelProto.MatchString matchString = ModelProto.MatchString.newBuilder()
.setType(ModelProto.MatchString.MatchStringType.EXACT)
.setValue(StringValue.of("value"))
.setValueType(ModelProto.MatchString.ValueType.TEXT).build();
RateLimitProto.Rule rule = RateLimitProto.Rule.newBuilder()
.putLabels("${http.method}", matchString).build();
return RateLimitProto.RateLimit.newBuilder().addRules(rule).build();
}
});
rateLimitRuleLabelResolver = new RateLimitRuleLabelResolver(serviceRuleManager);
}
@Test
public void testGetExpressionLabelKeys() {
// rateLimitRule == null
String serviceName = "TestApp1";
Set<String> labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isEmpty();
// CollectionUtils.isEmpty(rules)
serviceName = "TestApp2";
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isEmpty();
// CollectionUtils.isEmpty(labels)
serviceName = "TestApp3";
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isEmpty();
// Has labels
serviceName = "TestApp4";
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isNotEmpty();
assertThat(labelKeys).contains("${http.method}");
}
}

@ -0,0 +1,46 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.ratelimit.config;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisRateLimitBootstrapConfiguration}.
*
* @author Haotian Zhang
*/
public class PolarisRateLimitBootstrapConfigurationTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(PolarisRateLimitBootstrapConfiguration.class))
.withPropertyValues("spring.cloud.polaris.ratelimit.enabled=true");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(PolarisRateLimitProperties.class);
assertThat(context).hasSingleBean(PolarisRateLimitBootstrapConfiguration.RateLimitConfigModifier.class);
});
}
}

@ -0,0 +1,99 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.ratelimit.config;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisRateLimitConfiguration}.
*
* @author Haotian Zhang
*/
public class PolarisRateLimitConfigurationTest {
private ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner();
private WebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner();
private ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = new ReactiveWebApplicationContextRunner();
@Test
public void testNoWebApplication() {
this.applicationContextRunner
.withConfiguration(AutoConfigurations.of(
PolarisContextAutoConfiguration.class,
PolarisRateLimitProperties.class,
PolarisRateLimitConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(LimitAPI.class);
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class);
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class);
});
}
@Test
public void testServletWebApplication() {
this.webApplicationContextRunner
.withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class,
PolarisRateLimitProperties.class,
PolarisRateLimitConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(LimitAPI.class);
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
assertThat(context).hasSingleBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).hasSingleBean(QuotaCheckServletFilter.class);
assertThat(context).hasSingleBean(FilterRegistrationBean.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class);
});
}
@Test
public void testReactiveWebApplication() {
this.reactiveWebApplicationContextRunner
.withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class,
PolarisRateLimitProperties.class,
PolarisRateLimitConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(LimitAPI.class);
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class);
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
assertThat(context).hasSingleBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
assertThat(context).hasSingleBean(QuotaCheckReactiveFilter.class);
});
}
}

@ -0,0 +1,59 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.ratelimit.config;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisRateLimitProperties}.
*
* @author Haotian Zhang
*/
public class PolarisRateLimitPropertiesTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PolarisRateLimitPropertiesAutoConfiguration.class, PolarisRateLimitProperties.class))
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectRequestTips=xxx")
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectRequestTipsFilePath=/index.html")
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectHttpCode=419")
.withPropertyValues("spring.cloud.polaris.ratelimit.maxQueuingTime=500");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
PolarisRateLimitProperties polarisRateLimitProperties = context.getBean(PolarisRateLimitProperties.class);
assertThat(polarisRateLimitProperties.getRejectRequestTips()).isEqualTo("xxx");
assertThat(polarisRateLimitProperties.getRejectRequestTipsFilePath()).isEqualTo("/index.html");
assertThat(polarisRateLimitProperties.getRejectHttpCode()).isEqualTo(419);
assertThat(polarisRateLimitProperties.getMaxQueuingTime()).isEqualTo(500L);
});
}
@Configuration
@EnableAutoConfiguration
static class PolarisRateLimitPropertiesAutoConfiguration {
}
}

@ -0,0 +1,212 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.ratelimit.filter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
/**
* Test for {@link QuotaCheckReactiveFilter}.
*
* @author Haotian Zhang
*/
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*", "javax.script.*"})
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest(ExpressionLabelUtils.class)
@SpringBootTest(classes = QuotaCheckReactiveFilterTest.TestApplication.class)
public class QuotaCheckReactiveFilterTest {
private PolarisRateLimiterLabelReactiveResolver labelResolver = exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver");
private QuotaCheckReactiveFilter quotaCheckReactiveFilter;
@BeforeClass
public static void beforeClass() {
// mock ExpressionLabelUtils.resolve()
mockStatic(ExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())).thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
}
@Before
public void setUp() {
MetadataContext.LOCAL_NAMESPACE = "TEST";
LimitAPI limitAPI = mock(LimitAPI.class);
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
if (serviceName.equals("TestApp1")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp2")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
}
else {
return new QuotaResponse(new QuotaResult(null, 0, null));
}
});
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
polarisRateLimitProperties.setRejectHttpCode(419);
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.EMPTY_SET);
this.quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
}
@Test
public void testGetOrder() {
assertThat(this.quotaCheckReactiveFilter.getOrder()).isEqualTo(RateLimitConstant.FILTER_ORDER);
}
@Test
public void testInit() {
quotaCheckReactiveFilter.init();
try {
Field rejectTips = QuotaCheckReactiveFilter.class.getDeclaredField("rejectTips");
rejectTips.setAccessible(true);
assertThat(rejectTips.get(quotaCheckReactiveFilter)).isEqualTo("RejectRequestTips");
}
catch (NoSuchFieldException | IllegalAccessException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testGetRuleExpressionLabels() {
try {
Method getCustomResolvedLabels = QuotaCheckReactiveFilter.class.getDeclaredMethod("getCustomResolvedLabels", ServerWebExchange.class);
getCustomResolvedLabels.setAccessible(true);
// Mock request
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/test").build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
// labelResolver != null
Map<String, String> result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
assertThat(result.size()).isEqualTo(1);
assertThat(result.get("ReactiveResolver")).isEqualTo("ReactiveResolver");
// throw exception
PolarisRateLimiterLabelReactiveResolver exceptionLabelResolver = new PolarisRateLimiterLabelReactiveResolver() {
@Override
public Map<String, String> resolve(ServerWebExchange exchange) {
throw new RuntimeException("Mock exception.");
}
};
quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(null, exceptionLabelResolver, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
assertThat(result.size()).isEqualTo(0);
// labelResolver == null
quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(null, null, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
assertThat(result.size()).isEqualTo(0);
getCustomResolvedLabels.setAccessible(false);
}
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testFilter() {
// Create mock WebFilterChain
WebFilterChain webFilterChain = serverWebExchange -> Mono.empty();
// Mock request
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/test").build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
quotaCheckReactiveFilter.init();
// Pass
MetadataContext.LOCAL_SERVICE = "TestApp1";
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
// Unirate waiting 1000ms
MetadataContext.LOCAL_SERVICE = "TestApp2";
long startTimestamp = System.currentTimeMillis();
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L);
// Rate limited
MetadataContext.LOCAL_SERVICE = "TestApp3";
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
ServerHttpResponse response = exchange.getResponse();
assertThat(response.getRawStatusCode()).isEqualTo(419);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE);
// Exception
MetadataContext.LOCAL_SERVICE = "TestApp4";
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,209 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.ratelimit.filter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
/**
* Test for {@link QuotaCheckServletFilter}.
*
* @author Haotian Zhang
*/
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*", "javax.script.*"})
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest(ExpressionLabelUtils.class)
@SpringBootTest(classes = QuotaCheckServletFilterTest.TestApplication.class)
public class QuotaCheckServletFilterTest {
private PolarisRateLimiterLabelServletResolver labelResolver = exchange -> Collections.singletonMap("ServletResolver", "ServletResolver");
private QuotaCheckServletFilter quotaCheckServletFilter;
@BeforeClass
public static void beforeClass() {
// mock ExpressionLabelUtils.resolve()
mockStatic(ExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())).thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
}
@Before
public void setUp() {
MetadataContext.LOCAL_NAMESPACE = "TEST";
LimitAPI limitAPI = mock(LimitAPI.class);
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
if (serviceName.equals("TestApp1")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp2")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
}
else {
return new QuotaResponse(new QuotaResult(null, 0, null));
}
});
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
polarisRateLimitProperties.setRejectHttpCode(419);
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.EMPTY_SET);
this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
}
@Test
public void testInit() {
quotaCheckServletFilter.init();
try {
Field rejectTips = QuotaCheckServletFilter.class.getDeclaredField("rejectTips");
rejectTips.setAccessible(true);
assertThat(rejectTips.get(quotaCheckServletFilter)).isEqualTo("RejectRequestTips");
}
catch (NoSuchFieldException | IllegalAccessException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testGetRuleExpressionLabels() {
try {
Method getCustomResolvedLabels = QuotaCheckServletFilter.class.getDeclaredMethod("getCustomResolvedLabels", HttpServletRequest.class);
getCustomResolvedLabels.setAccessible(true);
// Mock request
MockHttpServletRequest request = new MockHttpServletRequest();
// labelResolver != null
Map<String, String> result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
assertThat(result.size()).isEqualTo(1);
assertThat(result.get("ServletResolver")).isEqualTo("ServletResolver");
// throw exception
PolarisRateLimiterLabelServletResolver exceptionLabelResolver = request1 -> {
throw new RuntimeException("Mock exception.");
};
quotaCheckServletFilter = new QuotaCheckServletFilter(null, exceptionLabelResolver, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
assertThat(result.size()).isEqualTo(0);
// labelResolver == null
quotaCheckServletFilter = new QuotaCheckServletFilter(null, null, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
assertThat(result.size()).isEqualTo(0);
getCustomResolvedLabels.setAccessible(false);
}
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testDoFilterInternal() {
// Create mock FilterChain
FilterChain filterChain = (servletRequest, servletResponse) -> {
};
// Mock request
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
quotaCheckServletFilter.init();
try {
// Pass
MetadataContext.LOCAL_SERVICE = "TestApp1";
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
// Unirate waiting 1000ms
MetadataContext.LOCAL_SERVICE = "TestApp2";
long startTimestamp = System.currentTimeMillis();
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L);
// Rate limited
MetadataContext.LOCAL_SERVICE = "TestApp3";
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
assertThat(response.getStatus()).isEqualTo(419);
assertThat(response.getContentAsString()).isEqualTo("RejectRequestTips");
// Exception
MetadataContext.LOCAL_SERVICE = "TestApp4";
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
}
catch (ServletException | IOException e) {
fail("Exception encountered.", e);
}
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,97 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.ratelimit.utils;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.when;
/**
* Test for {@link QuotaCheckUtils}.
*
* @author Haotian Zhang
*/
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*", "javax.script.*"})
public class QuotaCheckUtilsTest {
private LimitAPI limitAPI;
@Before
public void setUp() {
limitAPI = mock(LimitAPI.class);
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
if (serviceName.equals("TestApp1")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp2")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
}
else {
throw new RuntimeException("Mock exception.");
}
});
}
@Test
public void testGetQuota() {
// Pass
String serviceName = "TestApp1";
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk");
// Unirate waiting 1000ms
serviceName = "TestApp2";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(1000);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk");
// Rate limited
serviceName = "TestApp3";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultLimited);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultLimited");
// Exception
serviceName = "TestApp4";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("get quota failed");
}
}

@ -0,0 +1,79 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.ratelimit.utils;
import java.io.IOException;
import com.tencent.cloud.common.util.ResourceFileUtils;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.QUOTA_LIMITED_INFO;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
/**
* Test for {@link RateLimitUtils}.
*
* @author Haotian Zhang
*/
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*", "javax.script.*"})
@PrepareForTest(ResourceFileUtils.class)
public class RateLimitUtilsTest {
@BeforeClass
public static void beforeClass() throws IOException {
mockStatic(ResourceFileUtils.class);
when(ResourceFileUtils.readFile(anyString())).thenAnswer(invocation -> {
String rejectFilePath = invocation.getArgument(0).toString();
if (rejectFilePath.equals("exception.html")) {
throw new IOException("Mock exceptions");
}
else {
return "RejectRequestTips";
}
});
}
@Test
public void testGetRejectTips() {
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
// RejectRequestTips
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo("RejectRequestTips");
// RejectRequestTipsFilePath
polarisRateLimitProperties.setRejectRequestTips(null);
polarisRateLimitProperties.setRejectRequestTipsFilePath("reject-tips.html");
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo("RejectRequestTips");
// RejectRequestTipsFilePath with Exception
polarisRateLimitProperties.setRejectRequestTips(null);
polarisRateLimitProperties.setRejectRequestTipsFilePath("exception.html");
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo(QUOTA_LIMITED_INFO);
}
}

@ -31,8 +31,10 @@ public final class ReflectionUtils {
} }
public static Object getFieldValue(Object instance, String fieldName) { public static Object getFieldValue(Object instance, String fieldName) {
Field field = org.springframework.util.ReflectionUtils Field field = org.springframework.util.ReflectionUtils.findField(instance.getClass(), fieldName);
.findField(instance.getClass(), fieldName); if (field == null) {
return null;
}
field.setAccessible(true); field.setAccessible(true);
try { try {

@ -18,6 +18,10 @@
package com.tencent.cloud.ratelimit.example.service.callee; package com.tencent.cloud.ratelimit.example.service.callee;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -38,6 +42,8 @@ import org.springframework.web.client.RestTemplate;
@RequestMapping("/business") @RequestMapping("/business")
public class BusinessController { public class BusinessController {
private static final Logger LOG = LoggerFactory.getLogger(BusinessController.class);
private final AtomicInteger index = new AtomicInteger(0); private final AtomicInteger index = new AtomicInteger(0);
@Autowired @Autowired
@ -46,6 +52,8 @@ public class BusinessController {
@Value("${spring.application.name}") @Value("${spring.application.name}")
private String appName; private String appName;
private AtomicLong lastTimestamp = new AtomicLong(0);
/** /**
* Get information. * Get information.
* @return information * @return information
@ -77,4 +85,21 @@ public class BusinessController {
return builder.toString(); return builder.toString();
} }
/**
* Get information with unirate.
* @return information
*/
@GetMapping("/unirate")
public String unirate() {
long currentTimestamp = System.currentTimeMillis();
long lastTime = lastTimestamp.get();
if (lastTime != 0) {
LOG.info("Current timestamp:" + currentTimestamp + ", diff from last timestamp:" + (currentTimestamp - lastTime));
}
else {
LOG.info("Current timestamp:" + currentTimestamp);
}
lastTimestamp.set(currentTimestamp);
return "hello world for ratelimit service with diff from last request:" + (currentTimestamp - lastTime) + "ms.";
}
} }

@ -11,3 +11,4 @@ spring:
ratelimit: ratelimit:
enabled: true enabled: true
rejectRequestTipsFilePath: reject-tips.html rejectRequestTipsFilePath: reject-tips.html
maxQueuingTime: 500

Loading…
Cancel
Save