commit
70786309ea
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue