feat:support concurrency rate limit. (#1455)

* feat:support concurrency rate limit.

* feat:support concurrency rate limit.
pull/1479/head
Haotian Zhang 1 month ago committed by GitHub
parent 7e9b96bae1
commit ec43a2528c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -38,3 +38,4 @@
- [feat: support lossless config from console & support warmup.](https://github.com/Tencent/spring-cloud-tencent/pull/1445) - [feat: support lossless config from console & support warmup.](https://github.com/Tencent/spring-cloud-tencent/pull/1445)
- [feat:add admin http handler.](https://github.com/Tencent/spring-cloud-tencent/pull/1450) - [feat:add admin http handler.](https://github.com/Tencent/spring-cloud-tencent/pull/1450)
- [feat:upgrade spring cloud 2023 version.](https://github.com/Tencent/spring-cloud-tencent/pull/1451) - [feat:upgrade spring cloud 2023 version.](https://github.com/Tencent/spring-cloud-tencent/pull/1451)
- [feat:support concurrency rate limit.](https://github.com/Tencent/spring-cloud-tencent/pull/1455)

@ -25,7 +25,6 @@ import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateEnhancedPlugin; import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataScgEnhancedPlugin; import com.tencent.cloud.metadata.core.EncodeTransferMedataScgEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientEnhancedPlugin; import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientEnhancedPlugin;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -66,8 +65,8 @@ public class MetadataTransferAutoConfiguration {
} }
@Bean @Bean
public DecodeTransferMetadataServletFilter metadataServletFilter(PolarisContextProperties polarisContextProperties) { public DecodeTransferMetadataServletFilter metadataServletFilter() {
return new DecodeTransferMetadataServletFilter(polarisContextProperties); return new DecodeTransferMetadataServletFilter();
} }
} }
@ -79,8 +78,8 @@ public class MetadataTransferAutoConfiguration {
protected static class MetadataReactiveFilterConfig { protected static class MetadataReactiveFilterConfig {
@Bean @Bean
public DecodeTransferMetadataReactiveFilter metadataReactiveFilter(PolarisContextProperties polarisContextProperties) { public DecodeTransferMetadataReactiveFilter metadataReactiveFilter() {
return new DecodeTransferMetadataReactiveFilter(polarisContextProperties); return new DecodeTransferMetadataReactiveFilter();
} }
} }

@ -27,7 +27,6 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.UrlUtils; import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.metadata.provider.ReactiveMetadataProvider; import com.tencent.cloud.metadata.provider.ReactiveMetadataProvider;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.api.utils.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -54,11 +53,6 @@ import static com.tencent.polaris.metadata.core.constant.MetadataConstants.LOCAL
public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered { public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered {
private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataReactiveFilter.class); private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataReactiveFilter.class);
private PolarisContextProperties polarisContextProperties;
public DecodeTransferMetadataReactiveFilter(PolarisContextProperties polarisContextProperties) {
this.polarisContextProperties = polarisContextProperties;
}
@Override @Override
public int getOrder() { public int getOrder() {

@ -27,7 +27,6 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.UrlUtils; import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.metadata.provider.ServletMetadataProvider; import com.tencent.cloud.metadata.provider.ServletMetadataProvider;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.api.utils.StringUtils;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
@ -56,12 +55,6 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataServletFilter.class); private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataServletFilter.class);
private PolarisContextProperties polarisContextProperties;
public DecodeTransferMetadataServletFilter(PolarisContextProperties polarisContextProperties) {
this.polarisContextProperties = polarisContextProperties;
}
@Override @Override
protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest, protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest,
@NonNull HttpServletResponse httpServletResponse, FilterChain filterChain) @NonNull HttpServletResponse httpServletResponse, FilterChain filterChain)

@ -20,7 +20,6 @@ package com.tencent.cloud.metadata.core;
import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -55,7 +54,7 @@ public class DecodeTransferMetadataReactiveFilterTest {
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
this.metadataReactiveFilter = new DecodeTransferMetadataReactiveFilter(new PolarisContextProperties()); this.metadataReactiveFilter = new DecodeTransferMetadataReactiveFilter();
} }
@Test @Test

@ -1,73 +0,0 @@
/*
* 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.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.specification.api.v1.model.ModelProto;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto;
import org.springframework.util.CollectionUtils;
/**
* resolve labels from rate limit rule.
*
*@author lepdou 2022-05-13
*/
@Deprecated
public class RateLimitRuleLabelResolver {
private final ServiceRuleManager serviceRuleManager;
public RateLimitRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
this.serviceRuleManager = serviceRuleManager;
}
public Set<String> getExpressionLabelKeys(String namespace, String service) {
RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service);
if (rateLimitRule == null) {
return Collections.emptySet();
}
List<RateLimitProto.Rule> rules = rateLimitRule.getRulesList();
if (CollectionUtils.isEmpty(rules)) {
return Collections.emptySet();
}
Set<String> expressionLabels = new HashSet<>();
for (RateLimitProto.Rule rule : rules) {
Map<String, ModelProto.MatchString> labels = rule.getLabelsMap();
if (CollectionUtils.isEmpty(labels)) {
return Collections.emptySet();
}
for (String key : labels.keySet()) {
if (ExpressionLabelUtils.isExpressionLabel(key)) {
expressionLabels.add(key);
}
}
}
return expressionLabels;
}
}

@ -20,14 +20,9 @@ package com.tencent.cloud.polaris.ratelimit.config;
import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager; import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiveResolver;
import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServletResolver;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -63,20 +58,13 @@ public class PolarisRateLimitAutoConfiguration {
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
protected static class QuotaCheckFilterConfig { protected static class QuotaCheckFilterConfig {
@Bean
public RateLimitRuleArgumentServletResolver rateLimitRuleArgumentResolver(ServiceRuleManager serviceRuleManager,
@Autowired(required = false) PolarisRateLimiterLabelServletResolver labelResolver) {
return new RateLimitRuleArgumentServletResolver(serviceRuleManager, labelResolver);
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public QuotaCheckServletFilter quotaCheckFilter(PolarisSDKContextManager polarisSDKContextManager, public QuotaCheckServletFilter quotaCheckFilter(PolarisSDKContextManager polarisSDKContextManager,
PolarisRateLimitProperties polarisRateLimitProperties, PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleArgumentServletResolver rateLimitRuleArgumentResolver,
@Autowired(required = false) PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { @Autowired(required = false) PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) {
return new QuotaCheckServletFilter(polarisSDKContextManager.getLimitAPI(), polarisRateLimitProperties, return new QuotaCheckServletFilter(polarisSDKContextManager.getLimitAPI(), polarisSDKContextManager.getAssemblyAPI(),
rateLimitRuleArgumentResolver, polarisRateLimiterLimitedFallback); polarisRateLimitProperties, polarisRateLimiterLimitedFallback);
} }
@Bean @Bean
@ -97,21 +85,14 @@ public class PolarisRateLimitAutoConfiguration {
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
protected static class MetadataReactiveFilterConfig { protected static class QuotaCheckReactiveFilterConfig {
@Bean
public RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentResolver(ServiceRuleManager serviceRuleManager,
@Autowired(required = false) PolarisRateLimiterLabelReactiveResolver labelResolver) {
return new RateLimitRuleArgumentReactiveResolver(serviceRuleManager, labelResolver);
}
@Bean @Bean
public QuotaCheckReactiveFilter quotaCheckReactiveFilter(PolarisSDKContextManager polarisSDKContextManager, public QuotaCheckReactiveFilter quotaCheckReactiveFilter(PolarisSDKContextManager polarisSDKContextManager,
PolarisRateLimitProperties polarisRateLimitProperties, PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentResolver,
@Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { @Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) {
return new QuotaCheckReactiveFilter(polarisSDKContextManager.getLimitAPI(), polarisRateLimitProperties, return new QuotaCheckReactiveFilter(polarisSDKContextManager.getLimitAPI(), polarisSDKContextManager.getAssemblyAPI(),
rateLimitRuleArgumentResolver, polarisRateLimiterLimitedFallback); polarisRateLimitProperties, polarisRateLimiterLimitedFallback);
} }
} }
} }

@ -23,19 +23,18 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiveResolver;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback;
import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils; import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils;
import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils; import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils;
import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.assembly.api.AssemblyAPI;
import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode; import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
@ -53,6 +52,7 @@ import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebFilterChain;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static org.springframework.core.io.buffer.DefaultDataBufferFactory.DEFAULT_INITIAL_CAPACITY;
/** /**
* Reactive filter to check quota. * Reactive filter to check quota.
@ -65,22 +65,20 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
private final LimitAPI limitAPI; private final LimitAPI limitAPI;
private final PolarisRateLimitProperties polarisRateLimitProperties; private final AssemblyAPI assemblyAPI;
private final RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentResolver; private final PolarisRateLimitProperties polarisRateLimitProperties;
private final PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback; private final PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback;
private String rejectTips; private String rejectTips;
public QuotaCheckReactiveFilter(LimitAPI limitAPI, public QuotaCheckReactiveFilter(LimitAPI limitAPI, AssemblyAPI assemblyAPI,
PolarisRateLimitProperties polarisRateLimitProperties, PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentResolver,
@Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { @Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) {
this.limitAPI = limitAPI; this.limitAPI = limitAPI;
this.assemblyAPI = assemblyAPI;
this.polarisRateLimitProperties = polarisRateLimitProperties; this.polarisRateLimitProperties = polarisRateLimitProperties;
this.rateLimitRuleArgumentResolver = rateLimitRuleArgumentResolver;
this.polarisRateLimiterLimitedFallback = polarisRateLimiterLimitedFallback; this.polarisRateLimiterLimitedFallback = polarisRateLimiterLimitedFallback;
} }
@ -99,31 +97,41 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE; String localService = MetadataContext.LOCAL_SERVICE;
Set<Argument> arguments = rateLimitRuleArgumentResolver.getArguments(exchange, localNamespace, localService);
long waitMs = -1; long waitMs = -1;
QuotaResponse quotaResponse = null;
try { try {
String path = exchange.getRequest().getURI().getPath(); String path = exchange.getRequest().getURI().getPath();
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota( quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, path);
limitAPI, localNamespace, localService, 1, arguments, path);
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
ServerHttpResponse response = exchange.getResponse(); ServerHttpResponse response = exchange.getResponse();
DataBuffer dataBuffer; DataBuffer dataBuffer;
if (!Objects.isNull(polarisRateLimiterLimitedFallback)) { if (Objects.nonNull(quotaResponse.getActiveRule())
&& StringUtils.isNotBlank(quotaResponse.getActiveRule().getCustomResponse().getBody())) {
response.setRawStatusCode(polarisRateLimitProperties.getRejectHttpCode());
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
dataBuffer = response.bufferFactory().allocateBuffer(DEFAULT_INITIAL_CAPACITY)
.write(quotaResponse.getActiveRule().getCustomResponse().getBody()
.getBytes(StandardCharsets.UTF_8));
}
else if (!Objects.isNull(polarisRateLimiterLimitedFallback)) {
response.setRawStatusCode(polarisRateLimiterLimitedFallback.rejectHttpCode()); response.setRawStatusCode(polarisRateLimiterLimitedFallback.rejectHttpCode());
response.getHeaders().setContentType(polarisRateLimiterLimitedFallback.mediaType()); response.getHeaders().setContentType(polarisRateLimiterLimitedFallback.mediaType());
dataBuffer = response.bufferFactory().allocateBuffer() dataBuffer = response.bufferFactory().allocateBuffer(DEFAULT_INITIAL_CAPACITY)
.write(polarisRateLimiterLimitedFallback.rejectTips() .write(polarisRateLimiterLimitedFallback.rejectTips()
.getBytes(polarisRateLimiterLimitedFallback.charset())); .getBytes(polarisRateLimiterLimitedFallback.charset()));
} }
else { else {
response.setRawStatusCode(polarisRateLimitProperties.getRejectHttpCode()); response.setRawStatusCode(polarisRateLimitProperties.getRejectHttpCode());
response.getHeaders().setContentType(MediaType.TEXT_HTML); response.getHeaders().setContentType(MediaType.TEXT_HTML);
dataBuffer = response.bufferFactory().allocateBuffer() dataBuffer = response.bufferFactory().allocateBuffer(DEFAULT_INITIAL_CAPACITY)
.write(rejectTips.getBytes(StandardCharsets.UTF_8)); .write(rejectTips.getBytes(StandardCharsets.UTF_8));
} }
// set flow control to header
response.getHeaders() response.getHeaders()
.add(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc()); .add(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc());
// set trace span
RateLimitUtils.reportTrace(assemblyAPI, quotaResponse.getActiveRule().getId().getValue());
if (Objects.nonNull(quotaResponse.getActiveRule())) { if (Objects.nonNull(quotaResponse.getActiveRule())) {
try { try {
String encodedActiveRuleName = URLEncoder.encode( String encodedActiveRuleName = URLEncoder.encode(
@ -135,6 +143,7 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
quotaResponse.getActiveRuleName(), e); quotaResponse.getActiveRuleName(), e);
} }
} }
RateLimitUtils.release(quotaResponse);
return response.writeWith(Mono.just(dataBuffer)); return response.writeWith(Mono.just(dataBuffer));
} }
// Unirate // Unirate
@ -149,11 +158,13 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
LOG.error("fail to invoke getQuota, service is " + localService, t); LOG.error("fail to invoke getQuota, service is " + localService, t);
} }
QuotaResponse finalQuotaResponse = quotaResponse;
if (waitMs > 0) { if (waitMs > 0) {
return Mono.delay(Duration.ofMillis(waitMs)).flatMap(e -> chain.filter(exchange)); return Mono.delay(Duration.ofMillis(waitMs))
.flatMap(e -> chain.filter(exchange).doFinally((v) -> RateLimitUtils.release(finalQuotaResponse)));
} }
else { else {
return chain.filter(exchange); return chain.filter(exchange).doFinally((v) -> RateLimitUtils.release(finalQuotaResponse));
} }
} }

@ -22,19 +22,18 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServletResolver;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback;
import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils; import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils;
import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils; import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils;
import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.assembly.api.AssemblyAPI;
import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode; import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
@ -68,21 +67,20 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(QuotaCheckServletFilter.class); private static final Logger LOG = LoggerFactory.getLogger(QuotaCheckServletFilter.class);
private final LimitAPI limitAPI; private final LimitAPI limitAPI;
private final PolarisRateLimitProperties polarisRateLimitProperties; private final AssemblyAPI assemblyAPI;
private final RateLimitRuleArgumentServletResolver rateLimitRuleArgumentResolver; private final PolarisRateLimitProperties polarisRateLimitProperties;
private final PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback; private final PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback;
private String rejectTips; private String rejectTips;
public QuotaCheckServletFilter(LimitAPI limitAPI, public QuotaCheckServletFilter(LimitAPI limitAPI, AssemblyAPI assemblyAPI,
PolarisRateLimitProperties polarisRateLimitProperties, PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleArgumentServletResolver rateLimitRuleArgumentResolver,
@Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { @Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) {
this.limitAPI = limitAPI; this.limitAPI = limitAPI;
this.assemblyAPI = assemblyAPI;
this.polarisRateLimitProperties = polarisRateLimitProperties; this.polarisRateLimitProperties = polarisRateLimitProperties;
this.rateLimitRuleArgumentResolver = rateLimitRuleArgumentResolver;
this.polarisRateLimiterLimitedFallback = polarisRateLimiterLimitedFallback; this.polarisRateLimiterLimitedFallback = polarisRateLimiterLimitedFallback;
} }
@ -96,15 +94,17 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
@NonNull FilterChain filterChain) throws ServletException, IOException { @NonNull FilterChain filterChain) throws ServletException, IOException {
String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE; String localService = MetadataContext.LOCAL_SERVICE;
QuotaResponse quotaResponse = null;
Set<Argument> arguments = rateLimitRuleArgumentResolver.getArguments(request, localNamespace, localService);
try { try {
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, request.getRequestURI());
localNamespace, localService, 1, arguments, request.getRequestURI());
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
if (!Objects.isNull(polarisRateLimiterLimitedFallback)) { if (Objects.nonNull(quotaResponse.getActiveRule())
&& StringUtils.isNotBlank(quotaResponse.getActiveRule().getCustomResponse().getBody())) {
response.setStatus(polarisRateLimitProperties.getRejectHttpCode());
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write(quotaResponse.getActiveRule().getCustomResponse().getBody());
}
else if (!Objects.isNull(polarisRateLimiterLimitedFallback)) {
response.setStatus(polarisRateLimiterLimitedFallback.rejectHttpCode()); response.setStatus(polarisRateLimiterLimitedFallback.rejectHttpCode());
String contentType = new MediaType(polarisRateLimiterLimitedFallback.mediaType(), polarisRateLimiterLimitedFallback.charset()).toString(); String contentType = new MediaType(polarisRateLimiterLimitedFallback.mediaType(), polarisRateLimiterLimitedFallback.charset()).toString();
response.setContentType(contentType); response.setContentType(contentType);
@ -115,7 +115,10 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
response.setContentType("text/html;charset=UTF-8"); response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(rejectTips); response.getWriter().write(rejectTips);
} }
// set flow control to header
response.addHeader(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc()); response.addHeader(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc());
// set trace span
RateLimitUtils.reportTrace(assemblyAPI, quotaResponse.getActiveRule().getId().getValue());
if (Objects.nonNull(quotaResponse.getActiveRule())) { if (Objects.nonNull(quotaResponse.getActiveRule())) {
try { try {
String encodedActiveRuleName = URLEncoder.encode( String encodedActiveRuleName = URLEncoder.encode(
@ -127,6 +130,7 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
quotaResponse.getActiveRuleName(), e); quotaResponse.getActiveRuleName(), e);
} }
} }
RateLimitUtils.release(quotaResponse);
return; return;
} }
// Unirate // Unirate
@ -142,7 +146,12 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
LOG.error("fail to invoke getQuota, service is " + localService, t); LOG.error("fail to invoke getQuota, service is " + localService, t);
} }
try {
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }
finally {
RateLimitUtils.release(quotaResponse);
}
}
} }

@ -1,128 +0,0 @@
/*
* 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.resolver;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME;
import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE;
/**
* resolve arguments from rate limit rule for Reactive.
*
* @author seansyyu 2023-03-09
*/
public class RateLimitRuleArgumentReactiveResolver {
private static final Logger LOG = LoggerFactory.getLogger(RateLimitRuleArgumentReactiveResolver.class);
private final ServiceRuleManager serviceRuleManager;
private final PolarisRateLimiterLabelReactiveResolver labelResolver;
public RateLimitRuleArgumentReactiveResolver(ServiceRuleManager serviceRuleManager, PolarisRateLimiterLabelReactiveResolver labelResolver) {
this.serviceRuleManager = serviceRuleManager;
this.labelResolver = labelResolver;
}
public Set<Argument> getArguments(ServerWebExchange request, String namespace, String service) {
RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service);
if (rateLimitRule == null) {
return Collections.emptySet();
}
List<RateLimitProto.Rule> rules = rateLimitRule.getRulesList();
if (CollectionUtils.isEmpty(rules)) {
return Collections.emptySet();
}
return rules.stream()
.flatMap(rule -> rule.getArgumentsList().stream())
.map(matchArgument -> {
String matchKey = matchArgument.getKey();
Argument argument = null;
switch (matchArgument.getType()) {
case CUSTOM:
argument = StringUtils.isBlank(matchKey) ? null :
Argument.buildCustom(matchKey, Optional.ofNullable(getCustomResolvedLabels(request).get(matchKey))
.orElse(StringUtils.EMPTY));
break;
case METHOD:
argument = Argument.buildMethod(request.getRequest().getMethod().name());
break;
case HEADER:
argument = StringUtils.isBlank(matchKey) ? null :
Argument.buildHeader(matchKey, Optional.ofNullable(request.getRequest().getHeaders()
.getFirst(matchKey)).orElse(StringUtils.EMPTY));
break;
case QUERY:
argument = StringUtils.isBlank(matchKey) ? null :
Argument.buildQuery(matchKey, Optional.ofNullable(request.getRequest().getQueryParams()
.getFirst(matchKey)).orElse(StringUtils.EMPTY));
break;
case CALLER_SERVICE:
String sourceServiceNamespace = MetadataContextHolder.getDisposableMetadata(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, true)
.orElse(StringUtils.EMPTY);
String sourceServiceName = MetadataContextHolder.getDisposableMetadata(DEFAULT_METADATA_SOURCE_SERVICE_NAME, true)
.orElse(StringUtils.EMPTY);
if (!StringUtils.isEmpty(sourceServiceNamespace) && !StringUtils.isEmpty(sourceServiceName)) {
argument = Argument.buildCallerService(sourceServiceNamespace, sourceServiceName);
}
break;
case CALLER_IP:
InetSocketAddress remoteAddress = request.getRequest().getRemoteAddress();
argument = Argument.buildCallerIP(remoteAddress != null ? remoteAddress.getAddress()
.getHostAddress() : StringUtils.EMPTY);
break;
default:
break;
}
return argument;
}).filter(Objects::nonNull).collect(Collectors.toSet());
}
private Map<String, String> getCustomResolvedLabels(ServerWebExchange request) {
if (labelResolver != null) {
try {
return labelResolver.resolve(request);
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e);
}
}
return Collections.emptyMap();
}
}

@ -1,122 +0,0 @@
/*
* 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.resolver;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME;
import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE;
/**
* resolve arguments from rate limit rule for Servlet.
*
* @author seansyyu 2023-03-09
*/
public class RateLimitRuleArgumentServletResolver {
private static final Logger LOG = LoggerFactory.getLogger(QuotaCheckServletFilter.class);
private final ServiceRuleManager serviceRuleManager;
private final PolarisRateLimiterLabelServletResolver labelResolver;
public RateLimitRuleArgumentServletResolver(ServiceRuleManager serviceRuleManager, PolarisRateLimiterLabelServletResolver labelResolver) {
this.serviceRuleManager = serviceRuleManager;
this.labelResolver = labelResolver;
}
public Set<Argument> getArguments(HttpServletRequest request, String namespace, String service) {
RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service);
if (rateLimitRule == null) {
return Collections.emptySet();
}
List<RateLimitProto.Rule> rules = rateLimitRule.getRulesList();
if (CollectionUtils.isEmpty(rules)) {
return Collections.emptySet();
}
return rules.stream()
.flatMap(rule -> rule.getArgumentsList().stream())
.map(matchArgument -> {
String matchKey = matchArgument.getKey();
Argument argument = null;
switch (matchArgument.getType()) {
case CUSTOM:
argument = StringUtils.isBlank(matchKey) ? null :
Argument.buildCustom(matchKey, Optional.ofNullable(getCustomResolvedLabels(request).get(matchKey)).orElse(StringUtils.EMPTY));
break;
case METHOD:
argument = Argument.buildMethod(request.getMethod());
break;
case HEADER:
argument = StringUtils.isBlank(matchKey) ? null :
Argument.buildHeader(matchKey, Optional.ofNullable(request.getHeader(matchKey)).orElse(StringUtils.EMPTY));
break;
case QUERY:
argument = StringUtils.isBlank(matchKey) ? null :
Argument.buildQuery(matchKey, Optional.ofNullable(request.getParameter(matchKey)).orElse(StringUtils.EMPTY));
break;
case CALLER_SERVICE:
String sourceServiceNamespace = MetadataContextHolder.getDisposableMetadata(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, true).orElse(StringUtils.EMPTY);
String sourceServiceName = MetadataContextHolder.getDisposableMetadata(DEFAULT_METADATA_SOURCE_SERVICE_NAME, true).orElse(StringUtils.EMPTY);
if (!StringUtils.isEmpty(sourceServiceNamespace) && !StringUtils.isEmpty(sourceServiceName)) {
argument = Argument.buildCallerService(sourceServiceNamespace, sourceServiceName);
}
break;
case CALLER_IP:
argument = Argument.buildCallerIP(Optional.ofNullable(request.getRemoteAddr()).orElse(StringUtils.EMPTY));
break;
default:
break;
}
return argument;
}).filter(Objects::nonNull).collect(Collectors.toSet());
}
private Map<String, String> getCustomResolvedLabels(HttpServletRequest request) {
if (labelResolver != null) {
try {
return labelResolver.resolve(request);
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e);
}
}
return Collections.emptyMap();
}
}

@ -1,38 +0,0 @@
/*
* 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.spi;
import java.util.Map;
import org.springframework.web.server.ServerWebExchange;
/**
* Resolve custom label from request. The label used for rate limit params.
*
* @author lepdou 2022-03-31
*/
public interface PolarisRateLimiterLabelReactiveResolver {
/**
* Resolve custom label from request.
* @param exchange the http request
* @return resolved labels
*/
Map<String, String> resolve(ServerWebExchange exchange);
}

@ -0,0 +1,44 @@
/*
* 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.tsf;
import com.tencent.cloud.common.tsf.ConditionalOnTsfConsulEnabled;
import com.tencent.cloud.polaris.context.config.extend.consul.ConsulProperties;
import com.tencent.cloud.polaris.context.config.extend.tsf.TsfCoreProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* Auto configuration of TSF rate limit.
*
* @author Haotian Zhang
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnTsfConsulEnabled
public class TsfRateLimitAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TsfRateLimitConfigModifier tsfRateLimitConfigModifier(TsfCoreProperties tsfCoreProperties,
ConsulProperties consulProperties, Environment environment) {
return new TsfRateLimitConfigModifier(tsfCoreProperties, consulProperties, environment);
}
}

@ -13,26 +13,22 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.ratelimit.spi; package com.tencent.cloud.polaris.ratelimit.tsf;
import java.util.Map; import com.tencent.cloud.common.tsf.ConditionalOnTsfConsulEnabled;
import jakarta.servlet.http.HttpServletRequest; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/** /**
* Resolve custom label from request. The label used for rate limit params. * Bootstrap configuration for TSF rate limit.
* *
* @author lepdou 2022-03-31 * @author Haotian Zhang
*/
public interface PolarisRateLimiterLabelServletResolver {
/**
* Resolve custom label from request.
* @param request the http request
* @return resolved labels
*/ */
Map<String, String> resolve(HttpServletRequest request); @Configuration(proxyBeanMethods = false)
@ConditionalOnTsfConsulEnabled
@Import(TsfRateLimitAutoConfiguration.class)
public class TsfRateLimitBootstrapConfiguration {
} }

@ -0,0 +1,68 @@
/*
* 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.tsf;
import java.util.Map;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.polaris.context.PolarisConfigModifier;
import com.tencent.cloud.polaris.context.config.extend.consul.ConsulProperties;
import com.tencent.cloud.polaris.context.config.extend.tsf.TsfContextUtils;
import com.tencent.cloud.polaris.context.config.extend.tsf.TsfCoreProperties;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import com.tencent.polaris.ratelimit.client.sync.tsf.TsfRateLimitConstants;
import org.springframework.core.env.Environment;
/**
* Config modifier for TSF rate limit.
*
* @author Haotian Zhang
*/
public class TsfRateLimitConfigModifier implements PolarisConfigModifier {
private final TsfCoreProperties tsfCoreProperties;
private final ConsulProperties consulProperties;
private final Environment environment;
public TsfRateLimitConfigModifier(TsfCoreProperties tsfCoreProperties, ConsulProperties consulProperties,
Environment environment) {
this.tsfCoreProperties = tsfCoreProperties;
this.consulProperties = consulProperties;
this.environment = environment;
}
@Override
public void modify(ConfigurationImpl configuration) {
if (TsfContextUtils.isTsfConsulEnabled(environment)) {
Map<String, String> metadata = configuration.getProvider().getRateLimit().getMetadata();
metadata.put(TsfRateLimitConstants.RATE_LIMIT_MASTER_IP_KEY, tsfCoreProperties.getRatelimitMasterIp());
metadata.put(TsfRateLimitConstants.RATE_LIMIT_MASTER_PORT_KEY, String.valueOf(tsfCoreProperties.getRatelimitMasterPort()));
metadata.put(TsfRateLimitConstants.SERVICE_NAME_KEY, tsfCoreProperties.getServiceName());
metadata.put(TsfRateLimitConstants.INSTANCE_ID_KEY, tsfCoreProperties.getInstanceId());
metadata.put(TsfRateLimitConstants.TOKEN_KEY, consulProperties.getAclToken());
}
}
@Override
public int getOrder() {
return OrderConstant.Modifier.RATE_LIMIT_ORDER;
}
}

@ -17,12 +17,9 @@
package com.tencent.cloud.polaris.ratelimit.utils; package com.tencent.cloud.polaris.ratelimit.utils;
import java.util.Map; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import java.util.Set;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult; import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest; import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -40,34 +37,13 @@ public final class QuotaCheckUtils {
private QuotaCheckUtils() { private QuotaCheckUtils() {
} }
@Deprecated public static QuotaResponse getQuota(LimitAPI limitAPI, String namespace, String service, int count, String method) {
public static QuotaResponse getQuota(LimitAPI limitAPI, String namespace, String service, int count,
Map<String, String> labels, String method) {
// build quota request
QuotaRequest quotaRequest = new QuotaRequest();
quotaRequest.setNamespace(namespace);
quotaRequest.setService(service);
quotaRequest.setCount(count);
quotaRequest.setLabels(labels);
quotaRequest.setMethod(method);
try {
return limitAPI.getQuota(quotaRequest);
}
catch (Throwable throwable) {
LOG.error("fail to invoke getQuota of LimitAPI with QuotaRequest[{}].", quotaRequest, throwable);
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "get quota failed"));
}
}
public static QuotaResponse getQuota(LimitAPI limitAPI, String namespace, String service, int count,
Set<Argument> arguments, String method) {
// build quota request // build quota request
QuotaRequest quotaRequest = new QuotaRequest(); QuotaRequest quotaRequest = new QuotaRequest();
quotaRequest.setNamespace(namespace); quotaRequest.setNamespace(namespace);
quotaRequest.setService(service); quotaRequest.setService(service);
quotaRequest.setCount(count); quotaRequest.setCount(count);
quotaRequest.setArguments(arguments); quotaRequest.setMetadataContext(MetadataContextHolder.get());
quotaRequest.setMethod(method); quotaRequest.setMethod(method);
try { try {

@ -18,9 +18,17 @@
package com.tencent.cloud.polaris.ratelimit.utils; package com.tencent.cloud.polaris.ratelimit.utils;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.util.ResourceFileUtils; import com.tencent.cloud.common.util.ResourceFileUtils;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.polaris.api.plugin.stat.TraceConstants;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.assembly.api.AssemblyAPI;
import com.tencent.polaris.assembly.api.pojo.TraceAttributes;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -63,4 +71,33 @@ public final class RateLimitUtils {
return RateLimitConstant.QUOTA_LIMITED_INFO; return RateLimitConstant.QUOTA_LIMITED_INFO;
} }
public static void reportTrace(AssemblyAPI assemblyAPI, String ruleId) {
try {
if (assemblyAPI != null) {
Map<String, String> attributes = new HashMap<>();
attributes.put(TraceConstants.RateLimitRuleId, ruleId);
TraceAttributes traceAttributes = new TraceAttributes();
traceAttributes.setAttributes(attributes);
traceAttributes.setAttributeLocation(TraceAttributes.AttributeLocation.SPAN);
assemblyAPI.updateTraceAttributes(traceAttributes);
}
}
catch (Throwable throwable) {
LOG.warn("[RateLimit] Report rule id {} to trace error.", ruleId, throwable);
}
}
public static void release(QuotaResponse quotaResponse) {
if (quotaResponse != null && CollectionUtils.isNotEmpty(quotaResponse.getReleaseList())) {
for (Runnable release : quotaResponse.getReleaseList()) {
try {
release.run();
}
catch (Throwable throwable) {
LOG.warn("[RateLimit] Release error.", throwable);
}
}
}
}
} }

@ -1,2 +1,3 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitPropertiesBootstrapConfiguration com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitPropertiesBootstrapConfiguration,\
com.tencent.cloud.polaris.ratelimit.tsf.TsfRateLimitBootstrapConfiguration

@ -1,3 +1,4 @@
com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitAutoConfiguration com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitAutoConfiguration
com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitPropertiesAutoConfiguration com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitPropertiesAutoConfiguration
com.tencent.cloud.polaris.ratelimit.endpoint.PolarisRateLimitRuleEndpointAutoConfiguration com.tencent.cloud.polaris.ratelimit.endpoint.PolarisRateLimitRuleEndpointAutoConfiguration
com.tencent.cloud.polaris.ratelimit.tsf.TsfRateLimitAutoConfiguration

@ -17,15 +17,16 @@
package com.tencent.cloud.polaris.context; package com.tencent.cloud.polaris.context;
import com.google.protobuf.StringValue;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServletResolver;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback;
import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode; import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
import com.tencent.polaris.ratelimit.factory.LimitAPIFactory; import com.tencent.polaris.ratelimit.factory.LimitAPIFactory;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto;
import com.tencent.polaris.test.mock.discovery.NamingServer; import com.tencent.polaris.test.mock.discovery.NamingServer;
import com.tencent.polaris.test.mock.discovery.NamingService; import com.tencent.polaris.test.mock.discovery.NamingService;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
@ -118,6 +119,13 @@ public class CalleeControllerTests {
QuotaResponse quotaResponse = mock(QuotaResponse.class); QuotaResponse quotaResponse = mock(QuotaResponse.class);
when(quotaResponse.getCode()).thenReturn(QuotaResultCode.QuotaResultLimited); when(quotaResponse.getCode()).thenReturn(QuotaResultCode.QuotaResultLimited);
when(quotaResponse.getInfo()).thenReturn("Testing rate limit after 10 times success."); when(quotaResponse.getInfo()).thenReturn("Testing rate limit after 10 times success.");
RateLimitProto.Rule rule = mock(RateLimitProto.Rule.class);
when(rule.getId()).thenReturn(StringValue.of("rate-test"));
RateLimitProto.CustomResponse customResponse = mock(RateLimitProto.CustomResponse.class);
when(customResponse.getBody()).thenReturn("limited");
when(rule.getCustomResponse()).thenReturn(customResponse);
when(quotaResponse.getActiveRule()).thenReturn(rule);
when(quotaResponse.getActiveRuleName()).thenReturn("rate-test");
when(limitAPI.getQuota(any())).thenReturn(quotaResponse); when(limitAPI.getQuota(any())).thenReturn(quotaResponse);
} }
String result = restTemplate.getForObject(url, String.class); String result = restTemplate.getForObject(url, String.class);
@ -126,7 +134,7 @@ public class CalleeControllerTests {
} }
catch (RestClientException e) { catch (RestClientException e) {
if (e instanceof TooManyRequests) { if (e instanceof TooManyRequests) {
System.out.println(((TooManyRequests) e).getResponseBodyAsString()); assertThat(((TooManyRequests) e).getResponseBodyAsString()).isEqualTo("limited");
hasLimited = true; hasLimited = true;
} }
else { else {
@ -157,10 +165,8 @@ public class CalleeControllerTests {
@Primary @Primary
public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI, public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI,
PolarisRateLimitProperties polarisRateLimitProperties, PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleArgumentServletResolver rateLimitRuleArgumentResolver,
@Autowired(required = false) PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { @Autowired(required = false) PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) {
return new QuotaCheckServletFilter(limitAPI, polarisRateLimitProperties, return new QuotaCheckServletFilter(limitAPI, null, polarisRateLimitProperties, polarisRateLimiterLimitedFallback);
rateLimitRuleArgumentResolver, polarisRateLimiterLimitedFallback);
} }
} }
} }

@ -1,99 +0,0 @@
/*
* 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.specification.api.v1.model.ModelProto;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
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
*/
@ExtendWith(MockitoExtension.class)
public class RateLimitRuleLabelResolverTest {
private RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
@BeforeEach
void setUp() {
ServiceRuleManager 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}");
}
}

@ -20,8 +20,6 @@ package com.tencent.cloud.polaris.ratelimit.config;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiveResolver;
import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServletResolver;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -53,12 +51,10 @@ public class PolarisRateLimitAutoConfigurationTest {
PolarisRateLimitProperties.class, PolarisRateLimitProperties.class,
PolarisRateLimitAutoConfiguration.class)) PolarisRateLimitAutoConfiguration.class))
.run(context -> { .run(context -> {
assertThat(context).doesNotHaveBean(RateLimitRuleArgumentServletResolver.class);
assertThat(context).doesNotHaveBean(RateLimitRuleArgumentReactiveResolver.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.QuotaCheckFilterConfig.class); assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class); assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class);
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class); assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.MetadataReactiveFilterConfig.class); assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.QuotaCheckReactiveFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class); assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class);
}); });
} }
@ -71,13 +67,11 @@ public class PolarisRateLimitAutoConfigurationTest {
PolarisRateLimitProperties.class, PolarisRateLimitProperties.class,
PolarisRateLimitAutoConfiguration.class)) PolarisRateLimitAutoConfiguration.class))
.run(context -> { .run(context -> {
assertThat(context).hasSingleBean(RateLimitRuleArgumentServletResolver.class);
assertThat(context).hasSingleBean(PolarisRateLimitAutoConfiguration.QuotaCheckFilterConfig.class); assertThat(context).hasSingleBean(PolarisRateLimitAutoConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).hasSingleBean(QuotaCheckServletFilter.class); assertThat(context).hasSingleBean(QuotaCheckServletFilter.class);
assertThat(context).hasSingleBean(FilterRegistrationBean.class); assertThat(context).hasSingleBean(FilterRegistrationBean.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.MetadataReactiveFilterConfig.class); assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.QuotaCheckReactiveFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class); assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class);
assertThat(context).doesNotHaveBean(RateLimitRuleArgumentReactiveResolver.class);
}); });
} }
@ -89,12 +83,10 @@ public class PolarisRateLimitAutoConfigurationTest {
PolarisRateLimitProperties.class, PolarisRateLimitProperties.class,
PolarisRateLimitAutoConfiguration.class)) PolarisRateLimitAutoConfiguration.class))
.run(context -> { .run(context -> {
assertThat(context).doesNotHaveBean(RateLimitRuleArgumentServletResolver.class);
assertThat(context).hasSingleBean(RateLimitRuleArgumentReactiveResolver.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.QuotaCheckFilterConfig.class); assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class); assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class);
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class); assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
assertThat(context).hasSingleBean(PolarisRateLimitAutoConfiguration.MetadataReactiveFilterConfig.class); assertThat(context).hasSingleBean(PolarisRateLimitAutoConfiguration.QuotaCheckReactiveFilterConfig.class);
assertThat(context).hasSingleBean(QuotaCheckReactiveFilter.class); assertThat(context).hasSingleBean(QuotaCheckReactiveFilter.class);
}); });
} }

@ -34,8 +34,6 @@ import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiveResolver;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult; import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.core.LimitAPI;
@ -76,8 +74,6 @@ import static org.mockito.Mockito.when;
@SpringBootTest(classes = QuotaCheckReactiveFilterTest.TestApplication.class, @SpringBootTest(classes = QuotaCheckReactiveFilterTest.TestApplication.class,
properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"}) properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"})
public class QuotaCheckReactiveFilterTest { public class QuotaCheckReactiveFilterTest {
private final PolarisRateLimiterLabelReactiveResolver labelResolver =
exchange -> Collections.singletonMap("xxx", "xxx");
private QuotaCheckReactiveFilter quotaCheckReactiveFilter; private QuotaCheckReactiveFilter quotaCheckReactiveFilter;
private QuotaCheckReactiveFilter quotaCheckWithRateLimiterLimitedFallbackReactiveFilter; private QuotaCheckReactiveFilter quotaCheckWithRateLimiterLimitedFallbackReactiveFilter;
private PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback; private PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback;
@ -126,10 +122,10 @@ public class QuotaCheckReactiveFilterTest {
RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.newBuilder().addRules(rateLimitRule).build(); RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.newBuilder().addRules(rateLimitRule).build();
when(serviceRuleManager.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit); when(serviceRuleManager.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit);
RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentReactiveResolver = new RateLimitRuleArgumentReactiveResolver(serviceRuleManager, labelResolver); this.quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(limitAPI, null, polarisRateLimitProperties, null);
this.quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(limitAPI, polarisRateLimitProperties, rateLimitRuleArgumentReactiveResolver, null);
this.polarisRateLimiterLimitedFallback = new JsonPolarisRateLimiterLimitedFallback(); this.polarisRateLimiterLimitedFallback = new JsonPolarisRateLimiterLimitedFallback();
this.quotaCheckWithRateLimiterLimitedFallbackReactiveFilter = new QuotaCheckReactiveFilter(limitAPI, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleArgumentReactiveResolver, polarisRateLimiterLimitedFallback); this.quotaCheckWithRateLimiterLimitedFallbackReactiveFilter = new QuotaCheckReactiveFilter(limitAPI, null,
polarisRateLimitWithHtmlRejectTipsProperties, polarisRateLimiterLimitedFallback);
} }
@Test @Test

@ -23,7 +23,6 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
@ -33,8 +32,6 @@ import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServletResolver;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult; import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.core.LimitAPI;
@ -74,8 +71,6 @@ import static org.mockito.Mockito.when;
}) })
public class QuotaCheckServletFilterTest { public class QuotaCheckServletFilterTest {
private final PolarisRateLimiterLabelServletResolver labelResolver =
exchange -> Collections.singletonMap("xxx", "xxx");
private QuotaCheckServletFilter quotaCheckServletFilter; private QuotaCheckServletFilter quotaCheckServletFilter;
private QuotaCheckServletFilter quotaCheckWithHtmlRejectTipsServletFilter; private QuotaCheckServletFilter quotaCheckWithHtmlRejectTipsServletFilter;
private QuotaCheckServletFilter quotaCheckWithRateLimiterLimitedFallbackFilter; private QuotaCheckServletFilter quotaCheckWithRateLimiterLimitedFallbackFilter;
@ -96,7 +91,8 @@ public class QuotaCheckServletFilterTest {
} }
else if (serviceName.equals("TestApp3")) { else if (serviceName.equals("TestApp3")) {
QuotaResponse response = new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited")); QuotaResponse response = new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
response.setActiveRule(RateLimitProto.Rule.newBuilder().setName(StringValue.newBuilder().setValue("MOCK_RULE").build()).build()); response.setActiveRule(RateLimitProto.Rule.newBuilder()
.setName(StringValue.newBuilder().setValue("MOCK_RULE").build()).build());
return response; return response;
} }
else { else {
@ -115,18 +111,21 @@ public class QuotaCheckServletFilterTest {
ServiceRuleManager serviceRuleManager = mock(ServiceRuleManager.class); ServiceRuleManager serviceRuleManager = mock(ServiceRuleManager.class);
RateLimitProto.Rule.Builder ratelimitRuleBuilder = RateLimitProto.Rule.newBuilder(); RateLimitProto.Rule.Builder ratelimitRuleBuilder = RateLimitProto.Rule.newBuilder();
InputStream inputStream = QuotaCheckServletFilterTest.class.getClassLoader().getResourceAsStream("ratelimit.json"); InputStream inputStream = QuotaCheckServletFilterTest.class.getClassLoader()
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("")); .getResourceAsStream("ratelimit.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, ratelimitRuleBuilder); JsonFormat.parser().ignoringUnknownFields().merge(json, ratelimitRuleBuilder);
RateLimitProto.Rule rateLimitRule = ratelimitRuleBuilder.build(); RateLimitProto.Rule rateLimitRule = ratelimitRuleBuilder.build();
RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.newBuilder().addRules(rateLimitRule).build(); RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.newBuilder().addRules(rateLimitRule).build();
when(serviceRuleManager.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit); when(serviceRuleManager.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit);
RateLimitRuleArgumentServletResolver rateLimitRuleArgumentServletResolver = new RateLimitRuleArgumentServletResolver(serviceRuleManager, labelResolver); this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, null, polarisRateLimitProperties, null);
this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, polarisRateLimitProperties, rateLimitRuleArgumentServletResolver, null); this.quotaCheckWithHtmlRejectTipsServletFilter = new QuotaCheckServletFilter(limitAPI, null,
this.quotaCheckWithHtmlRejectTipsServletFilter = new QuotaCheckServletFilter(limitAPI, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleArgumentServletResolver, null); polarisRateLimitWithHtmlRejectTipsProperties, null);
this.polarisRateLimiterLimitedFallback = new JsonPolarisRateLimiterLimitedFallback(); this.polarisRateLimiterLimitedFallback = new JsonPolarisRateLimiterLimitedFallback();
this.quotaCheckWithRateLimiterLimitedFallbackFilter = new QuotaCheckServletFilter(limitAPI, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleArgumentServletResolver, polarisRateLimiterLimitedFallback); this.quotaCheckWithRateLimiterLimitedFallbackFilter = new QuotaCheckServletFilter(limitAPI, null,
polarisRateLimitWithHtmlRejectTipsProperties, polarisRateLimiterLimitedFallback);
} }
@Test @Test

@ -1,144 +0,0 @@
/*
* 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.resolver;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilterTest;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.server.ServerWebExchange;
import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME;
import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = RateLimitRuleArgumentReactiveResolverTest.TestApplication.class,
properties = {
"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"
})
public class RateLimitRuleArgumentReactiveResolverTest {
private final PolarisRateLimiterLabelReactiveResolver labelResolver =
exchange -> Collections.singletonMap("xxx", "xxx");
private final PolarisRateLimiterLabelReactiveResolver labelResolverEx =
exchange -> {
throw new RuntimeException();
};
private RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentReactiveResolver1;
private RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentReactiveResolver2;
private RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentReactiveResolver3;
private RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentReactiveResolver4;
@BeforeEach
void setUp() throws InvalidProtocolBufferException {
MetadataContext.LOCAL_NAMESPACE = "TEST";
ServiceRuleManager serviceRuleManager = mock(ServiceRuleManager.class);
RateLimitProto.Rule.Builder ratelimitRuleBuilder = RateLimitProto.Rule.newBuilder();
InputStream inputStream = QuotaCheckServletFilterTest.class.getClassLoader().getResourceAsStream("ratelimit.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, ratelimitRuleBuilder);
RateLimitProto.Rule rateLimitRule = ratelimitRuleBuilder.build();
RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.newBuilder().addRules(rateLimitRule).build();
when(serviceRuleManager.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit);
// normal
this.rateLimitRuleArgumentReactiveResolver1 = new RateLimitRuleArgumentReactiveResolver(serviceRuleManager, labelResolver);
// ex
this.rateLimitRuleArgumentReactiveResolver2 = new RateLimitRuleArgumentReactiveResolver(serviceRuleManager, labelResolverEx);
// null
ServiceRuleManager serviceRuleManager1 = mock(ServiceRuleManager.class);
when(serviceRuleManager1.getServiceRateLimitRule(anyString(), anyString())).thenReturn(null);
this.rateLimitRuleArgumentReactiveResolver3 = new RateLimitRuleArgumentReactiveResolver(serviceRuleManager1, labelResolver);
// null 2
ServiceRuleManager serviceRuleManager2 = mock(ServiceRuleManager.class);
RateLimitProto.RateLimit rateLimit2 = RateLimitProto.RateLimit.newBuilder().build();
when(serviceRuleManager2.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit2);
this.rateLimitRuleArgumentReactiveResolver4 = new RateLimitRuleArgumentReactiveResolver(serviceRuleManager2, labelResolver);
}
@Test
public void testGetRuleArguments() {
// Mock request
MetadataContext.LOCAL_SERVICE = "Test";
// Mock request
MockServerHttpRequest request = MockServerHttpRequest.get("http://127.0.0.1:8080/test")
.remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
.header("xxx", "xxx")
.queryParam("yyy", "yyy")
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
MetadataContext metadataContext = new MetadataContext();
metadataContext.setUpstreamDisposableMetadata(new HashMap<String, String>() {{
put(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, MetadataContext.LOCAL_NAMESPACE);
put(DEFAULT_METADATA_SOURCE_SERVICE_NAME, MetadataContext.LOCAL_SERVICE);
}});
MetadataContextHolder.set(metadataContext);
Set<Argument> arguments = rateLimitRuleArgumentReactiveResolver1.getArguments(exchange, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE);
Set<Argument> exceptRes = new HashSet<>();
exceptRes.add(Argument.buildMethod("GET"));
exceptRes.add(Argument.buildHeader("xxx", "xxx"));
exceptRes.add(Argument.buildQuery("yyy", "yyy"));
exceptRes.add(Argument.buildCallerIP("127.0.0.1"));
exceptRes.add(Argument.buildCustom("xxx", "xxx"));
exceptRes.add(Argument.buildCallerService(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE));
assertThat(arguments).isEqualTo(exceptRes);
rateLimitRuleArgumentReactiveResolver2.getArguments(exchange, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE);
rateLimitRuleArgumentReactiveResolver3.getArguments(exchange, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE);
rateLimitRuleArgumentReactiveResolver4.getArguments(exchange, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE);
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -1,135 +0,0 @@
/*
* 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.resolver;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilterTest;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME;
import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = RateLimitRuleArgumentServletResolverTest.TestApplication.class,
properties = {
"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"
})
public class RateLimitRuleArgumentServletResolverTest {
private final PolarisRateLimiterLabelServletResolver labelResolver =
exchange -> Collections.singletonMap("xxx", "xxx");
private final PolarisRateLimiterLabelServletResolver labelResolverEx =
exchange -> {
throw new RuntimeException();
};
private RateLimitRuleArgumentServletResolver rateLimitRuleArgumentServletResolver1;
private RateLimitRuleArgumentServletResolver rateLimitRuleArgumentServletResolver2;
private RateLimitRuleArgumentServletResolver rateLimitRuleArgumentServletResolver3;
private RateLimitRuleArgumentServletResolver rateLimitRuleArgumentServletResolver4;
@BeforeEach
void setUp() throws InvalidProtocolBufferException {
MetadataContext.LOCAL_NAMESPACE = "TEST";
ServiceRuleManager serviceRuleManager = mock(ServiceRuleManager.class);
RateLimitProto.Rule.Builder ratelimitRuleBuilder = RateLimitProto.Rule.newBuilder();
InputStream inputStream = QuotaCheckServletFilterTest.class.getClassLoader().getResourceAsStream("ratelimit.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, ratelimitRuleBuilder);
RateLimitProto.Rule rateLimitRule = ratelimitRuleBuilder.build();
RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.newBuilder().addRules(rateLimitRule).build();
when(serviceRuleManager.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit);
// normal
this.rateLimitRuleArgumentServletResolver1 = new RateLimitRuleArgumentServletResolver(serviceRuleManager, labelResolver);
// ex
this.rateLimitRuleArgumentServletResolver2 = new RateLimitRuleArgumentServletResolver(serviceRuleManager, labelResolverEx);
// null
ServiceRuleManager serviceRuleManager1 = mock(ServiceRuleManager.class);
when(serviceRuleManager1.getServiceRateLimitRule(anyString(), anyString())).thenReturn(null);
this.rateLimitRuleArgumentServletResolver3 = new RateLimitRuleArgumentServletResolver(serviceRuleManager1, labelResolver);
// null 2
ServiceRuleManager serviceRuleManager2 = mock(ServiceRuleManager.class);
RateLimitProto.RateLimit rateLimit2 = RateLimitProto.RateLimit.newBuilder().build();
when(serviceRuleManager2.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit2);
this.rateLimitRuleArgumentServletResolver4 = new RateLimitRuleArgumentServletResolver(serviceRuleManager2, labelResolver);
}
@Test
public void testGetRuleArguments() {
// Mock request
MetadataContext.LOCAL_SERVICE = "Test";
MockHttpServletRequest request = new MockHttpServletRequest(null, "GET", "/xxx");
request.setParameter("yyy", "yyy");
request.addHeader("xxx", "xxx");
MetadataContext metadataContext = new MetadataContext();
metadataContext.setUpstreamDisposableMetadata(new HashMap<String, String>() {{
put(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, MetadataContext.LOCAL_NAMESPACE);
put(DEFAULT_METADATA_SOURCE_SERVICE_NAME, MetadataContext.LOCAL_SERVICE);
}});
MetadataContextHolder.set(metadataContext);
Set<Argument> arguments = rateLimitRuleArgumentServletResolver1.getArguments(request, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE);
Set<Argument> exceptRes = new HashSet<>();
exceptRes.add(Argument.buildMethod("GET"));
exceptRes.add(Argument.buildHeader("xxx", "xxx"));
exceptRes.add(Argument.buildQuery("yyy", "yyy"));
exceptRes.add(Argument.buildCallerIP("127.0.0.1"));
exceptRes.add(Argument.buildCustom("xxx", "xxx"));
exceptRes.add(Argument.buildCallerService(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE));
assertThat(arguments).isEqualTo(exceptRes);
rateLimitRuleArgumentServletResolver2.getArguments(request, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE);
rateLimitRuleArgumentServletResolver3.getArguments(request, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE);
rateLimitRuleArgumentServletResolver4.getArguments(request, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE);
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -17,9 +17,6 @@
package com.tencent.cloud.polaris.ratelimit.utils; package com.tencent.cloud.polaris.ratelimit.utils;
import java.util.HashMap;
import java.util.HashSet;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult; import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest; import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
@ -69,59 +66,28 @@ public class QuotaCheckUtilsTest {
public void testGetQuota() { public void testGetQuota() {
// Pass // Pass
String serviceName = "TestApp1"; String serviceName = "TestApp1";
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashMap<>(), null); QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, 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, new HashMap<>(), 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, new HashMap<>(), 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, new HashMap<>(), null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("get quota failed");
}
@Test
public void testGetQuota2() {
// Pass
String serviceName = "TestApp1";
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashSet<>(), null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk); assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0); assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk"); assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk");
// Unirate waiting 1000ms // Unirate waiting 1000ms
serviceName = "TestApp2"; serviceName = "TestApp2";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashSet<>(), null); quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk); assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(1000); assertThat(quotaResponse.getWaitMs()).isEqualTo(1000);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk"); assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk");
// Rate limited // Rate limited
serviceName = "TestApp3"; serviceName = "TestApp3";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashSet<>(), null); quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultLimited); assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultLimited);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0); assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultLimited"); assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultLimited");
// Exception // Exception
serviceName = "TestApp4"; serviceName = "TestApp4";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashSet<>(), null); quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk); assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0); assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("get quota failed"); assertThat(quotaResponse.getInfo()).isEqualTo("get quota failed");

@ -28,6 +28,8 @@ import com.tencent.polaris.metadata.core.MetadataContainer;
import com.tencent.polaris.metadata.core.MetadataProvider; import com.tencent.polaris.metadata.core.MetadataProvider;
import com.tencent.polaris.metadata.core.MetadataType; import com.tencent.polaris.metadata.core.MetadataType;
import com.tencent.polaris.metadata.core.TransitiveType; import com.tencent.polaris.metadata.core.TransitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -42,6 +44,8 @@ import static com.tencent.cloud.common.metadata.MetadataContext.FRAGMENT_UPSTREA
*/ */
public final class MetadataContextHolder { public final class MetadataContextHolder {
private static final Logger LOG = LoggerFactory.getLogger(MetadataContextHolder.class);
private static StaticMetadataManager staticMetadataManager; private static StaticMetadataManager staticMetadataManager;
static { static {
@ -58,9 +62,15 @@ public final class MetadataContextHolder {
private static MetadataContext createMetadataManager() { private static MetadataContext createMetadataManager() {
MetadataContext metadataManager = new MetadataContext(); MetadataContext metadataManager = new MetadataContext();
if (staticMetadataManager == null) { if (staticMetadataManager == null) {
if (ApplicationContextAwareUtils.getApplicationContext() != null) {
staticMetadataManager = ApplicationContextAwareUtils.getApplicationContext() staticMetadataManager = ApplicationContextAwareUtils.getApplicationContext()
.getBean(StaticMetadataManager.class); .getBean(StaticMetadataManager.class);
} }
else {
// for junit test.
return metadataManager;
}
}
// local custom metadata // local custom metadata
MetadataContainer metadataContainer = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false); MetadataContainer metadataContainer = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false);
Map<String, String> mergedStaticMetadata = staticMetadataManager.getMergedStaticMetadata(); Map<String, String> mergedStaticMetadata = staticMetadataManager.getMergedStaticMetadata();

@ -43,7 +43,7 @@ public final class TsfContext {
return; return;
} }
MetadataContext tsfCoreContext = MetadataContextHolder.get(); MetadataContext tsfCoreContext = MetadataContextHolder.get();
TransitiveType transitive = TransitiveType.NONE; TransitiveType transitive = TransitiveType.DISPOSABLE;
if (null != flags) { if (null != flags) {
for (Tag.ControlFlag flag : flags) { for (Tag.ControlFlag flag : flags) {
if (flag == Tag.ControlFlag.TRANSITIVE) { if (flag == Tag.ControlFlag.TRANSITIVE) {

@ -36,6 +36,7 @@ public class CustomMetadata implements InstanceMetadataProvider {
public Map<String, String> getMetadata() { public Map<String, String> getMetadata() {
Map<String, String> metadata = new HashMap<>(); Map<String, String> metadata = new HashMap<>();
metadata.put("k1", "v1"); metadata.put("k1", "v1");
metadata.put("lane", "lane1");
return metadata; return metadata;
} }

@ -1,62 +0,0 @@
/*
* 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.quickstart.callee.ratelimit;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* resolver custom label from request.
*
* @author lepdou 2022-03-31
*/
@Component
public class CustomLabelResolver implements PolarisRateLimiterLabelServletResolver {
private static final Logger LOG = LoggerFactory.getLogger(CustomLabelResolver.class);
@Value("${label.key-value:}")
private String[] keyValues;
@Override
public Map<String, String> resolve(HttpServletRequest request) {
// rate limit by some request params. such as query params, headers ..
return getLabels(keyValues);
}
private Map<String, String> getLabels(String[] keyValues) {
Map<String, String> labels = new HashMap<>();
for (String kv : keyValues) {
String key = kv.substring(0, kv.indexOf(":"));
String value = kv.substring(kv.indexOf(":") + 1);
labels.put(key, value);
}
LOG.info("Current labels:{}", labels);
return labels;
}
}

@ -49,3 +49,8 @@ management:
- polaris-config - polaris-config
label: label:
key-value: user:zhangsan key-value: user:zhangsan
logging:
file:
name: /sct-demo-logs/${spring.application.name}/root.log
level:
root: INFO

@ -36,6 +36,7 @@ public class CustomMetadata implements InstanceMetadataProvider {
public Map<String, String> getMetadata() { public Map<String, String> getMetadata() {
Map<String, String> metadata = new HashMap<>(); Map<String, String> metadata = new HashMap<>();
metadata.put("k1", "v2"); metadata.put("k1", "v2");
metadata.put("lane", "lane2");
return metadata; return metadata;
} }

@ -1,60 +0,0 @@
/*
* 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.quickstart.callee.ratelimit;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
/**
* resolver custom label from request.
*
* @author sean yu
*/
@Component
public class CustomLabelResolverReactive implements PolarisRateLimiterLabelReactiveResolver {
private static final Logger LOG = LoggerFactory.getLogger(CustomLabelResolverReactive.class);
@Value("${label.key-value:}")
private String[] keyValues;
@Override
public Map<String, String> resolve(ServerWebExchange exchange) {
// rate limit by some request params. such as query params, headers ..
return getLabels(keyValues);
}
private Map<String, String> getLabels(String[] keyValues) {
Map<String, String> labels = new HashMap<>();
for (String kv : keyValues) {
String key = kv.substring(0, kv.indexOf(":"));
String value = kv.substring(kv.indexOf(":") + 1);
labels.put(key, value);
}
LOG.info("Current labels:{}", labels);
return labels;
}
}

@ -48,3 +48,8 @@ management:
- polaris-config - polaris-config
label: label:
key-value: user2:lisi key-value: user2:lisi
logging:
file:
name: /sct-demo-logs/${spring.application.name}/root.log
level:
root: INFO

@ -18,6 +18,9 @@
package com.tencent.cloud.quickstart.caller; package com.tencent.cloud.quickstart.caller;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.spi.InstanceMetadataProvider; import com.tencent.cloud.common.spi.InstanceMetadataProvider;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -28,6 +31,12 @@ import org.springframework.stereotype.Component;
@Component @Component
public class CustomMetadataProvider implements InstanceMetadataProvider { public class CustomMetadataProvider implements InstanceMetadataProvider {
@Override
public Map<String, String> getMetadata() {
Map<String, String> metadata = new HashMap<>();
metadata.put("k1", "v1");
return metadata;
}
// @Override // @Override
// public String getRegion() { // public String getRegion() {
// return "huadong"; // return "huadong";

@ -40,6 +40,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
@ -100,8 +101,13 @@ public class QuickstartCallerController {
HttpEntity<String> entity = new HttpEntity<>(headers); HttpEntity<String> entity = new HttpEntity<>(headers);
// 使用 exchange 方法发送 GET 请求,并获取响应 // 使用 exchange 方法发送 GET 请求,并获取响应
try {
return restTemplate.exchange(url, HttpMethod.GET, entity, String.class); return restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
} }
catch (HttpClientErrorException | HttpServerErrorException httpClientErrorException) {
return new ResponseEntity<>(httpClientErrorException.getResponseBodyAsString(), httpClientErrorException.getStatusCode());
}
}
/** /**
* Get information of callee. * Get information of callee.
@ -121,19 +127,22 @@ public class QuickstartCallerController {
* Get information 30 times per 1 second. * Get information 30 times per 1 second.
* *
* @return result of 30 calls. * @return result of 30 calls.
* @throws InterruptedException exception
*/ */
@GetMapping("/ratelimit") @GetMapping("/ratelimit")
public String invokeInfo() throws InterruptedException { public String invokeInfo() {
StringBuffer builder = new StringBuffer(); StringBuilder builder = new StringBuilder();
CountDownLatch count = new CountDownLatch(30);
AtomicInteger index = new AtomicInteger(0); AtomicInteger index = new AtomicInteger(0);
for (int i = 0; i < 30; i++) { for (int i = 0; i < 30; i++) {
new Thread(() -> {
try { try {
ResponseEntity<String> entity = restTemplate.getForEntity( ResponseEntity<String> entity = restTemplate.getForEntity(
"http://QuickstartCalleeService/quickstart/callee/info", String.class); "http://QuickstartCalleeService/quickstart/callee/info", String.class);
builder.append(entity.getBody() + "\n"); builder.append(entity.getBody() + "\n");
try {
Thread.sleep(30);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
} }
catch (RestClientException e) { catch (RestClientException e) {
if (e instanceof HttpClientErrorException.TooManyRequests) { if (e instanceof HttpClientErrorException.TooManyRequests) {
@ -143,10 +152,8 @@ public class QuickstartCallerController {
throw e; throw e;
} }
} }
count.countDown();
}).start();
} }
count.await();
return builder.toString(); return builder.toString();
} }

@ -50,3 +50,8 @@ management:
include: include:
- polaris-discovery - polaris-discovery
- polaris-circuit-breaker - polaris-circuit-breaker
logging:
file:
name: /sct-demo-logs/${spring.application.name}/root.log
level:
root: INFO

@ -57,3 +57,8 @@ spring:
- Path=/QuickstartCallerService/** - Path=/QuickstartCallerService/**
filters: filters:
- StripPrefix=1 - StripPrefix=1
logging:
file:
name: /sct-demo-logs/${spring.application.name}/root.log
level:
root: INFO

@ -27,6 +27,11 @@
<artifactId>spring-cloud-starter-tencent-polaris-contract</artifactId> <artifactId>spring-cloud-starter-tencent-polaris-contract</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>

@ -53,6 +53,7 @@ public class TsfContextConfigModifier implements PolarisConfigModifier {
tsfEventReporterConfig.setTsfNamespaceId(tsfCoreProperties.getTsfNamespaceId()); tsfEventReporterConfig.setTsfNamespaceId(tsfCoreProperties.getTsfNamespaceId());
tsfEventReporterConfig.setServiceName(tsfCoreProperties.getServiceName()); tsfEventReporterConfig.setServiceName(tsfCoreProperties.getServiceName());
tsfEventReporterConfig.setToken(consulProperties.getAclToken()); tsfEventReporterConfig.setToken(consulProperties.getAclToken());
tsfEventReporterConfig.setApplicationId(tsfCoreProperties.getTsfApplicationId());
configuration.getGlobal().getEventReporter() configuration.getGlobal().getEventReporter()
.setPluginConfig(DefaultPlugins.TSF_EVENT_REPORTER_TYPE, tsfEventReporterConfig); .setPluginConfig(DefaultPlugins.TSF_EVENT_REPORTER_TYPE, tsfEventReporterConfig);
} }

@ -129,6 +129,12 @@ public class TsfCoreProperties {
@Value("${tsf_event_master_port:15200}") @Value("${tsf_event_master_port:15200}")
private Integer eventMasterPort; private Integer eventMasterPort;
@Value("${tsf_ratelimit_master_ip:}")
private String ratelimitMasterIp;
@Value("${tsf_ratelimit_master_port:7000}")
private Integer ratelimitMasterPort;
public String getAppId() { public String getAppId() {
return appId; return appId;
} }
@ -270,6 +276,22 @@ public class TsfCoreProperties {
this.eventMasterPort = eventMasterPort; this.eventMasterPort = eventMasterPort;
} }
public String getRatelimitMasterIp() {
return ratelimitMasterIp;
}
public void setRatelimitMasterIp(String ratelimitMasterIp) {
this.ratelimitMasterIp = ratelimitMasterIp;
}
public Integer getRatelimitMasterPort() {
return ratelimitMasterPort;
}
public void setRatelimitMasterPort(Integer ratelimitMasterPort) {
this.ratelimitMasterPort = ratelimitMasterPort;
}
@Override @Override
public String toString() { public String toString() {
return "TsfCoreProperties{" + return "TsfCoreProperties{" +
@ -289,6 +311,8 @@ public class TsfCoreProperties {
", scheme='" + scheme + '\'' + ", scheme='" + scheme + '\'' +
", eventMasterIp='" + eventMasterIp + '\'' + ", eventMasterIp='" + eventMasterIp + '\'' +
", eventMasterPort=" + eventMasterPort + ", eventMasterPort=" + eventMasterPort +
", ratelimitMasterIp='" + ratelimitMasterIp + '\'' +
", ratelimitMasterPort=" + ratelimitMasterPort +
'}'; '}';
} }
} }

Loading…
Cancel
Save