Merge branch 'main' into main

pull/441/head
cheese8 3 years ago committed by GitHub
commit 07a77a95a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,4 +3,8 @@
- [Bugfix: optimize ratelimit actuator](https://github.com/Tencent/spring-cloud-tencent/pull/413)
- [Feature: add rate limit filter debug log](https://github.com/Tencent/spring-cloud-tencent/pull/417)
- [Feature: Optimized configuration update](https://github.com/Tencent/spring-cloud-tencent/pull/423)
- [Feature: add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/428)
- [Optimize: add EncodeTransferMedataRestTemplateInterceptor to RestTemplate](https://github.com/Tencent/spring-cloud-tencent/pull/434)
- [Optimize: Specification apollo code reference notes](https://github.com/Tencent/spring-cloud-tencent/pull/442)
- [Feature: graceful service registration after ApplicationReadyEventg](https://github.com/Tencent/spring-cloud-tencent/pull/441)

@ -47,6 +47,7 @@
<module>spring-cloud-starter-tencent-polaris-ratelimit</module>
<module>spring-cloud-starter-tencent-polaris-circuitbreaker</module>
<module>spring-cloud-starter-tencent-polaris-router</module>
<module>spring-cloud-tencent-plugin-starters</module>
<module>spring-cloud-tencent-dependencies</module>
<module>spring-cloud-tencent-examples</module>
<module>spring-cloud-tencent-coverage</module>

@ -18,8 +18,9 @@
package com.tencent.cloud.metadata.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.netflix.zuul.ZuulFilter;
import com.tencent.cloud.common.constant.MetadataConstant;
@ -30,18 +31,15 @@ import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateIntercept
import com.tencent.cloud.metadata.core.EncodeTransferMedataScgFilter;
import com.tencent.cloud.metadata.core.EncodeTransferMetadataZuulFilter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import static javax.servlet.DispatcherType.ASYNC;
@ -143,9 +141,10 @@ public class MetadataTransferAutoConfiguration {
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
protected static class MetadataTransferRestTemplateConfig implements ApplicationContextAware {
protected static class MetadataTransferRestTemplateConfig {
private ApplicationContext context;
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor() {
@ -153,59 +152,12 @@ public class MetadataTransferAutoConfiguration {
}
@Bean
BeanPostProcessor encodeTransferMetadataRestTemplatePostProcessor(
EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor) {
// Coping with multiple bean injection scenarios
Map<String, RestTemplate> beans = this.context.getBeansOfType(RestTemplate.class);
// If the restTemplate has been created when the
// MetadataRestTemplatePostProcessor Bean
// is initialized, then manually set the interceptor.
if (!CollectionUtils.isEmpty(beans)) {
for (RestTemplate restTemplate : beans.values()) {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
// Avoid setting interceptor repeatedly.
if (!interceptors.contains(encodeTransferMedataRestTemplateInterceptor)) {
interceptors.add(encodeTransferMedataRestTemplateInterceptor);
restTemplate.setInterceptors(interceptors);
}
}
}
return new EncodeTransferMetadataRestTemplatePostProcessor(encodeTransferMedataRestTemplateInterceptor);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
public static class EncodeTransferMetadataRestTemplatePostProcessor
implements BeanPostProcessor {
private final EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor;
EncodeTransferMetadataRestTemplatePostProcessor(
EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor) {
this.encodeTransferMedataRestTemplateInterceptor = encodeTransferMedataRestTemplateInterceptor;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof RestTemplate) {
RestTemplate restTemplate = (RestTemplate) bean;
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
// Avoid setting interceptor repeatedly.
if (!interceptors.contains(encodeTransferMedataRestTemplateInterceptor)) {
interceptors.add(this.encodeTransferMedataRestTemplateInterceptor);
restTemplate.setInterceptors(interceptors);
}
}
return bean;
}
public SmartInitializingSingleton addEncodeTransferMedataInterceptorForRestTemplate(EncodeTransferMedataRestTemplateInterceptor interceptor) {
return () -> restTemplates.forEach(restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
list.add(interceptor);
restTemplate.setInterceptors(list);
});
}
}
}

@ -25,6 +25,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.tencent.cloud.common.constant.MetadataConstant;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders;
@ -38,9 +39,6 @@ import org.springframework.web.server.ServerWebExchange;
*/
public final class CustomTransitiveMetadataResolver {
private static final String TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-";
private static final int TRANSITIVE_HEADER_PREFIX_LENGTH = TRANSITIVE_HEADER_PREFIX.length();
private CustomTransitiveMetadataResolver() {
}
@ -50,10 +48,20 @@ public final class CustomTransitiveMetadataResolver {
HttpHeaders headers = exchange.getRequest().getHeaders();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String key = entry.getKey();
if (StringUtils.isNotBlank(key)
&& StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
if (StringUtils.isBlank(key)) {
continue;
}
// resolve sct transitive header
if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX)
&& !CollectionUtils.isEmpty(entry.getValue())) {
String sourceKey = StringUtils.substring(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, entry.getValue().get(0));
}
//resolve polaris transitive header
if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX)
&& !CollectionUtils.isEmpty(entry.getValue())) {
String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, entry.getValue().get(0));
}
}
@ -67,11 +75,21 @@ public final class CustomTransitiveMetadataResolver {
Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
String key = headers.nextElement();
if (StringUtils.isBlank(key)) {
continue;
}
// resolve sct transitive header
if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX)
&& StringUtils.isNotBlank(request.getHeader(key))) {
String sourceKey = StringUtils.substring(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, request.getHeader(key));
}
if (StringUtils.isNotBlank(key)
&& StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
// resolve polaris transitive header
if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX)
&& StringUtils.isNotBlank(request.getHeader(key))) {
String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, request.getHeader(key));
}
}

@ -59,11 +59,10 @@ public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered {
// get metadata of current thread
MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT);
// add new metadata and cover old
if (metadataContext == null) {
metadataContext = MetadataContextHolder.get();
}
Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
if (!CollectionUtils.isEmpty(customMetadata)) {
String metadataStr = JacksonUtils.serialize2Json(customMetadata);

@ -18,6 +18,12 @@
package com.tencent.cloud.metadata.config;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMetadataZuulFilter;
@ -26,7 +32,12 @@ import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
/**
* Test for {@link MetadataTransferAutoConfiguration}.
@ -54,10 +65,6 @@ public class MetadataTransferAutoConfigurationTest {
MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class);
Assertions.assertThat(context).hasSingleBean(
EncodeTransferMedataRestTemplateInterceptor.class);
Assertions.assertThat(context).hasSingleBean(
MetadataTransferAutoConfiguration
.MetadataTransferRestTemplateConfig
.EncodeTransferMetadataRestTemplatePostProcessor.class);
Assertions.assertThat(context).hasSingleBean(
MetadataTransferAutoConfiguration.MetadataTransferZuulFilterConfig.class);
Assertions.assertThat(context)
@ -67,4 +74,44 @@ public class MetadataTransferAutoConfigurationTest {
Assertions.assertThat(context).hasSingleBean(GlobalFilter.class);
});
}
@Test
public void test2() {
this.applicationContextRunner
.withConfiguration(
AutoConfigurations.of(MetadataTransferAutoConfiguration.class, RestTemplateConfiguration.class))
.run(context -> {
Assertions.assertThat(context)
.hasSingleBean(EncodeTransferMedataFeignInterceptor.class);
EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor = context.getBean(EncodeTransferMedataRestTemplateInterceptor.class);
Map<String, RestTemplate> restTemplateMap = context.getBeansOfType(RestTemplate.class);
Assertions.assertThat(restTemplateMap.size()).isEqualTo(2);
for (String beanName : Arrays.asList("restTemplate", "loadBalancedRestTemplate")) {
RestTemplate restTemplate = restTemplateMap.get(beanName);
Assertions.assertThat(restTemplate).isNotNull();
List<ClientHttpRequestInterceptor> encodeTransferMedataFeignInterceptorList = restTemplate.getInterceptors()
.stream()
.filter(interceptor -> Objects.equals(interceptor, encodeTransferMedataRestTemplateInterceptor))
.collect(Collectors.toList());
//EncodeTransferMedataFeignInterceptor is not added repeatedly
Assertions.assertThat(encodeTransferMedataFeignInterceptorList.size()).isEqualTo(1);
}
});
}
@Configuration
static class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@LoadBalanced
@Bean
public RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
}
}

@ -34,7 +34,7 @@ import org.springframework.mock.web.server.MockServerWebExchange;
public class CustomTransitiveMetadataResolverTest {
@Test
public void test() {
public void testSCTTransitiveMetadata() {
MockServerHttpRequest.BaseBuilder<?> builder = MockServerHttpRequest.get("");
builder.header("X-SCT-Metadata-Transitive-a", "test");
MockServerWebExchange exchange = MockServerWebExchange.from(builder);
@ -44,11 +44,30 @@ public class CustomTransitiveMetadataResolverTest {
}
@Test
public void testServlet() {
public void testPolarisTransitiveMetadata() {
MockServerHttpRequest.BaseBuilder<?> builder = MockServerHttpRequest.get("");
builder.header("X-Polaris-Metadata-Transitive-a", "test");
MockServerWebExchange exchange = MockServerWebExchange.from(builder);
Map<String, String> resolve = CustomTransitiveMetadataResolver.resolve(exchange);
Assertions.assertThat(resolve.size()).isEqualTo(1);
Assertions.assertThat(resolve.get("a")).isEqualTo("test");
}
@Test
public void testSCTServletTransitiveMetadata() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("X-SCT-Metadata-Transitive-a", "test");
Map<String, String> resolve = CustomTransitiveMetadataResolver.resolve(request);
Assertions.assertThat(resolve.size()).isEqualTo(1);
Assertions.assertThat(resolve.get("a")).isEqualTo("test");
}
@Test
public void testPolarisServletTransitiveMetadata() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("X-Polaris-Metadata-Transitive-a", "test");
Map<String, String> resolve = CustomTransitiveMetadataResolver.resolve(request);
Assertions.assertThat(resolve.size()).isEqualTo(1);
Assertions.assertThat(resolve.get("a")).isEqualTo("test");
}
}

@ -20,13 +20,21 @@ package com.tencent.cloud.polaris.config;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceAutoRefresher;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.adapter.SmartConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor;
import com.tencent.cloud.polaris.config.condition.ConditionalOnNonDefaultBehavior;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.listener.PolarisConfigChangeEventListener;
import com.tencent.cloud.polaris.config.spring.annotation.SpringValueProcessor;
import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -44,9 +52,10 @@ public class PolarisConfigAutoConfiguration {
public PolarisPropertySourceAutoRefresher polarisPropertySourceAutoRefresher(
PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager,
ContextRefresher contextRefresher) {
SpringValueRegistry springValueRegistry,
PlaceholderHelper placeholderHelper) {
return new PolarisPropertySourceAutoRefresher(polarisConfigProperties,
polarisPropertySourceManager, contextRefresher);
polarisPropertySourceManager, springValueRegistry, placeholderHelper);
}
@Bean
@ -58,4 +67,30 @@ public class PolarisConfigAutoConfiguration {
public PolarisConfigChangeEventListener polarisConfigChangeEventListener() {
return new PolarisConfigChangeEventListener();
}
@Bean
public SpringValueRegistry springValueRegistry() {
return new SpringValueRegistry();
}
@Bean
public PlaceholderHelper placeholderHelper() {
return new PlaceholderHelper();
}
@Bean
public SpringValueProcessor springValueProcessor(PlaceholderHelper placeholderHelper, SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) {
return new SpringValueProcessor(placeholderHelper, springValueRegistry, polarisConfigProperties);
}
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
@ConditionalOnNonDefaultBehavior
public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder(
ConfigurationPropertiesBeans beans) {
// If using default behavior, not use SmartConfigurationPropertiesRebinder.
// Minimize te possibility of making mistakes.
return new SmartConfigurationPropertiesRebinder(beans);
}
}

@ -19,6 +19,8 @@ package com.tencent.cloud.polaris.config;
import com.tencent.cloud.polaris.config.adapter.PolarisConfigFileLocator;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.adapter.SmartConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.condition.ConditionalOnNonDefaultBehavior;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
@ -27,7 +29,11 @@ import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import com.tencent.polaris.configuration.factory.ConfigFileServiceFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -79,4 +85,14 @@ public class PolarisConfigBootstrapAutoConfiguration {
PolarisContextProperties polarisContextProperties) {
return new ConfigurationModifier(polarisConfigProperties, polarisContextProperties);
}
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
@ConditionalOnNonDefaultBehavior
public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder(
ConfigurationPropertiesBeans beans) {
// If using default behavior, not use SmartConfigurationPropertiesRebinder.
// Minimize te possibility of making mistakes.
return new SmartConfigurationPropertiesRebinder(beans);
}
}

@ -18,22 +18,32 @@
package com.tencent.cloud.polaris.config.adapter;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValue;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.CollectionUtils;
/**
@ -43,29 +53,40 @@ import org.springframework.util.CollectionUtils;
* @author lepdou 2022-03-28
*/
public class PolarisPropertySourceAutoRefresher
implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {
implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware, BeanFactoryAware {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisPropertySourceAutoRefresher.class);
private final PolarisConfigProperties polarisConfigProperties;
private final PolarisPropertySourceManager polarisPropertySourceManager;
private final ContextRefresher contextRefresher;
private final AtomicBoolean registered = new AtomicBoolean(false);
private ApplicationContext applicationContext;
private ConfigurableApplicationContext context;
private TypeConverter typeConverter;
private final SpringValueRegistry springValueRegistry;
private ConfigurableBeanFactory beanFactory;
private final PlaceholderHelper placeholderHelper;
public PolarisPropertySourceAutoRefresher(
PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager,
ContextRefresher contextRefresher) {
SpringValueRegistry springValueRegistry,
PlaceholderHelper placeholderHelper) {
this.polarisConfigProperties = polarisConfigProperties;
this.polarisPropertySourceManager = polarisPropertySourceManager;
this.contextRefresher = contextRefresher;
this.springValueRegistry = springValueRegistry;
this.placeholderHelper = placeholderHelper;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
this.context = (ConfigurableApplicationContext) applicationContext;
this.beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
this.typeConverter = this.beanFactory.getTypeConverter();
}
@Override
@ -91,6 +112,7 @@ public class PolarisPropertySourceAutoRefresher
for (PolarisPropertySource polarisPropertySource : polarisPropertySources) {
polarisPropertySource.getConfigKVFile()
.addChangeListener((ConfigKVFileChangeListener) configKVFileChangeEvent -> {
LOGGER.info(
"[SCT Config] received polaris config change event and will refresh spring context."
+ "namespace = {}, group = {}, fileName = {}",
@ -115,11 +137,70 @@ public class PolarisPropertySourceAutoRefresher
source.remove(changedKey);
break;
}
}
// rebuild beans with @RefreshScope annotation
contextRefresher.refresh();
Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, changedKey);
if (targetValues == null || targetValues.isEmpty()) {
continue;
}
// update the value
for (SpringValue val : targetValues) {
updateSpringValue(val);
}
}
context.publishEvent(new EnvironmentChangeEvent(context, configKVFileChangeEvent.changedKeys()));
});
}
}
private void updateSpringValue(SpringValue springValue) {
try {
Object value = resolvePropertyValue(springValue);
springValue.update(value);
LOGGER.info("Auto update polaris changed value successfully, new value: {}, {}", value,
springValue);
}
catch (Throwable ex) {
LOGGER.error("Auto update polaris changed value failed, {}", springValue.toString(), ex);
}
}
/**
* Logic transplanted from DefaultListableBeanFactory.
*
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor,
* java.lang.String, java.util.Set, org.springframework.beans.TypeConverter)
*/
private Object resolvePropertyValue(SpringValue springValue) {
// value will never be null
Object value = placeholderHelper
.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());
if (springValue.isJson()) {
value = parseJsonValue((String) value, springValue.getTargetType());
}
else {
value = springValue.isField() ? this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getField()) :
this.typeConverter.convertIfNecessary(value, springValue.getTargetType(),
springValue.getMethodParameter());
}
return value;
}
private Object parseJsonValue(String json, Class<?> targetType) {
try {
return JacksonUtils.json2JavaBean(json, targetType);
}
catch (Throwable ex) {
LOGGER.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
throw ex;
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}
}

@ -0,0 +1,123 @@
/*
* 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.config.adapter;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.tencent.cloud.polaris.config.enums.RefreshBehavior;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import static com.tencent.cloud.polaris.config.condition.NonDefaultBehaviorCondition.POLARIS_CONFIG_REFRESH_BEHAVIOR;
import static com.tencent.cloud.polaris.config.enums.RefreshBehavior.ALL_BEANS;
/**
* Extend {@link ConfigurationPropertiesRebinder}.
* <p>
* Spring team doesn't seem to support single {@link ConfigurationPropertiesBean} refresh.
* <p>
* SmartConfigurationPropertiesRebinder can refresh specific
* {@link ConfigurationPropertiesBean} base on the change keys.
* <p>
* <strong> NOTE: We still use Spring's default behavior (full refresh) as default
* behavior, This feature can be considered an advanced feature, it may not be as stable
* as the default behavior. </strong>
* <code><a href=https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/SmartConfigurationPropertiesRebinder.java>
* SmartConfigurationPropertiesRebinder</a></code>
*
* @author weihubeats 2022-7-10
*/
public class SmartConfigurationPropertiesRebinder extends ConfigurationPropertiesRebinder {
private static final String BEANS = "beans";
private Map<String, ConfigurationPropertiesBean> beanMap;
private ApplicationContext applicationContext;
private RefreshBehavior refreshBehavior;
public SmartConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
super(beans);
fillBeanMap(beans);
}
@SuppressWarnings("unchecked")
private void fillBeanMap(ConfigurationPropertiesBeans beans) {
this.beanMap = new HashMap<>();
Field field = ReflectionUtils.findField(beans.getClass(), BEANS);
if (field != null) {
field.setAccessible(true);
this.beanMap.putAll((Map<String, ConfigurationPropertiesBean>) Optional
.ofNullable(ReflectionUtils.getField(field, beans))
.orElse(Collections.emptyMap()));
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
super.setApplicationContext(applicationContext);
this.applicationContext = applicationContext;
this.refreshBehavior = this.applicationContext.getEnvironment().getProperty(
POLARIS_CONFIG_REFRESH_BEHAVIOR, RefreshBehavior.class,
ALL_BEANS);
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource())
// Backwards compatible
|| event.getKeys().equals(event.getSource())) {
switch (refreshBehavior) {
case SPECIFIC_BEAN:
rebindSpecificBean(event);
break;
default:
rebind();
break;
}
}
}
private void rebindSpecificBean(EnvironmentChangeEvent event) {
Set<String> refreshedSet = new HashSet<>();
beanMap.forEach((name, bean) -> event.getKeys().forEach(changeKey -> {
String prefix = AnnotationUtils.getValue(bean.getAnnotation()).toString();
// prevent multiple refresh one ConfigurationPropertiesBean.
if (changeKey.startsWith(prefix) && refreshedSet.add(name)) {
rebind(name);
}
}));
}
}

@ -40,7 +40,7 @@ import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerCon
/**
* {@link PolarisConfigAnnotationProcessor} implementation for spring .
* <p>Refer to the Apollo project implementation
* <p>This source file was reference from
* <code><a href=https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java>
* ApolloAnnotationProcessor</a></code>
* @author <a href="mailto:iskp.me@gmail.com">Palmer Xu</a> 2022-06-07

@ -26,7 +26,7 @@ import java.lang.annotation.Target;
/**
* Configuring the change listener annotation.
* <p>Refer to the Apollo project implementation
* <p>This source file was reference from
* <code><a href=https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java>
* ApolloAnnotationProcessor</a></code>
* @author Palmer Xu 2022-05-31

@ -0,0 +1,40 @@
/*
* 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.config.condition;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
/**
* custom annotation.
*
* @author weihubeats 2022-7-13
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(NonDefaultBehaviorCondition.class)
public @interface ConditionalOnNonDefaultBehavior {
}

@ -0,0 +1,57 @@
/*
* 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.config.condition;
import com.tencent.cloud.polaris.config.enums.RefreshBehavior;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Extend SpringBootCondition.
*
* @author weihubeats 2022-7-13
*/
public class NonDefaultBehaviorCondition extends SpringBootCondition {
/**
* refresh behavior config.
*/
public static final String POLARIS_CONFIG_REFRESH_BEHAVIOR = "spring.cloud.polaris.config.refresh-behavior";
/**
* refresh behavior config default value.
*/
private static final RefreshBehavior DEFAULT_REFRESH_BEHAVIOR = RefreshBehavior.ALL_BEANS;
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
RefreshBehavior behavior = context.getEnvironment().getProperty(
POLARIS_CONFIG_REFRESH_BEHAVIOR, RefreshBehavior.class,
DEFAULT_REFRESH_BEHAVIOR);
if (DEFAULT_REFRESH_BEHAVIOR == behavior) {
return ConditionOutcome.noMatch("no matched");
}
return ConditionOutcome.match("matched");
}
}

@ -0,0 +1,40 @@
/*
* 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.config.enums;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
/**
* Refresh behavior.
*
* @author weihubeats 2022-7-13
*/
public enum RefreshBehavior {
/**
* Refresh all {@link ConfigurationPropertiesBean}s.
*/
ALL_BEANS,
/**
* Refresh specific {@link ConfigurationPropertiesBean} base on change key.
*/
SPECIFIC_BEAN,
}

@ -49,7 +49,7 @@ import static com.tencent.polaris.configuration.api.core.ChangeType.MODIFIED;
/**
* Polaris Config Listener Context Defined .
* <p>Refer to the Apollo project implementation
* <p>This source file was reference from
* <code><a href=https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java>
* AbstractConfig</a></code>
*

@ -0,0 +1,73 @@
package com.tencent.cloud.polaris.config.spring.annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.util.ReflectionUtils;
/**
* Get spring bean properties and methods.
*
* @author weihubeats 2022-7-10
*/
public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrdered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
Class clazz = bean.getClass();
for (Field field : findAllField(clazz)) {
processField(bean, beanName, field);
}
for (Method method : findAllMethod(clazz)) {
processMethod(bean, beanName, method);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* subclass should implement this method to process field.
* @param bean bean
* @param beanName beanName
* @param field field
*/
protected abstract void processField(Object bean, String beanName, Field field);
/**
* subclass should implement this method to process method.
* @param bean bean
* @param beanName beanName
* @param method method
*/
protected abstract void processMethod(Object bean, String beanName, Method method);
@Override
public int getOrder() {
//make it as late as possible
return Ordered.LOWEST_PRECEDENCE;
}
private List<Field> findAllField(Class clazz) {
final List<Field> res = new LinkedList<>();
ReflectionUtils.doWithFields(clazz, field -> res.add(field));
return res;
}
private List<Method> findAllMethod(Class clazz) {
final List<Method> res = new LinkedList<>();
ReflectionUtils.doWithMethods(clazz, method -> res.add(method));
return res;
}
}

@ -0,0 +1,172 @@
package com.tencent.cloud.polaris.config.spring.annotation;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Set;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValue;
import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinition;
import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinitionProcessor;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Bean;
/**
* Spring value processor of field or method which has @Value and xml config placeholders.
* <br/>
* <br/>
* This source file was originally from:
* <code><a href=https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/SpringValueProcessor.java>
* SpringValueProcessor</a></code>
*
* @author weihubeats 2022-7-10
*/
public class SpringValueProcessor extends PolarisProcessor implements BeanFactoryPostProcessor, BeanFactoryAware {
private static final Logger LOGGER = LoggerFactory.getLogger(SpringValueProcessor.class);
private final PolarisConfigProperties polarisConfigProperties;
private final PlaceholderHelper placeholderHelper;
private final SpringValueRegistry springValueRegistry;
private BeanFactory beanFactory;
private Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions;
public SpringValueProcessor(PlaceholderHelper placeholderHelper,
SpringValueRegistry springValueRegistry,
PolarisConfigProperties polarisConfigProperties) {
this.placeholderHelper = placeholderHelper;
this.polarisConfigProperties = polarisConfigProperties;
this.springValueRegistry = springValueRegistry;
beanName2SpringValueDefinitions = LinkedListMultimap.create();
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
if (polarisConfigProperties.isAutoRefresh() && beanFactory instanceof BeanDefinitionRegistry) {
beanName2SpringValueDefinitions = SpringValueDefinitionProcessor
.getBeanName2SpringValueDefinitions((BeanDefinitionRegistry) beanFactory);
}
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (polarisConfigProperties.isAutoRefresh()) {
super.postProcessBeforeInitialization(bean, beanName);
processBeanPropertyValues(bean, beanName);
}
return bean;
}
@Override
protected void processField(Object bean, String beanName, Field field) {
// register @Value on field
Value value = field.getAnnotation(Value.class);
if (value == null) {
return;
}
doRegister(bean, beanName, field, value);
}
@Override
protected void processMethod(Object bean, String beanName, Method method) {
//register @Value on method
Value value = method.getAnnotation(Value.class);
if (value == null) {
return;
}
//skip Configuration bean methods
if (method.getAnnotation(Bean.class) != null) {
return;
}
if (method.getParameterTypes().length != 1) {
LOGGER.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters",
bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
return;
}
doRegister(bean, beanName, method, value);
}
private void doRegister(Object bean, String beanName, Member member, Value value) {
Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
if (keys.isEmpty()) {
return;
}
for (String key : keys) {
SpringValue springValue;
if (member instanceof Field) {
Field field = (Field) member;
springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
}
else if (member instanceof Method) {
Method method = (Method) member;
springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
}
else {
LOGGER.error("Polaris @Value annotation currently only support to be used on methods and fields, "
+ "but is used on {}", member.getClass());
return;
}
springValueRegistry.register(beanFactory, key, springValue);
LOGGER.info("Monitoring {}", springValue);
}
}
private void processBeanPropertyValues(Object bean, String beanName) {
Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions
.get(beanName);
if (propertySpringValues == null || propertySpringValues.isEmpty()) {
return;
}
for (SpringValueDefinition definition : propertySpringValues) {
try {
PropertyDescriptor pd = BeanUtils
.getPropertyDescriptor(bean.getClass(), definition.getPropertyName());
Method method = pd.getWriteMethod();
if (method == null) {
continue;
}
SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(),
bean, beanName, method, false);
springValueRegistry.register(beanFactory, definition.getKey(), springValue);
LOGGER.debug("Monitoring {}", springValue);
}
catch (Throwable ex) {
LOGGER.error("Failed to enable auto update feature for {}.{}", bean.getClass(),
definition.getPropertyName());
}
}
// clear
beanName2SpringValueDefinitions.removeAll(beanName);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}

@ -0,0 +1,195 @@
/*
* 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.config.spring.property;
import java.util.Set;
import java.util.Stack;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.util.StringUtils;
/**
* Placeholder helper functions.
* <br/>
* <br/>
* This source file was originally from:
* <code><a href=https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/PlaceholderHelper.java>
* PlaceholderHelper</a></code>
*
* @author weihubeats 2022-7-10
*/
public class PlaceholderHelper {
private static final String PLACEHOLDER_PREFIX = "${";
private static final String PLACEHOLDER_SUFFIX = "}";
private static final String VALUE_SEPARATOR = ":";
private static final String SIMPLE_PLACEHOLDER_PREFIX = "{";
private static final String EXPRESSION_PREFIX = "#{";
private static final String EXPRESSION_SUFFIX = "}";
/**
* Resolve placeholder property values, e.g.
* @param beanFactory beanFactory
* @param beanName beanName
* @param placeholder placeholder
* @return "${somePropertyValue}" -> "the actual property value"
*/
public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) {
// resolve string value
String strVal = beanFactory.resolveEmbeddedValue(placeholder);
BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory
.getMergedBeanDefinition(beanName) : null);
// resolve expressions like "#{systemProperties.myProp}"
return evaluateBeanDefinitionString(beanFactory, strVal, bd);
}
private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, String value,
BeanDefinition beanDefinition) {
if (beanFactory.getBeanExpressionResolver() == null) {
return value;
}
Scope scope = (beanDefinition != null ? beanFactory
.getRegisteredScope(beanDefinition.getScope()) : null);
return beanFactory.getBeanExpressionResolver()
.evaluate(value, new BeanExpressionContext(beanFactory, scope));
}
/**
*
* @param propertyString propertyString
* @return
* Extract keys from placeholder, e.g.
* <li>${some.key} => "some.key"</li>
* <li>${some.key:${some.other.key:100}} => "some.key", "some.other.key"</li>
* <li>${${some.key}} => "some.key"</li>
* <li>${${some.key:other.key}} => "some.key"</li>
* <li>${${some.key}:${another.key}} => "some.key", "another.key"</li>
* <li>#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"</li>
*/
public Set<String> extractPlaceholderKeys(String propertyString) {
Set<String> placeholderKeys = Sets.newHashSet();
if (Strings.isNullOrEmpty(propertyString) || (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString))) {
return placeholderKeys;
}
Stack<String> stack = new Stack<>();
stack.push(propertyString);
while (!stack.isEmpty()) {
String strVal = stack.pop();
int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
if (startIndex == -1) {
placeholderKeys.add(strVal);
continue;
}
int endIndex = findPlaceholderEndIndex(strVal, startIndex);
if (endIndex == -1) {
// invalid placeholder?
continue;
}
String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
// ${some.key:other.key}
if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {
stack.push(placeholderCandidate);
}
else {
// some.key:${some.other.key:100}
int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);
if (separatorIndex == -1) {
stack.push(placeholderCandidate);
}
else {
stack.push(placeholderCandidate.substring(0, separatorIndex));
String defaultValuePart =
normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));
if (!Strings.isNullOrEmpty(defaultValuePart)) {
stack.push(defaultValuePart);
}
}
}
// has remaining part, e.g. ${a}.${b}
if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
if (!Strings.isNullOrEmpty(remainingPart)) {
stack.push(remainingPart);
}
}
}
return placeholderKeys;
}
private boolean isNormalizedPlaceholder(String propertyString) {
return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX);
}
private boolean isExpressionWithPlaceholder(String propertyString) {
return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.contains(EXPRESSION_SUFFIX)
&& propertyString.contains(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX);
}
private String normalizeToPlaceholder(String strVal) {
int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
if (startIndex == -1) {
return null;
}
int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX);
if (endIndex == -1) {
return null;
}
return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length());
}
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + PLACEHOLDER_PREFIX.length();
int withinNestedPlaceholder = 0;
while (index < buf.length()) {
if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
index = index + PLACEHOLDER_SUFFIX.length();
}
else {
return index;
}
}
else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) {
withinNestedPlaceholder++;
index = index + SIMPLE_PLACEHOLDER_PREFIX.length();
}
else {
index++;
}
}
return -1;
}
}

@ -0,0 +1,156 @@
/*
* 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.config.spring.property;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import org.springframework.core.MethodParameter;
/**
* Spring @Value method info.
* <br/>
* <br/>
* This source file was originally from:
* <code><a href=https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValue.java>
* SpringValue</a></code>
*
* @author weihubeats 2022-7-10
*/
public class SpringValue {
private MethodParameter methodParameter;
private Field field;
private final WeakReference<Object> beanRef;
private final String beanName;
private final String key;
private final String placeholder;
private final Class<?> targetType;
private Type genericType;
private final boolean isJson;
public SpringValue(String key, String placeholder, Object bean, String beanName, Field field, boolean isJson) {
this.beanRef = new WeakReference<>(bean);
this.beanName = beanName;
this.field = field;
this.key = key;
this.placeholder = placeholder;
this.targetType = field.getType();
this.isJson = isJson;
if (isJson) {
this.genericType = field.getGenericType();
}
}
public SpringValue(String key, String placeholder, Object bean, String beanName, Method method, boolean isJson) {
this.beanRef = new WeakReference<>(bean);
this.beanName = beanName;
this.methodParameter = new MethodParameter(method, 0);
this.key = key;
this.placeholder = placeholder;
Class<?>[] paramTps = method.getParameterTypes();
this.targetType = paramTps[0];
this.isJson = isJson;
if (isJson) {
this.genericType = method.getGenericParameterTypes()[0];
}
}
public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
if (isField()) {
injectField(newVal);
}
else {
injectMethod(newVal);
}
}
private void injectField(Object newVal) throws IllegalAccessException {
Object bean = beanRef.get();
if (bean == null) {
return;
}
boolean accessible = field.isAccessible();
field.setAccessible(true);
field.set(bean, newVal);
field.setAccessible(accessible);
}
private void injectMethod(Object newVal)
throws InvocationTargetException, IllegalAccessException {
Object bean = beanRef.get();
if (bean == null) {
return;
}
methodParameter.getMethod().invoke(bean, newVal);
}
public String getBeanName() {
return beanName;
}
public Class<?> getTargetType() {
return targetType;
}
public String getPlaceholder() {
return this.placeholder;
}
public MethodParameter getMethodParameter() {
return methodParameter;
}
public boolean isField() {
return this.field != null;
}
public Field getField() {
return field;
}
public Type getGenericType() {
return genericType;
}
public boolean isJson() {
return isJson;
}
boolean isTargetBeanValid() {
return beanRef.get() != null;
}
@Override
public String toString() {
Object bean = beanRef.get();
if (bean == null) {
return "";
}
if (isField()) {
return String
.format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass()
.getName(), field.getName());
}
return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(),
methodParameter.getMethod().getName());
}
}

@ -0,0 +1,54 @@
/*
* 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.config.spring.property;
/**
* Spring value.
* <br/>
* <br/>
* This source file was originally from:
* <code><a href=https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueDefinition.java>
* SpringValueDefinition</a></code>
*
* @author weihubeats 2022-7-10
*/
public class SpringValueDefinition {
private final String key;
private final String placeholder;
private final String propertyName;
public SpringValueDefinition(String key, String placeholder, String propertyName) {
this.key = key;
this.placeholder = placeholder;
this.propertyName = propertyName;
}
public String getKey() {
return key;
}
public String getPlaceholder() {
return placeholder;
}
public String getPropertyName() {
return propertyName;
}
}

@ -0,0 +1,125 @@
/*
* 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.config.spring.property;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
/**
* To process xml config placeholders, e.g.
*
* <pre>
* &lt;bean class=&quot;com.demo.bean.XmlBean&quot;&gt;
* &lt;property name=&quot;timeout&quot; value=&quot;${timeout:200}&quot;/&gt;
* &lt;property name=&quot;batch&quot; value=&quot;${batch:100}&quot;/&gt;
* &lt;/bean&gt;
* </pre>
*
* This source file was originally from:
* <code><a href=https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueDefinitionProcessor.java>
* SpringValueDefinitionProcessor</a></code>
*
* @author weihubeats 2022-7-10
*/
public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor {
private static final Map<BeanDefinitionRegistry, Multimap<String, SpringValueDefinition>> beanName2SpringValueDefinitions =
Maps.newConcurrentMap();
private static final Set<BeanDefinitionRegistry> PROPERTY_VALUES_PROCESSED_BEAN_FACTORIES = Sets.newConcurrentHashSet();
private final PlaceholderHelper placeholderHelper;
private PolarisConfigProperties polarisConfigProperties;
public SpringValueDefinitionProcessor(PlaceholderHelper placeholderHelper, PolarisConfigProperties polarisConfigProperties) {
this.polarisConfigProperties = polarisConfigProperties;
this.placeholderHelper = placeholderHelper;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (polarisConfigProperties.isAutoRefresh()) {
processPropertyValues(registry);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
public static Multimap<String, SpringValueDefinition> getBeanName2SpringValueDefinitions(BeanDefinitionRegistry registry) {
Multimap<String, SpringValueDefinition> springValueDefinitions = beanName2SpringValueDefinitions.get(registry);
if (springValueDefinitions == null) {
springValueDefinitions = LinkedListMultimap.create();
}
return springValueDefinitions;
}
private void processPropertyValues(BeanDefinitionRegistry beanRegistry) {
if (!PROPERTY_VALUES_PROCESSED_BEAN_FACTORIES.add(beanRegistry)) {
// already initialized
return;
}
if (!beanName2SpringValueDefinitions.containsKey(beanRegistry)) {
beanName2SpringValueDefinitions.put(beanRegistry, LinkedListMultimap.create());
}
Multimap<String, SpringValueDefinition> springValueDefinitions = beanName2SpringValueDefinitions.get(beanRegistry);
String[] beanNames = beanRegistry.getBeanDefinitionNames();
for (String beanName : beanNames) {
BeanDefinition beanDefinition = beanRegistry.getBeanDefinition(beanName);
MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
List<PropertyValue> propertyValues = mutablePropertyValues.getPropertyValueList();
for (PropertyValue propertyValue : propertyValues) {
Object value = propertyValue.getValue();
if (!(value instanceof TypedStringValue)) {
continue;
}
String placeholder = ((TypedStringValue) value).getValue();
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeholder);
if (keys.isEmpty()) {
continue;
}
for (String key : keys) {
springValueDefinitions.put(beanName, new SpringValueDefinition(key, placeholder, propertyValue.getName()));
}
}
}
}
}

@ -0,0 +1,107 @@
/*
* 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.config.spring.property;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.tencent.polaris.client.util.NamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
/**
* Spring value auto registry.
* <br/>
* <br/>
* This source file was originally from:
* <code><a href=https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueRegistry.java>
* SpringValueRegistry</a></code>
*
* @author weihubeats 2022-7-10
*/
public class SpringValueRegistry {
private static final Logger logger = LoggerFactory.getLogger(SpringValueRegistry.class);
private static final long CLEAN_INTERVAL_IN_SECONDS = 5;
private final Map<BeanFactory, Multimap<String, SpringValue>> registry = Maps.newConcurrentMap();
private final AtomicBoolean initialized = new AtomicBoolean(false);
private final Object LOCK = new Object();
public void register(BeanFactory beanFactory, String key, SpringValue springValue) {
if (!registry.containsKey(beanFactory)) {
synchronized (LOCK) {
if (!registry.containsKey(beanFactory)) {
registry.put(beanFactory, Multimaps.synchronizedListMultimap(LinkedListMultimap.create()));
}
}
}
registry.get(beanFactory).put(key, springValue);
// lazy initialize
if (initialized.compareAndSet(false, true)) {
initialize();
}
}
public Collection<SpringValue> get(BeanFactory beanFactory, String key) {
Multimap<String, SpringValue> beanFactorySpringValues = registry.get(beanFactory);
if (beanFactorySpringValues == null) {
return null;
}
return beanFactorySpringValues.get(key);
}
private void initialize() {
Executors.newSingleThreadScheduledExecutor(
new NamedThreadFactory("polaris-spring-value-registry")).scheduleAtFixedRate(
() -> {
try {
scanAndClean();
}
catch (Throwable ex) {
logger.error(ex.getMessage(), ex);
}
}, CLEAN_INTERVAL_IN_SECONDS, CLEAN_INTERVAL_IN_SECONDS, TimeUnit.SECONDS);
}
private void scanAndClean() {
Iterator<Multimap<String, SpringValue>> iterator = registry.values().iterator();
while (!Thread.currentThread().isInterrupted() && iterator.hasNext()) {
Multimap<String, SpringValue> springValues = iterator.next();
Iterator<Map.Entry<String, SpringValue>> springValueIterator = springValues.entries().iterator();
while (springValueIterator.hasNext()) {
Map.Entry<String, SpringValue> springValue = springValueIterator.next();
if (!springValue.getValue().isTargetBeanValid()) {
// clear unused spring values
springValueIterator.remove();
}
}
}
}
}

@ -41,6 +41,12 @@
"defaultValue": "true",
"description": "Whether to connect to a remote server, suitable for local development mode.",
"sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties"
},
{
"name": "spring.cloud.polaris.config.refresh-behavior",
"type": "com.tencent.cloud.polaris.config.enums.RefreshBehavior",
"defaultValue": "all_beans",
"description": "ConfigurationPropertiesBean refresh behavior."
}
]
}

@ -0,0 +1,19 @@
package com.tencent.cloud.polaris.config.adapter;
/**
* Mock config kv file for test.
*
* @author weihubeats 2022-7-10
*/
public class MockedConfigChange {
private String k1;
String getK1() {
return k1;
}
void setK1(String k1) {
this.k1 = k1;
}
}

@ -18,11 +18,17 @@
package com.tencent.cloud.polaris.config.adapter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.Lists;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValue;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.polaris.configuration.api.core.ChangeType;
import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
@ -32,9 +38,13 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ConfigurableApplicationContext;
import static org.mockito.Mockito.verify;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
@ -55,10 +65,33 @@ public class PolarisPropertiesSourceAutoRefresherTest {
@Mock
private ContextRefresher contextRefresher;
@Mock
private SpringValueRegistry springValueRegistry;
@Mock
private PlaceholderHelper placeholderHelper;
@Test
public void testConfigFileChanged() {
public void testConfigFileChanged() throws Exception {
PolarisPropertySourceAutoRefresher refresher = new PolarisPropertySourceAutoRefresher(polarisConfigProperties,
polarisPropertySourceManager, contextRefresher);
polarisPropertySourceManager, springValueRegistry, placeholderHelper);
ConfigurableApplicationContext applicationContext = mock(ConfigurableApplicationContext.class);
ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class);
TypeConverter typeConverter = mock(TypeConverter.class);
when(beanFactory.getTypeConverter()).thenReturn(typeConverter);
when(applicationContext.getBeanFactory()).thenReturn(beanFactory);
refresher.setApplicationContext(applicationContext);
when(typeConverter.convertIfNecessary(any(), any(), (Field) any())).thenReturn("v11");
Collection<SpringValue> springValues = new ArrayList<>();
MockedConfigChange mockedConfigChange = new MockedConfigChange();
mockedConfigChange.setK1("v1");
Field field = mockedConfigChange.getClass().getDeclaredField("k1");
SpringValue springValue = new SpringValue("v1", "placeholder", mockedConfigChange, "mockedConfigChange", field, false);
springValues.add(springValue);
when(springValueRegistry.get(any(), any())).thenReturn(springValues);
when(polarisConfigProperties.isAutoRefresh()).thenReturn(true);
@ -89,33 +122,5 @@ public class PolarisPropertiesSourceAutoRefresherTest {
Assert.assertEquals("v3", polarisPropertySource.getProperty("k3"));
Assert.assertNull(polarisPropertySource.getProperty("k2"));
Assert.assertEquals("v4", polarisPropertySource.getProperty("k4"));
verify(contextRefresher).refresh();
}
@Test
public void testNewConfigFile() {
PolarisPropertySourceAutoRefresher refresher = new PolarisPropertySourceAutoRefresher(polarisConfigProperties,
polarisPropertySourceManager, contextRefresher);
when(polarisConfigProperties.isAutoRefresh()).thenReturn(true);
Map<String, Object> emptyContent = new HashMap<>();
MockedConfigKVFile file = new MockedConfigKVFile(emptyContent);
PolarisPropertySource polarisPropertySource = new PolarisPropertySource(testNamespace, testServiceName, testFileName,
file, emptyContent);
when(polarisPropertySourceManager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource));
ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", null, "v1", ChangeType.ADDED);
Map<String, ConfigPropertyChangeInfo> changeInfos = new HashMap<>();
changeInfos.put("k1", changeInfo);
ConfigKVFileChangeEvent event = new ConfigKVFileChangeEvent(changeInfos);
refresher.onApplicationEvent(null);
file.fireChangeListener(event);
Assert.assertEquals("v1", polarisPropertySource.getProperty("k1"));
verify(contextRefresher).refresh();
}
}

@ -24,7 +24,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RateLimitProto;

@ -28,7 +28,7 @@ import javax.annotation.PostConstruct;
import com.google.common.collect.Maps;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -161,6 +161,6 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
private Map<String, String> getRuleExpressionLabels(ServerWebExchange exchange, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service);
return ExpressionLabelUtils.resolve(exchange, expressionLabels);
return SpringWebExpressionLabelUtils.resolve(exchange, expressionLabels);
}
}

@ -31,7 +31,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -158,6 +158,6 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
private Map<String, String> getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service);
return ExpressionLabelUtils.resolve(request, expressionLabels);
return ServletExpressionLabelUtils.resolve(request, expressionLabels);
}
}

@ -26,7 +26,7 @@ import java.util.concurrent.CountDownLatch;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -75,15 +75,15 @@ import static org.mockito.Mockito.when;
public class QuotaCheckReactiveFilterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
private static MockedStatic<SpringWebExpressionLabelUtils> expressionLabelUtilsMockedStatic;
private final PolarisRateLimiterLabelReactiveResolver labelResolver =
exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver");
private QuotaCheckReactiveFilter quotaCheckReactiveFilter;
@BeforeClass
public static void beforeClass() {
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet()))
expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class);
when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet()))
.thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);

@ -30,7 +30,7 @@ import javax.servlet.http.HttpServletRequest;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
@ -74,7 +74,7 @@ import static org.mockito.Mockito.when;
public class QuotaCheckServletFilterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
private static MockedStatic<SpringWebExpressionLabelUtils> expressionLabelUtilsMockedStatic;
private PolarisRateLimiterLabelServletResolver labelResolver =
exchange -> Collections.singletonMap("ServletResolver", "ServletResolver");
private QuotaCheckServletFilter quotaCheckServletFilter;
@ -82,8 +82,8 @@ public class QuotaCheckServletFilterTest {
@BeforeClass
public static void beforeClass() {
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet()))
expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class);
when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet()))
.thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);

@ -19,10 +19,7 @@
package com.tencent.cloud.polaris.router;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
@ -40,15 +37,11 @@ import com.tencent.cloud.common.pojo.PolarisServer;
import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils;
import com.tencent.cloud.polaris.loadbalancer.PolarisWeightedRule;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.plugins.router.metadata.MetadataRouter;
import com.tencent.polaris.plugins.router.nearby.NearbyRouter;
import com.tencent.polaris.plugins.router.rule.RuleBasedRouter;
import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
@ -82,24 +75,21 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule {
final static String STRATEGY_AVAILABILITY_FILTERING = "availabilityFilteringRule";
private final PolarisLoadBalancerProperties loadBalancerProperties;
private final PolarisNearByRouterProperties polarisNearByRouterProperties;
private final PolarisMetadataRouterProperties polarisMetadataRouterProperties;
private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
private final RouterAPI routerAPI;
private final List<RouterRequestInterceptor> requestInterceptors;
private final List<RouterResponseInterceptor> responseInterceptors;
private final AbstractLoadBalancerRule delegateRule;
public PolarisLoadBalancerCompositeRule(RouterAPI routerAPI,
PolarisLoadBalancerProperties polarisLoadBalancerProperties,
PolarisNearByRouterProperties polarisNearByRouterProperties,
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties,
IClientConfig iClientConfig) {
IClientConfig iClientConfig,
List<RouterRequestInterceptor> requestInterceptors,
List<RouterResponseInterceptor> responseInterceptors) {
this.routerAPI = routerAPI;
this.polarisNearByRouterProperties = polarisNearByRouterProperties;
this.loadBalancerProperties = polarisLoadBalancerProperties;
this.polarisMetadataRouterProperties = polarisMetadataRouterProperties;
this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties;
this.requestInterceptors = requestInterceptors;
this.responseInterceptors = responseInterceptors;
delegateRule = getRule();
delegateRule.initWithNiwsConfig(iClientConfig);
@ -118,80 +108,66 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule {
}
// 2. filter by router
List<Server> serversAfterRouter = doRouter(allServers, key);
// 3. filter by load balance.
// A LoadBalancer needs to be regenerated for each request,
// because the list of servers may be different after filtered by router
ILoadBalancer loadBalancer = new SimpleLoadBalancer();
loadBalancer.addServers(serversAfterRouter);
delegateRule.setLoadBalancer(loadBalancer);
if (key instanceof PolarisRouterContext) {
PolarisRouterContext routerContext = (PolarisRouterContext) key;
List<Server> serversAfterRouter = doRouter(allServers, routerContext);
// 3. filter by load balance.
// A LoadBalancer needs to be regenerated for each request,
// because the list of servers may be different after filtered by router
ILoadBalancer loadBalancer = new SimpleLoadBalancer();
loadBalancer.addServers(serversAfterRouter);
delegateRule.setLoadBalancer(loadBalancer);
}
return delegateRule.choose(key);
}
List<Server> doRouter(List<Server> allServers, Object key) {
List<Server> doRouter(List<Server> allServers, PolarisRouterContext routerContext) {
ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers);
// filter instance by routers
ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, key);
ProcessRoutersRequest processRoutersRequest = buildProcessRoutersBaseRequest(serviceInstances);
// process request interceptors
processRouterRequestInterceptors(processRoutersRequest, routerContext);
// process router chain
ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest);
List<Server> filteredInstances = new ArrayList<>();
// process response interceptors
processRouterResponseInterceptors(routerContext, processRoutersResponse);
// transfer polaris server to ribbon server
ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances();
List<Server> filteredInstances = new ArrayList<>();
for (Instance instance : filteredServiceInstances.getInstances()) {
filteredInstances.add(new PolarisServer(serviceInstances, instance));
}
return filteredInstances;
}
ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, Object key) {
ProcessRoutersRequest buildProcessRoutersBaseRequest(ServiceInstances serviceInstances) {
ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest();
processRoutersRequest.setDstInstances(serviceInstances);
// metadata router
if (polarisMetadataRouterProperties.isEnabled()) {
Map<String, String> transitiveLabels = getRouterLabels(key, PolarisRouterContext.TRANSITIVE_LABELS);
processRoutersRequest.putRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, transitiveLabels);
}
// nearby router
if (polarisNearByRouterProperties.isEnabled()) {
Map<String, String> nearbyRouterMetadata = new HashMap<>();
nearbyRouterMetadata.put(NearbyRouter.ROUTER_ENABLED, "true");
processRoutersRequest.putRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY, nearbyRouterMetadata);
}
// rule based router
// set dynamic switch for rule based router
boolean ruleBasedRouterEnabled = polarisRuleBasedRouterProperties.isEnabled();
Map<String, String> ruleRouterMetadata = new HashMap<>();
ruleRouterMetadata.put(RuleBasedRouter.ROUTER_ENABLED, String.valueOf(ruleBasedRouterEnabled));
processRoutersRequest.putRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED, ruleRouterMetadata);
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setNamespace(MetadataContext.LOCAL_NAMESPACE);
serviceInfo.setService(MetadataContext.LOCAL_SERVICE);
if (ruleBasedRouterEnabled) {
Map<String, String> ruleRouterLabels = getRouterLabels(key, PolarisRouterContext.RULE_ROUTER_LABELS);
// The label information that the rule based routing depends on
// is placed in the metadata of the source service for transmission.
// Later, can consider putting it in routerMetadata like other routers.
serviceInfo.setMetadata(ruleRouterLabels);
}
processRoutersRequest.setSourceService(serviceInfo);
return processRoutersRequest;
}
private Map<String, String> getRouterLabels(Object key, String type) {
if (key instanceof PolarisRouterContext) {
return ((PolarisRouterContext) key).getLabels(type);
void processRouterRequestInterceptors(ProcessRoutersRequest processRoutersRequest, PolarisRouterContext routerContext) {
for (RouterRequestInterceptor requestInterceptor : requestInterceptors) {
requestInterceptor.apply(processRoutersRequest, routerContext);
}
}
private void processRouterResponseInterceptors(PolarisRouterContext routerContext, ProcessRoutersResponse processRoutersResponse) {
if (!CollectionUtils.isEmpty(responseInterceptors)) {
for (RouterResponseInterceptor responseInterceptor : responseInterceptors) {
responseInterceptor.apply(processRoutersResponse, routerContext);
}
}
return Collections.emptyMap();
}
public AbstractLoadBalancerRule getRule() {

@ -18,9 +18,14 @@
package com.tencent.cloud.polaris.router;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
@ -30,15 +35,14 @@ import org.springframework.util.CollectionUtils;
* @author lepdou 2022-05-17
*/
public class PolarisRouterContext {
/**
* the label for rule router.
* the labels for rule router, contain transitive metadata.
*/
public static final String RULE_ROUTER_LABELS = "ruleRouter";
public static final String ROUTER_LABELS = "allMetadata";
/**
* transitive labels.
*/
public static final String TRANSITIVE_LABELS = "transitive";
public static final String TRANSITIVE_LABELS = "transitiveMetadata";
private Map<String, Map<String, String>> labels;
@ -53,7 +57,54 @@ public class PolarisRouterContext {
return Collections.unmodifiableMap(subLabels);
}
public void setLabels(String labelType, Map<String, String> subLabels) {
public Map<String, String> getLabels(String labelType, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> typeLabels = getLabels(labelType);
if (CollectionUtils.isEmpty(typeLabels)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String key : labelKeys) {
String value = typeLabels.get(key);
if (StringUtils.isNotBlank(value)) {
labels.put(key, value);
}
}
return labels;
}
public String getLabel(String labelKey) {
Map<String, String> routerLabels = labels.get(ROUTER_LABELS);
if (CollectionUtils.isEmpty(routerLabels)) {
return StringUtils.EMPTY;
}
return routerLabels.get(labelKey);
}
public Set<String> getLabelAsSet(String labelKey) {
Map<String, String> routerLabels = labels.get(ROUTER_LABELS);
if (CollectionUtils.isEmpty(routerLabels)) {
return Collections.emptySet();
}
for (Map.Entry<String, String> entry : routerLabels.entrySet()) {
if (StringUtils.equalsIgnoreCase(labelKey, entry.getKey())) {
String keysStr = entry.getValue();
if (StringUtils.isNotBlank(keysStr)) {
String[] keysArr = StringUtils.split(keysStr, ",");
return new HashSet<>(Arrays.asList(keysArr));
}
}
}
return Collections.emptySet();
}
public void putLabels(String labelType, Map<String, String> subLabels) {
if (this.labels == null) {
this.labels = new HashMap<>();
}

@ -24,7 +24,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RoutingProto;

@ -18,13 +18,14 @@
package com.tencent.cloud.polaris.router.config;
import java.util.List;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IRule;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
import com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule;
import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor;
import com.tencent.polaris.router.api.core.RouterAPI;
import org.springframework.context.annotation.Bean;
@ -39,12 +40,9 @@ public class RibbonConfiguration {
@Bean
public IRule polarisLoadBalancerCompositeRule(RouterAPI routerAPI,
PolarisLoadBalancerProperties polarisLoadBalancerProperties,
PolarisNearByRouterProperties polarisNearByRouterProperties,
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties,
IClientConfig iClientConfig) {
return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties,
polarisNearByRouterProperties, polarisMetadataRouterProperties,
polarisRuleBasedRouterProperties, iClientConfig);
IClientConfig iClientConfig, List<RouterRequestInterceptor> requestInterceptors,
List<RouterResponseInterceptor> responseInterceptors) {
return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties, iClientConfig,
requestInterceptors, responseInterceptors);
}
}

@ -25,8 +25,12 @@ import com.tencent.cloud.polaris.router.beanprocessor.LoadBalancerInterceptorBea
import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -63,4 +67,22 @@ public class RouterAutoConfiguration {
public RouterRuleLabelResolver routerRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
return new RouterRuleLabelResolver(serviceRuleManager);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.polaris.router.metadata-router.enabled", matchIfMissing = true)
public MetadataRouterRequestInterceptor metadataRouterRequestInterceptor(PolarisMetadataRouterProperties polarisMetadataRouterProperties) {
return new MetadataRouterRequestInterceptor(polarisMetadataRouterProperties);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.polaris.router.nearby-router.enabled", matchIfMissing = true)
public NearbyRouterRequestInterceptor nearbyRouterRequestInterceptor(PolarisNearByRouterProperties polarisNearByRouterProperties) {
return new NearbyRouterRequestInterceptor(polarisNearByRouterProperties);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.polaris.router.rule-router.enabled", matchIfMissing = true)
public RuleBasedRouterRequestInterceptor ruleBasedRouterRequestInterceptor(PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
return new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties);
}
}

@ -25,7 +25,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils;
import feign.RequestTemplate;
import org.apache.commons.lang.StringUtils;

@ -71,7 +71,7 @@ public class PolarisFeignLoadBalancer extends FeignLoadBalancer {
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get()
routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE));
Map<String, String> labelHeaderValuesMap = new HashMap<>();
@ -86,7 +86,7 @@ public class PolarisFeignLoadBalancer extends FeignLoadBalancer {
catch (UnsupportedEncodingException e) {
throw new RuntimeException("unsupported charset exception " + UTF_8);
}
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labelHeaderValuesMap);
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labelHeaderValuesMap);
return routerContext;
}

@ -0,0 +1,57 @@
/*
* 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.router.interceptor;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.polaris.plugins.router.metadata.MetadataRouter;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
/**
* Router request interceptor for metadata router.
* @author lepdou 2022-07-06
*/
public class MetadataRouterRequestInterceptor implements RouterRequestInterceptor {
private static final String LABEL_KEY_METADATA_ROUTER_KEYS = "system-metadata-router-keys";
private final PolarisMetadataRouterProperties polarisMetadataRouterProperties;
public MetadataRouterRequestInterceptor(PolarisMetadataRouterProperties polarisMetadataRouterProperties) {
this.polarisMetadataRouterProperties = polarisMetadataRouterProperties;
}
@Override
public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) {
if (!polarisMetadataRouterProperties.isEnabled()) {
return;
}
// 1. get metadata router label keys
Set<String> metadataRouterKeys = routerContext.getLabelAsSet(LABEL_KEY_METADATA_ROUTER_KEYS);
// 2. get metadata router labels
Map<String, String> metadataRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS,
metadataRouterKeys);
// 3. set metadata router labels to request
request.addRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, metadataRouterLabels);
}
}

@ -0,0 +1,53 @@
/*
* 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.router.interceptor;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.polaris.plugins.router.nearby.NearbyRouter;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
/**
* Router request interceptor for nearby router.
* @author lepdou 2022-07-06
*/
public class NearbyRouterRequestInterceptor implements RouterRequestInterceptor {
private final PolarisNearByRouterProperties polarisNearByRouterProperties;
public NearbyRouterRequestInterceptor(PolarisNearByRouterProperties polarisNearByRouterProperties) {
this.polarisNearByRouterProperties = polarisNearByRouterProperties;
}
@Override
public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) {
if (!polarisNearByRouterProperties.isEnabled()) {
return;
}
Map<String, String> nearbyRouterMetadata = new HashMap<>();
nearbyRouterMetadata.put(NearbyRouter.ROUTER_ENABLED, "true");
request.addRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY, nearbyRouterMetadata);
}
}

@ -0,0 +1,59 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.interceptor;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.polaris.plugins.router.rule.RuleBasedRouter;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
/**
* Router request interceptor for rule based router.
* @author lepdou 2022-07-06
*/
public class RuleBasedRouterRequestInterceptor implements RouterRequestInterceptor {
private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
public RuleBasedRouterRequestInterceptor(PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties;
}
@Override
public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) {
boolean ruleBasedRouterEnabled = polarisRuleBasedRouterProperties.isEnabled();
// set dynamic switch for rule based router
Map<String, String> metadata = new HashMap<>();
metadata.put(RuleBasedRouter.ROUTER_ENABLED, String.valueOf(ruleBasedRouterEnabled));
request.addRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED, metadata);
// The label information that the rule based routing depends on
// is placed in the metadata of the source service for transmission.
// Later, can consider putting it in routerMetadata like other routers.
if (ruleBasedRouterEnabled) {
Map<String, String> ruleRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS);
request.getSourceService().setMetadata(ruleRouterLabels);
}
}
}

@ -30,7 +30,7 @@ import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
@ -137,8 +137,8 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels);
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
return routerContext;
}
@ -151,6 +151,6 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
return Collections.emptyMap();
}
return ExpressionLabelUtils.resolve(request, labelKeys);
return SpringWebExpressionLabelUtils.resolve(request, labelKeys);
}
}

@ -29,7 +29,7 @@ import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
@ -126,8 +126,8 @@ public class PolarisLoadBalancerClientFilter extends LoadBalancerClientFilter {
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels);
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
return routerContext;
}
@ -140,6 +140,6 @@ public class PolarisLoadBalancerClientFilter extends LoadBalancerClientFilter {
return Collections.emptyMap();
}
return ExpressionLabelUtils.resolve(exchange, labelKeys);
return SpringWebExpressionLabelUtils.resolve(exchange, labelKeys);
}
}

@ -0,0 +1,37 @@
/*
* 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.router.spi;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
/**
* The interceptor for router request. Router plugin can modify request by interceptor.
*
* @author lepdou 2022-07-11
*/
public interface RouterRequestInterceptor {
/**
* processing request.
* @param request the router request.
* @param routerContext the router context.
*/
void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext);
}

@ -0,0 +1,38 @@
/*
* 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.router.spi;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
/**
* The interceptor for router response. Router plugin can modify router response by interceptor.
*
* @author lepdou 2022-07-11
*/
public interface RouterResponseInterceptor {
/**
* processing router response.
*
* @param response the router response.
* @param routerContext the router context.
*/
void apply(ProcessRoutersResponse response, PolarisRouterContext routerContext);
}

@ -18,6 +18,7 @@
package com.tencent.cloud.polaris.router;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@ -44,6 +45,10 @@ import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperti
import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.Instance;
@ -87,23 +92,28 @@ public class PolarisLoadBalancerCompositeRuleTest {
private PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
@Mock
private RouterAPI routerAPI;
private IClientConfig config;
private String testNamespace = "testNamespace";
private String testCallerService = "testCallerService";
private String testCalleeService = "testCalleeService";
private final List<RouterRequestInterceptor> requestInterceptors = new ArrayList<>();
@Before
public void before() {
config = new DefaultClientConfigImpl();
config.loadDefaultValues();
requestInterceptors.add(new MetadataRouterRequestInterceptor(polarisMetadataRouterProperties));
requestInterceptors.add(new NearbyRouterRequestInterceptor(polarisNearByRouterProperties));
requestInterceptors.add(new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties));
}
@Test
public void testGetDefaultLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn("");
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule defaultRule = compositeRule.getRule();
@ -114,8 +124,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void testRandomLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RANDOM);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -126,8 +135,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void testWeightLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_WEIGHT);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -138,8 +146,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void testRetryLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RETRY);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -150,8 +157,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void testWeightedResponseTimeLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RESPONSE_TIME_WEIGHTED);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -162,8 +168,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void tesBestAvailableLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_BEST_AVAILABLE);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -174,8 +179,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void tesRoundRobinLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_ROUND_ROBIN);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -186,8 +190,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void testAvailabilityFilteringLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_AVAILABILITY_FILTERING);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -206,13 +209,18 @@ public class PolarisLoadBalancerCompositeRuleTest {
setTransitiveMetadata();
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
Map<String, String> oldRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS);
Map<String, String> newRouterLabels = new HashMap<>(oldRouterLabels);
newRouterLabels.put("system-metadata-router-keys", "k2");
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, newRouterLabels);
ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances);
compositeRule.processRouterRequestInterceptors(request, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA);
@ -236,13 +244,13 @@ public class PolarisLoadBalancerCompositeRuleTest {
setTransitiveMetadata();
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances);
compositeRule.processRouterRequestInterceptors(request, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY);
@ -267,13 +275,13 @@ public class PolarisLoadBalancerCompositeRuleTest {
setTransitiveMetadata();
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances);
compositeRule.processRouterRequestInterceptors(request, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED);
@ -298,8 +306,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
setTransitiveMetadata();
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
ProcessRoutersResponse assembleResponse = assembleProcessRoutersResponse();
when(routerAPI.processRouters(any())).thenReturn(assembleResponse);
@ -339,8 +346,8 @@ public class PolarisLoadBalancerCompositeRuleTest {
Map<String, String> routerLabels = new HashMap<>();
routerLabels.put("k2", "v2");
routerLabels.put("k3", "v3");
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, routerLabels);
routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, routerLabels);
return routerContext;
}

@ -20,7 +20,9 @@ package com.tencent.cloud.polaris.router;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Sets;
import org.junit.Assert;
import org.junit.Test;
@ -38,27 +40,76 @@ public class PolarisRouterContextTest {
labels.put("k2", "v2");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels);
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size());
Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size());
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k1"));
Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k2"));
Assert.assertNull(routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k3"));
Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size());
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k1"));
Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k2"));
Assert.assertNull(routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k3"));
}
@Test
public void testSetNull() {
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, null);
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, null);
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size());
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size());
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size());
}
@Test
public void testGetEmptyRouterContext() {
PolarisRouterContext routerContext = new PolarisRouterContext();
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size());
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size());
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size());
}
@Test
public void testGetLabelByKeys() {
Map<String, String> labels = new HashMap<>();
labels.put("k1", "v1");
labels.put("k2", "v2");
labels.put("k3", "v3");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
Map<String, String> resolvedLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS,
Sets.newHashSet("k1", "k2", "k4"));
Assert.assertEquals(2, resolvedLabels.size());
Assert.assertEquals("v1", resolvedLabels.get("k1"));
Assert.assertEquals("v2", resolvedLabels.get("k2"));
}
@Test
public void testGetLabel() {
Map<String, String> labels = new HashMap<>();
labels.put("k1", "v1");
labels.put("k2", "v2");
labels.put("k3", "v3");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
String resolvedLabel = routerContext.getLabel("k1");
Assert.assertEquals("v1", resolvedLabel);
}
@Test
public void testGetLabelAsSet() {
Map<String, String> labels = new HashMap<>();
labels.put("k1", "v1,v2,v3");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
Set<String> resolvedLabels = routerContext.getLabelAsSet("k1");
Assert.assertEquals(3, resolvedLabels.size());
Assert.assertTrue(resolvedLabels.contains("v1"));
Assert.assertTrue(resolvedLabels.contains("v2"));
Assert.assertTrue(resolvedLabels.contains("v3"));
}
}

@ -85,7 +85,7 @@ public class PolarisFeignLoadBalancerTest {
PolarisRouterContext routerContext = polarisFeignLoadBalancer.buildRouterContext(headers);
Assert.assertNotNull(routerContext);
Map<String, String> routerLabels = routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS);
Map<String, String> routerLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS);
Assert.assertNotNull(routerLabels);
Assert.assertEquals("v1", routerLabels.get("k1"));
Assert.assertEquals("v2", routerLabels.get("k2"));

@ -210,11 +210,11 @@ public class PolarisLoadBalancerInterceptorTest {
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).get("k1"));
Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).get("k2"));
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k1"));
Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k2"));
Assert.assertEquals("v4", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k4"));
Assert.assertEquals("GET", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("${http.method}"));
Assert.assertEquals("/user/get", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("${http.uri}"));
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k1"));
Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k2"));
Assert.assertEquals("v4", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k4"));
Assert.assertEquals("GET", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("${http.method}"));
Assert.assertEquals("/user/get", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("${http.uri}"));
}
}
}

@ -128,7 +128,7 @@ public class PolarisLoadBalancerClientFilterTest {
PolarisRouterContext routerContext = polarisLoadBalancerClientFilter.genRouterContext(webExchange, calleeService);
Map<String, String> routerLabels = routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS);
Map<String, String> routerLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS);
Assert.assertEquals("v1", routerLabels.get("${http.header.k1}"));
Assert.assertEquals("zhangsan", routerLabels.get("${http.query.userid}"));
Assert.assertEquals("blue", routerLabels.get("env"));

@ -26,6 +26,27 @@ import org.springframework.core.Ordered;
*/
public final class MetadataConstant {
/**
* sct transitive header prefix.
*/
public static final String SCT_TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-";
/**
* sct transitive header prefix length.
*/
public static final int SCT_TRANSITIVE_HEADER_PREFIX_LENGTH = SCT_TRANSITIVE_HEADER_PREFIX.length();
/**
* polaris transitive header prefix.
*/
public static final String POLARIS_TRANSITIVE_HEADER_PREFIX = "X-Polaris-Metadata-Transitive-";
/**
* polaris transitive header prefix length.
*/
public static final int POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH = POLARIS_TRANSITIVE_HEADER_PREFIX.length();
private MetadataConstant() {
}
/**
* Order of filter, interceptor, ...
*/

@ -19,11 +19,8 @@
package com.tencent.cloud.common.metadata.config;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter;
import com.tencent.cloud.common.spi.InstanceMetadataProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
@ -51,16 +48,4 @@ public class MetadataAutoConfiguration {
return new StaticMetadataManager(metadataLocalProperties, instanceMetadataProvider);
}
/**
* Create when gateway application is SCG.
*/
@Configuration
@ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.GlobalFilter")
static class MetadataScgFilterConfig {
@Bean
public GlobalFilter metadataFirstScgFilter() {
return new MetadataFirstScgFilter();
}
}
}

@ -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.common.metadata.filter.gateway;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER;
/**
* Scg output first filter used for setting peer info in context.
*
* @author Haotian Zhang
*/
public class MetadataFirstScgFilter implements GlobalFilter, Ordered {
/**
* Order of MetadataFirstScgFilter.
*/
public static final int METADATA_FIRST_FILTER_ORDER = ROUTE_TO_URL_FILTER_ORDER + 1;
@Override
public int getOrder() {
return METADATA_FIRST_FILTER_ORDER;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// get metadata of current thread
MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT);
if (metadataContext == null) {
metadataContext = MetadataContextHolder.get();
}
exchange.getAttributes().put(MetadataConstant.HeaderName.METADATA_CONTEXT, metadataContext);
return chain.filter(exchange);
}
}

@ -0,0 +1,65 @@
/*
* 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.common.rule;
import java.util.List;
/**
* Condition expression.
* @author lepdou 2022-07-06
*/
public class Condition {
private String key;
private String operation;
private List<String> values;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public List<String> getValues() {
return values;
}
public void setValues(List<String> values) {
this.values = values;
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
@Override
public String toString() {
return "Condition{" +
"key='" + key + '\'' +
", values='" + values + '\'' +
", operation='" + operation + '\'' +
'}';
}
}

@ -0,0 +1,48 @@
/*
* 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.common.rule;
import java.util.List;
import java.util.Map;
/**
* The util for condition expression.
* @author lepdou 2022-07-11
*/
public final class ConditionUtils {
private ConditionUtils() {
}
public static boolean match(Map<String, String> actualValues, List<Condition> conditions) {
boolean allMatched = true;
for (Condition condition : conditions) {
List<String> expectedValues = condition.getValues();
String operation = condition.getOperation();
String key = condition.getKey();
String actualValue = actualValues.get(key);
if (!Operation.match(expectedValues, actualValue, operation)) {
allMatched = false;
break;
}
}
return allMatched;
}
}

@ -0,0 +1,54 @@
/*
* 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.common.rule;
/**
* Key/value pair.
* @author lepdou 2022-07-06
*/
public class KVPair {
private String key;
private String value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "KVPair{" +
"key='" + key + '\'' +
", value='" + value + '\'' +
'}';
}
}

@ -0,0 +1,49 @@
/*
* 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.common.rule;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.util.CollectionUtils;
/**
* The util for key/value pair.
* @author lepdou 2022-07-11
*/
public final class KVPairUtils {
private KVPairUtils() {
}
public static Map<String, String> toMap(List<KVPair> labels) {
if (CollectionUtils.isEmpty(labels)) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>();
labels.forEach(label -> {
result.put(label.getKey(), label.getValue());
});
return result;
}
}

@ -0,0 +1,133 @@
/*
* 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.common.rule;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
/**
* The condition operation.
* @author lepdou 2022-07-11
*/
public enum Operation {
/**
* case sensitive string equals.
*/
EQUAL("EQUAL"),
/**
* case sensitive string not equals.
*/
NOT_EQUAL("NOT_EQUAL"),
/**
* whether element in collection.
*/
IN("IN"),
/**
* whether element not in collection.
*/
NOT_IN("NOT_IN"),
/**
* regex operation.
*/
REGEX("REGEX"),
/**
* whether element is blank.
*/
BLANK("BLANK"),
/**
* whether element is not blank.
*/
NOT_BLANK("NOT_BLANK");
private final String value;
Operation(String value) {
this.value = value;
}
public static boolean match(List<String> expectedValues, String actualValue, String rawOperation) {
String firstExpectedValue = null;
if (!CollectionUtils.isEmpty(expectedValues)) {
firstExpectedValue = expectedValues.get(0);
}
switch (getOperation(rawOperation)) {
case EQUAL:
return firstExpectedValue != null && StringUtils.equals(actualValue, firstExpectedValue);
case NOT_EQUAL:
return firstExpectedValue == null || !StringUtils.equals(actualValue, firstExpectedValue);
case BLANK:
return StringUtils.isBlank(actualValue);
case NOT_BLANK:
return !StringUtils.isBlank(actualValue);
case IN:
if (CollectionUtils.isEmpty(expectedValues)) {
return false;
}
return expectedValues.contains(actualValue);
case NOT_IN:
if (CollectionUtils.isEmpty(expectedValues)) {
return true;
}
return !expectedValues.contains(actualValue);
case REGEX:
if (firstExpectedValue == null) {
return false;
}
Pattern r = Pattern.compile(firstExpectedValue);
return r.matcher(actualValue).matches();
default:
return false;
}
}
public static Operation getOperation(String operation) {
if (StringUtils.equalsIgnoreCase(operation, EQUAL.value)) {
return EQUAL;
}
if (StringUtils.equalsIgnoreCase(operation, NOT_EQUAL.value)) {
return NOT_EQUAL;
}
if (StringUtils.equalsIgnoreCase(operation, IN.value)) {
return IN;
}
if (StringUtils.equalsIgnoreCase(operation, NOT_IN.value)) {
return NOT_IN;
}
if (StringUtils.equalsIgnoreCase(operation, REGEX.value)) {
return REGEX;
}
if (StringUtils.equalsIgnoreCase(operation, BLANK.value)) {
return BLANK;
}
if (StringUtils.equalsIgnoreCase(operation, NOT_BLANK.value)) {
return NOT_BLANK;
}
throw new RuntimeException("Unsupported operation. operation = " + operation);
}
public String getValue() {
return value;
}
}

@ -1,319 +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.common.util;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
/**
* the utils for parse label expression.
*
* @author lepdou 2022-05-13
* @author cheese8 2022-06-20
*/
public final class ExpressionLabelUtils {
/**
* the expression prefix of header label.
*/
public static final String LABEL_HEADER_PREFIX = "${http.header.";
/**
* the length of expression header label prefix.
*/
public static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length();
/**
* the expression prefix of query.
*/
public static final String LABEL_QUERY_PREFIX = "${http.query.";
/**
* the length of expression query label prefix.
*/
public static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length();
/**
* the expression prefix of cookie.
*/
public static final String LABEL_COOKIE_PREFIX = "${http.cookie.";
/**
* the length of expression cookie label prefix.
*/
public static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length();
/**
* the expression of method.
*/
public static final String LABEL_METHOD = "${http.method}";
/**
* the expression of uri.
*/
public static final String LABEL_URI = "${http.uri}";
/**
* the suffix of expression.
*/
public static final String LABEL_SUFFIX = "}";
private ExpressionLabelUtils() {
}
public static boolean isExpressionLabel(String labelKey) {
if (StringUtils.isEmpty(labelKey)) {
return false;
}
if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey) ||
StringUtils.startsWithIgnoreCase(LABEL_URI, labelKey)) {
return true;
}
return (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX) ||
StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX) ||
StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX))
&& StringUtils.endsWith(labelKey, LABEL_SUFFIX);
}
public static Map<String, String> resolve(HttpServletRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (!isExpressionLabel(labelKey)) {
continue;
}
if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) {
String headerKey = parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, request.getHeader(headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) {
String queryKey = parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(request.getQueryString(), queryKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) {
String cookieKey = parseCookieKey(labelKey);
if (StringUtils.isBlank(cookieKey)) {
continue;
}
labels.put(labelKey, getCookieValue(request.getCookies(), cookieKey));
}
else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) {
labels.put(labelKey, request.getMethod());
}
else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) {
labels.put(labelKey, request.getRequestURI());
}
}
return labels;
}
public static Map<String, String> resolve(ServerWebExchange exchange, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (!isExpressionLabel(labelKey)) {
continue;
}
if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) {
String headerKey = parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, getHeaderValue(exchange.getRequest(), headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) {
String queryKey = parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(exchange.getRequest(), queryKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) {
String cookieKey = parseCookieKey(labelKey);
if (StringUtils.isBlank(cookieKey)) {
continue;
}
labels.put(labelKey, getCookieValue(exchange.getRequest(), cookieKey));
}
else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) {
labels.put(labelKey, exchange.getRequest().getMethodValue());
}
else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) {
labels.put(labelKey, exchange.getRequest().getURI().getPath());
}
}
return labels;
}
public static Map<String, String> resolve(HttpRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (!isExpressionLabel(labelKey)) {
continue;
}
if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) {
String headerKey = parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, getHeaderValue(request, headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) {
String queryKey = parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(request, queryKey));
}
else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) {
labels.put(labelKey, request.getMethodValue());
}
else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) {
labels.put(labelKey, request.getURI().getPath());
}
}
return labels;
}
public static String parseHeaderKey(String expression) {
return expression.substring(LABEL_HEADER_PREFIX_LEN, expression.length() - 1);
}
public static String parseQueryKey(String expression) {
return expression.substring(LABEL_QUERY_PREFIX_LEN, expression.length() - 1);
}
public static String parseCookieKey(String expression) {
return expression.substring(LABEL_COOKIE_PREFIX_LEN, expression.length() - 1);
}
public static String getQueryValue(String queryString, String queryKey) {
if (StringUtils.isBlank(queryString)) {
return StringUtils.EMPTY;
}
String[] queries = StringUtils.split(queryString, "&");
if (queries == null || queries.length == 0) {
return StringUtils.EMPTY;
}
for (String query : queries) {
String[] queryKV = StringUtils.split(query, "=");
if (queryKV != null && queryKV.length == 2 && StringUtils.equals(queryKV[0], queryKey)) {
return queryKV[1];
}
}
return StringUtils.EMPTY;
}
public static String getCookieValue(Cookie[] cookies, String key) {
if (cookies == null || cookies.length == 0) {
return StringUtils.EMPTY;
}
for (Cookie cookie : cookies) {
if (StringUtils.equals(cookie.getName(), key)) {
return cookie.getValue();
}
}
return StringUtils.EMPTY;
}
public static String getHeaderValue(ServerHttpRequest request, String key) {
String value = request.getHeaders().getFirst(key);
if (value == null) {
return StringUtils.EMPTY;
}
return value;
}
public static String getQueryValue(ServerHttpRequest request, String key) {
MultiValueMap<String, String> queries = request.getQueryParams();
if (CollectionUtils.isEmpty(queries)) {
return StringUtils.EMPTY;
}
String value = queries.getFirst(key);
if (value == null) {
return StringUtils.EMPTY;
}
return value;
}
public static String getCookieValue(ServerHttpRequest request, String key) {
HttpCookie cookie = request.getCookies().getFirst(key);
if (cookie == null) {
return StringUtils.EMPTY;
}
return cookie.getValue();
}
public static String getHeaderValue(HttpRequest request, String key) {
HttpHeaders headers = request.getHeaders();
return headers.getFirst(key);
}
public static String getQueryValue(HttpRequest request, String key) {
String query = request.getURI().getQuery();
return getQueryValue(query, key);
}
public static String getFirstValue(Map<String, Collection<String>> valueMaps, String key) {
if (CollectionUtils.isEmpty(valueMaps)) {
return StringUtils.EMPTY;
}
Collection<String> values = valueMaps.get(key);
if (CollectionUtils.isEmpty(values)) {
return StringUtils.EMPTY;
}
for (String value : values) {
return value;
}
return StringUtils.EMPTY;
}
}

@ -61,6 +61,16 @@ public final class JacksonUtils {
}
}
public static <T> T deserialize(String jsonStr, Class<T> type) {
try {
return OM.readValue(jsonStr, type);
}
catch (JsonProcessingException e) {
LOG.error("Json to object failed. {}", type, e);
throw new RuntimeException("Json to object failed.", e);
}
}
/**
* Json to Map.
* @param jsonStr Json String
@ -84,4 +94,14 @@ public final class JacksonUtils {
throw new RuntimeException("Json to map failed.", e);
}
}
public static <T> T json2JavaBean(String content, Class<T> valueType) {
try {
return OM.readValue(content, valueType);
}
catch (Exception e) {
LOG.error("json {} to class {} failed. ", content, valueType, e);
throw new RuntimeException("json to class failed.", e);
}
}
}

@ -0,0 +1,136 @@
/*
* 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.common.util.expresstion;
import java.util.Collection;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
/**
* the utils for parse label expression.
*
* @author lepdou 2022-05-13
* @author cheese8 2022-06-20
*/
public final class ExpressionLabelUtils {
/**
* the expression prefix of header label.
*/
public static final String LABEL_HEADER_PREFIX = "${http.header.";
/**
* the length of expression header label prefix.
*/
public static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length();
/**
* the expression prefix of query.
*/
public static final String LABEL_QUERY_PREFIX = "${http.query.";
/**
* the length of expression query label prefix.
*/
public static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length();
/**
* the expression prefix of cookie.
*/
public static final String LABEL_COOKIE_PREFIX = "${http.cookie.";
/**
* the length of expression cookie label prefix.
*/
public static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length();
/**
* the expression of method.
*/
public static final String LABEL_METHOD = "${http.method}";
/**
* the expression of uri.
*/
public static final String LABEL_URI = "${http.uri}";
/**
* the suffix of expression.
*/
public static final String LABEL_SUFFIX = "}";
private ExpressionLabelUtils() {
}
public static boolean isExpressionLabel(String labelKey) {
if (StringUtils.isEmpty(labelKey)) {
return false;
}
if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey) ||
StringUtils.startsWithIgnoreCase(LABEL_URI, labelKey)) {
return true;
}
return (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX) ||
StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX) ||
StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX))
&& StringUtils.endsWith(labelKey, LABEL_SUFFIX);
}
public static String parseHeaderKey(String expression) {
return expression.substring(LABEL_HEADER_PREFIX_LEN, expression.length() - 1);
}
public static String parseQueryKey(String expression) {
return expression.substring(LABEL_QUERY_PREFIX_LEN, expression.length() - 1);
}
public static String parseCookieKey(String expression) {
return expression.substring(LABEL_COOKIE_PREFIX_LEN, expression.length() - 1);
}
public static String getQueryValue(String queryString, String queryKey) {
if (StringUtils.isBlank(queryString)) {
return StringUtils.EMPTY;
}
String[] queries = StringUtils.split(queryString, "&");
if (queries == null || queries.length == 0) {
return StringUtils.EMPTY;
}
for (String query : queries) {
String[] queryKV = StringUtils.split(query, "=");
if (queryKV != null && queryKV.length == 2 && StringUtils.equals(queryKV[0], queryKey)) {
return queryKV[1];
}
}
return StringUtils.EMPTY;
}
public static String getFirstValue(Map<String, Collection<String>> valueMaps, String key) {
if (CollectionUtils.isEmpty(valueMaps)) {
return StringUtils.EMPTY;
}
Collection<String> values = valueMaps.get(key);
if (CollectionUtils.isEmpty(values)) {
return StringUtils.EMPTY;
}
for (String value : values) {
return value;
}
return StringUtils.EMPTY;
}
}

@ -0,0 +1,96 @@
/*
* 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.common.util.expresstion;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
/**
* Parse labels from HttpServletRequest.
* @author lepdou 2022-07-11
*/
public final class ServletExpressionLabelUtils {
private ServletExpressionLabelUtils() {
}
public static Map<String, String> resolve(HttpServletRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (!ExpressionLabelUtils.isExpressionLabel(labelKey)) {
continue;
}
if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) {
String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, request.getHeader(headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) {
String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, ExpressionLabelUtils.getQueryValue(request.getQueryString(), queryKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_COOKIE_PREFIX)) {
String cookieKey = ExpressionLabelUtils.parseCookieKey(labelKey);
if (StringUtils.isBlank(cookieKey)) {
continue;
}
labels.put(labelKey, getCookieValue(request.getCookies(), cookieKey));
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) {
labels.put(labelKey, request.getMethod());
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) {
labels.put(labelKey, request.getRequestURI());
}
}
return labels;
}
public static String getCookieValue(Cookie[] cookies, String key) {
if (cookies == null || cookies.length == 0) {
return StringUtils.EMPTY;
}
for (Cookie cookie : cookies) {
if (StringUtils.equals(cookie.getName(), key)) {
return cookie.getValue();
}
}
return StringUtils.EMPTY;
}
}

@ -0,0 +1,161 @@
/*
* 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.common.util.expresstion;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
/**
* Parse labels from ServerWebExchange and HttpRequest.
* @author lepdou 2022-07-11
*/
public final class SpringWebExpressionLabelUtils {
private SpringWebExpressionLabelUtils() {
}
public static Map<String, String> resolve(ServerWebExchange exchange, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (!ExpressionLabelUtils.isExpressionLabel(labelKey)) {
continue;
}
if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) {
String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, getHeaderValue(exchange.getRequest(), headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) {
String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(exchange.getRequest(), queryKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_COOKIE_PREFIX)) {
String cookieKey = ExpressionLabelUtils.parseCookieKey(labelKey);
if (StringUtils.isBlank(cookieKey)) {
continue;
}
labels.put(labelKey, getCookieValue(exchange.getRequest(), cookieKey));
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) {
labels.put(labelKey, exchange.getRequest().getMethodValue());
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) {
labels.put(labelKey, exchange.getRequest().getURI().getPath());
}
}
return labels;
}
public static Map<String, String> resolve(HttpRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (!ExpressionLabelUtils.isExpressionLabel(labelKey)) {
continue;
}
if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) {
String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, getHeaderValue(request, headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) {
String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(request, queryKey));
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) {
labels.put(labelKey, request.getMethodValue());
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) {
labels.put(labelKey, request.getURI().getPath());
}
}
return labels;
}
public static String getHeaderValue(ServerHttpRequest request, String key) {
String value = request.getHeaders().getFirst(key);
if (value == null) {
return StringUtils.EMPTY;
}
return value;
}
public static String getQueryValue(ServerHttpRequest request, String key) {
MultiValueMap<String, String> queries = request.getQueryParams();
if (CollectionUtils.isEmpty(queries)) {
return StringUtils.EMPTY;
}
String value = queries.getFirst(key);
if (value == null) {
return StringUtils.EMPTY;
}
return value;
}
public static String getCookieValue(ServerHttpRequest request, String key) {
HttpCookie cookie = request.getCookies().getFirst(key);
if (cookie == null) {
return StringUtils.EMPTY;
}
return cookie.getValue();
}
public static String getHeaderValue(HttpRequest request, String key) {
HttpHeaders headers = request.getHeaders();
return headers.getFirst(key);
}
public static String getQueryValue(HttpRequest request, String key) {
String query = request.getURI().getQuery();
return ExpressionLabelUtils.getQueryValue(query, key);
}
}

@ -18,7 +18,6 @@
package com.tencent.cloud.common.metadata.config;
import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter;
import org.assertj.core.api.Assertions;
import org.junit.Test;
@ -50,9 +49,6 @@ public class MetadataAutoConfigurationTest {
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> {
Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
});
}
@ -65,9 +61,6 @@ public class MetadataAutoConfigurationTest {
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> {
Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
});
}
@ -80,9 +73,6 @@ public class MetadataAutoConfigurationTest {
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> {
Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
});
}
}

@ -0,0 +1,97 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.common.rule;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
/**
* Test for {@link Operation}.
* @author lepdou 2022-07-12
*/
@RunWith(MockitoJUnitRunner.class)
public class OperationTest {
@Test
public void testEqual() {
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "v1", Operation.EQUAL.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v2", Operation.EQUAL.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList(""), "v2", Operation.EQUAL.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "", Operation.EQUAL.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), null, Operation.EQUAL.getValue()));
Assert.assertFalse(Operation.match(Collections.emptyList(), "v1", Operation.EQUAL.getValue()));
}
@Test
public void testNotEqual() {
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v1", Operation.NOT_EQUAL.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "v2", Operation.NOT_EQUAL.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList(""), "v2", Operation.NOT_EQUAL.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "", Operation.NOT_EQUAL.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), null, Operation.NOT_EQUAL.getValue()));
Assert.assertTrue(Operation.match(Collections.emptyList(), "v1", Operation.NOT_EQUAL.getValue()));
}
@Test
public void testIn() {
Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v1", Operation.IN.getValue()));
Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v2", Operation.IN.getValue()));
Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v4", Operation.IN.getValue()));
Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "", Operation.IN.getValue()));
Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), null, Operation.IN.getValue()));
Assert.assertFalse(Operation.match(Collections.emptyList(), null, Operation.IN.getValue()));
}
@Test
public void testNotIn() {
Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v1", Operation.NOT_IN.getValue()));
Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v2", Operation.NOT_IN.getValue()));
Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v4", Operation.NOT_IN.getValue()));
Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "", Operation.NOT_IN.getValue()));
Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), null, Operation.NOT_IN.getValue()));
Assert.assertTrue(Operation.match(Collections.emptyList(), null, Operation.NOT_IN.getValue()));
}
@Test
public void testEmpty() {
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), null, Operation.BLANK.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "", Operation.BLANK.getValue()));
Assert.assertTrue(Operation.match(Collections.emptyList(), null, Operation.BLANK.getValue()));
}
@Test
public void testNotEmpty() {
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), null, Operation.NOT_BLANK.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "", Operation.NOT_BLANK.getValue()));
Assert.assertFalse(Operation.match(Collections.emptyList(), null, Operation.NOT_BLANK.getValue()));
Assert.assertTrue(Operation.match(Collections.emptyList(), "v1", Operation.NOT_BLANK.getValue()));
}
@Test
public void testRegex() {
Assert.assertTrue(Operation.match(Collections.singletonList("v[1~10]"), "v1", Operation.REGEX.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v[1~10]"), "v12", Operation.REGEX.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v[1~10]*"), "v12", Operation.REGEX.getValue()));
}
}

@ -23,6 +23,9 @@ import java.util.Map;
import java.util.Set;
import com.google.common.collect.Sets;
import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -105,7 +108,7 @@ public class ExpressionLabelUtilsTest {
request.setMethod(HttpMethod.GET.name());
request.setRequestURI("/users");
Map<String, String> result = ExpressionLabelUtils.resolve(request, labelKeys);
Map<String, String> result = ServletExpressionLabelUtils.resolve(request, labelKeys);
Assert.assertEquals("zhangsan", result.get(validLabel1));
Assert.assertEquals("zhangsan", result.get(validLabel2));
@ -149,7 +152,7 @@ public class ExpressionLabelUtilsTest {
.cookie(new HttpCookie("uid", "zhangsan")).build();
MockServerWebExchange exchange = new MockServerWebExchange.Builder(httpRequest).build();
Map<String, String> result = ExpressionLabelUtils.resolve(exchange, labelKeys);
Map<String, String> result = SpringWebExpressionLabelUtils.resolve(exchange, labelKeys);
Assert.assertEquals("zhangsan", result.get(validLabel1));
Assert.assertEquals("zhangsan", result.get(validLabel2));
@ -193,7 +196,7 @@ public class ExpressionLabelUtilsTest {
request.setURI(URI.create("http://calleeService/user/get?uid=zhangsan"));
request.getHeaders().add("uid", "zhangsan");
Map<String, String> result = ExpressionLabelUtils.resolve(request, labelKeys);
Map<String, String> result = SpringWebExpressionLabelUtils.resolve(request, labelKeys);
Assert.assertEquals("zhangsan", result.get(validLabel1));
Assert.assertEquals("zhangsan", result.get(validLabel2));

@ -63,6 +63,16 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-config</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-featureenv-plugin</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-gateway-plugin</artifactId>
</dependency>
</dependencies>
<build>

@ -71,7 +71,7 @@
<properties>
<revision>1.7.0-Hoxton.SR12-SNAPSHOT</revision>
<polaris.version>1.7.0</polaris.version>
<polaris.version>1.7.1-SNAPSHOT</polaris.version>
<logback.version>1.2.11</logback.version>
<mocktio.version>4.5.1</mocktio.version>
<byte-buddy.version>1.12.10</byte-buddy.version>
@ -151,6 +151,19 @@
<version>${revision}</version>
</dependency>
<!-- spring cloud tencent plugins -->
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-featureenv-plugin</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-gateway-plugin</artifactId>
<version>${revision}</version>
</dependency>
<!-- third part framework dependencies -->
<dependency>
<groupId>com.google.guava</groupId>

@ -12,6 +12,7 @@ spring:
groups:
- name: ${spring.application.name} # group name
files: [ "config/application.properties", "config/bootstrap.yml" ] # config/application.properties takes precedence over config/bootstrap.yml
refresh-behavior: specific_bean
management:
endpoints:
web:

@ -19,9 +19,14 @@
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
</project>

@ -7,8 +7,6 @@ spring:
cloud:
tencent:
metadata:
content:
env: blue
polaris:
address: grpc://183.47.111.80:8091
namespace: default

@ -18,6 +18,11 @@
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

@ -29,6 +29,16 @@
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-gateway-plugin</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-featureenv-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>

@ -6,11 +6,15 @@ spring:
name: GatewayScgService
cloud:
tencent:
metadata:
content:
env: blue
transitive:
- env
plugin:
scg:
staining:
enabled: true
rule-staining:
enabled: true
router:
feature-env:
enabled: true
polaris:
address: grpc://183.47.111.80:8091
namespace: default

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<packaging>pom</packaging>
<name>Spring Cloud Starter Tencent Solution</name>
<modules>
<module>spring-cloud-tencent-featureenv-plugin</module>
<module>spring-cloud-tencent-gateway-plugin</module>
</modules>
</project>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-featureenv-plugin</artifactId>
<name>Spring Cloud Tencent Feature Environment Plugin</name>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,42 @@
/*
* 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.plugin.featureenv;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Auto configuration for feature env.
* @author lepdou 2022-07-06
*/
@Configuration
@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.router.feature-env.enabled", matchIfMissing = true)
public class FeatureEnvAutoConfiguration {
@Bean
public FeatureEnvProperties featureEnvProperties() {
return new FeatureEnvProperties();
}
@Bean
public FeatureEnvRouterRequestInterceptor featureEnvRouterRequestInterceptor() {
return new FeatureEnvRouterRequestInterceptor();
}
}

@ -0,0 +1,39 @@
/*
* 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.plugin.featureenv;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* The properties for feature env.
* @author lepdou 2022-07-12
*/
@ConfigurationProperties("spring.cloud.tencent.plugin.router.feature-env")
public class FeatureEnvProperties {
private boolean enabled;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

@ -0,0 +1,65 @@
/*
* 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.plugin.featureenv;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.polaris.api.rpc.MetadataFailoverType;
import com.tencent.polaris.plugins.router.metadata.MetadataRouter;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import org.apache.commons.lang.StringUtils;
/**
* Build metadata router context for feature env scene.
* @author lepdou 2022-07-06
*/
public class FeatureEnvRouterRequestInterceptor implements RouterRequestInterceptor {
private static final String LABEL_KEY_FEATURE_ENV_ROUTER_KEY = "system-feature-env-router-label";
private static final String DEFAULT_FEATURE_ENV_ROUTER_LABEL = "env";
private static final String NOT_EXISTED_ENV = "NOT_EXISTED_ENV";
@Override
public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) {
//1. get feature env router label key
String envLabelKey = routerContext.getLabel(LABEL_KEY_FEATURE_ENV_ROUTER_KEY);
if (StringUtils.isBlank(envLabelKey)) {
envLabelKey = DEFAULT_FEATURE_ENV_ROUTER_LABEL;
}
//2. get feature env router label value
String envLabelValue = routerContext.getLabel(envLabelKey);
if (envLabelValue == null) {
// router to base env when not matched feature env
envLabelValue = NOT_EXISTED_ENV;
}
//3. set env metadata to router request
Map<String, String> envMetadata = new HashMap<>();
envMetadata.put(envLabelKey, envLabelValue);
request.addRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, envMetadata);
//4. set failover type to others
request.setMetadataFailoverType(MetadataFailoverType.METADATAFAILOVERNOTKEY);
}
}

@ -0,0 +1,10 @@
{
"properties": [
{
"name": "spring.cloud.tencent.plugin.router.feature-env.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for feature env plugin."
}
]
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.plugin.featureenv.FeatureEnvAutoConfiguration

@ -0,0 +1,104 @@
/*
* 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.plugin.featureenv;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.plugins.router.metadata.MetadataRouter;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
/**
* Test for {@link FeatureEnvRouterRequestInterceptor}.
* @author lepdou 2022-07-12
*/
@RunWith(MockitoJUnitRunner.class)
public class FeatureEnvRouterRequestInterceptorTest {
@Test
public void testDefaultRouterKey() {
Map<String, String> labels = new HashMap<>();
labels.put("env", "blue");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
ProcessRoutersRequest request = new ProcessRoutersRequest();
ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>());
request.setDstInstances(serviceInstances);
FeatureEnvRouterRequestInterceptor interceptor = new FeatureEnvRouterRequestInterceptor();
interceptor.apply(request, routerContext);
Map<String, String> metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA);
Assert.assertEquals(1, metadataRouterLabels.size());
Assert.assertEquals("blue", metadataRouterLabels.get("env"));
}
@Test
public void testSpecifyRouterKey() {
Map<String, String> labels = new HashMap<>();
labels.put("system-feature-env-router-label", "specify-env");
labels.put("specify-env", "blue");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
ProcessRoutersRequest request = new ProcessRoutersRequest();
ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>());
request.setDstInstances(serviceInstances);
FeatureEnvRouterRequestInterceptor interceptor = new FeatureEnvRouterRequestInterceptor();
interceptor.apply(request, routerContext);
Map<String, String> metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA);
Assert.assertEquals(1, metadataRouterLabels.size());
Assert.assertEquals("blue", metadataRouterLabels.get("specify-env"));
}
@Test
public void testNotExistedEnvLabel() {
Map<String, String> labels = new HashMap<>();
labels.put("system-feature-env-router-label", "specify-env");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
ProcessRoutersRequest request = new ProcessRoutersRequest();
ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>());
request.setDstInstances(serviceInstances);
FeatureEnvRouterRequestInterceptor interceptor = new FeatureEnvRouterRequestInterceptor();
interceptor.apply(request, routerContext);
Map<String, String> metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA);
Assert.assertEquals(1, metadataRouterLabels.size());
Assert.assertEquals("NOT_EXISTED_ENV", metadataRouterLabels.get("specify-env"));
}
}

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-gateway-plugin</artifactId>
<name>Spring Cloud Tencent Gateway Plugin</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,84 @@
/*
* 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.plugin.gateway;
import java.util.List;
import com.tencent.cloud.plugin.gateway.staining.StainingProperties;
import com.tencent.cloud.plugin.gateway.staining.TrafficStainer;
import com.tencent.cloud.plugin.gateway.staining.TrafficStainingGatewayFilter;
import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingExecutor;
import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingProperties;
import com.tencent.cloud.plugin.gateway.staining.rule.RuleTrafficStainer;
import com.tencent.cloud.plugin.gateway.staining.rule.StainingRuleManager;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Auto configuration for spring cloud gateway plugins.
* @author lepdou 2022-07-06
*/
@Configuration
@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.enabled", matchIfMissing = true)
public class SCGPluginsAutoConfiguration {
@Configuration
@ConditionalOnProperty("spring.cloud.tencent.plugin.scg.staining.enabled")
public static class StainingPluginConfiguration {
@Bean
public StainingProperties stainingProperties() {
return new StainingProperties();
}
@Configuration
@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled", matchIfMissing = true)
public static class RuleStainingPluginConfiguration {
@Bean
public RuleStainingProperties ruleStainingProperties() {
return new RuleStainingProperties();
}
@Bean
public StainingRuleManager stainingRuleManager(RuleStainingProperties stainingProperties, ConfigFileService configFileService) {
return new StainingRuleManager(stainingProperties, configFileService);
}
@Bean
public TrafficStainingGatewayFilter trafficStainingGatewayFilter(List<TrafficStainer> trafficStainer) {
return new TrafficStainingGatewayFilter(trafficStainer);
}
@Bean
public RuleStainingExecutor ruleStainingExecutor() {
return new RuleStainingExecutor();
}
@Bean
public RuleTrafficStainer ruleTrafficStainer(StainingRuleManager stainingRuleManager,
RuleStainingExecutor ruleStainingExecutor) {
return new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor);
}
}
}
}

@ -0,0 +1,39 @@
/*
* 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.plugin.gateway.staining;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* The properties for traffic staining.
* @author lepdou 2022-07-07
*/
@ConfigurationProperties("spring.cloud.tencent.plugin.scg.staining")
public class StainingProperties {
private boolean enabled;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

@ -0,0 +1,38 @@
/*
* 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.plugin.gateway.staining;
import java.util.Map;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
/**
* Staining according to request parameters. for example, when the request parameter uid=0, staining env=blue.
* @author lepdou 2022-07-06
*/
public interface TrafficStainer extends Ordered {
/**
* get stained labels from request.
* @param exchange the request.
* @return stained labels.
*/
Map<String, String> apply(ServerWebExchange exchange);
}

@ -0,0 +1,104 @@
/*
* 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.plugin.gateway.staining;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER;
/**
* Staining the request, and the stained labels will be passed to the link through transitive metadata.
* @author lepdou 2022-07-06
*/
public class TrafficStainingGatewayFilter implements GlobalFilter, Ordered {
private final List<TrafficStainer> trafficStainers;
public TrafficStainingGatewayFilter(List<TrafficStainer> trafficStainers) {
if (!CollectionUtils.isEmpty(trafficStainers)) {
trafficStainers.sort(Comparator.comparingInt(Ordered::getOrder));
}
this.trafficStainers = trafficStainers;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (CollectionUtils.isEmpty(trafficStainers)) {
return chain.filter(exchange);
}
// 1. get stained labels from request
Map<String, String> stainedLabels = getStainedLabels(exchange);
if (CollectionUtils.isEmpty(stainedLabels)) {
return chain.filter(exchange);
}
// 2. put stained labels to metadata context
ServerHttpRequest request = exchange.getRequest().mutate().headers((httpHeaders) -> {
MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT);
if (metadataContext == null) {
metadataContext = MetadataContextHolder.get();
}
Map<String, String> oldTransitiveMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
// append new transitive metadata
Map<String, String> newTransitiveMetadata = new HashMap<>(oldTransitiveMetadata);
newTransitiveMetadata.putAll(stainedLabels);
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE, newTransitiveMetadata);
}).build();
return chain.filter(exchange.mutate().request(request).build());
}
Map<String, String> getStainedLabels(ServerWebExchange exchange) {
Map<String, String> stainedLabels = new HashMap<>();
int size = trafficStainers.size();
for (int i = size - 1; i >= 0; i--) {
TrafficStainer stainer = trafficStainers.get(i);
Map<String, String> labels = stainer.apply(exchange);
if (!CollectionUtils.isEmpty(labels)) {
stainedLabels.putAll(labels);
}
}
return stainedLabels;
}
@Override
public int getOrder() {
return ROUTE_TO_URL_FILTER_ORDER + 1;
}
}

@ -0,0 +1,70 @@
/*
* 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.plugin.gateway.staining.rule;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.rule.Condition;
import com.tencent.cloud.common.rule.ConditionUtils;
import com.tencent.cloud.common.rule.KVPairUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
/**
* Resolve labels from request by staining rule.
* @author lepdou 2022-07-11
*/
public class RuleStainingExecutor {
Map<String, String> execute(ServerWebExchange exchange, StainingRule stainingRule) {
if (stainingRule == null) {
return Collections.emptyMap();
}
List<StainingRule.Rule> rules = stainingRule.getRules();
if (CollectionUtils.isEmpty(rules)) {
return Collections.emptyMap();
}
Map<String, String> parsedLabels = new HashMap<>();
for (StainingRule.Rule rule : rules) {
List<Condition> conditions = rule.getConditions();
Set<String> keys = new HashSet<>();
conditions.forEach(condition -> keys.add(condition.getKey()));
Map<String, String> actualValues = SpringWebExpressionLabelUtils.resolve(exchange, keys);
if (!ConditionUtils.match(actualValues, conditions)) {
continue;
}
parsedLabels.putAll(KVPairUtils.toMap(rule.getLabels()));
}
return parsedLabels;
}
}

@ -0,0 +1,73 @@
/*
* 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.plugin.gateway.staining.rule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* The properties for rule staining.
* @author lepdou 2022-07-11
*/
@ConfigurationProperties("spring.cloud.tencent.plugin.scg.staining.rule-staining")
public class RuleStainingProperties {
@Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.namespace:${spring.cloud.tencent.namespace:default}}")
private String namespace;
@Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.group:${spring.application.name:spring-cloud-gateway}}")
private String group;
@Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.fileName:rule/staining.json}")
private String fileName;
private boolean enabled = true;
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

@ -0,0 +1,57 @@
/*
* 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.plugin.gateway.staining.rule;
import java.util.Collections;
import java.util.Map;
import com.tencent.cloud.plugin.gateway.staining.TrafficStainer;
import org.springframework.web.server.ServerWebExchange;
/**
* Staining the request by staining rules.
* @author lepdou 2022-07-06
*/
public class RuleTrafficStainer implements TrafficStainer {
private final StainingRuleManager stainingRuleManager;
private final RuleStainingExecutor ruleStainingExecutor;
public RuleTrafficStainer(StainingRuleManager stainingRuleManager, RuleStainingExecutor ruleStainingExecutor) {
this.stainingRuleManager = stainingRuleManager;
this.ruleStainingExecutor = ruleStainingExecutor;
}
@Override
public Map<String, String> apply(ServerWebExchange exchange) {
StainingRule stainingRule = stainingRuleManager.getStainingRule();
if (stainingRule == null) {
return Collections.emptyMap();
}
return ruleStainingExecutor.execute(exchange, stainingRule);
}
@Override
public int getOrder() {
return 0;
}
}

@ -0,0 +1,78 @@
/*
* 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.plugin.gateway.staining.rule;
import java.util.List;
import com.tencent.cloud.common.rule.Condition;
import com.tencent.cloud.common.rule.KVPair;
/**
* The rules for staining.
* @author lepdou 2022-07-07
*/
public class StainingRule {
private List<Rule> rules;
public List<Rule> getRules() {
return rules;
}
public void setRules(List<Rule> rules) {
this.rules = rules;
}
@Override
public String toString() {
return "StainingRule{" +
"rules=" + rules +
'}';
}
public static class Rule {
private List<Condition> conditions;
private List<KVPair> labels;
public List<Condition> getConditions() {
return conditions;
}
public void setConditions(List<Condition> conditions) {
this.conditions = conditions;
}
public List<KVPair> getLabels() {
return labels;
}
public void setLabels(List<KVPair> labels) {
this.labels = labels;
}
@Override
public String toString() {
return "Rule{" +
"conditions=" + conditions +
", labels=" + labels +
'}';
}
}
}

@ -0,0 +1,80 @@
/*
* 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.plugin.gateway.staining.rule;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.polaris.configuration.api.core.ConfigFile;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Fetch staining rule from polaris, and deserialize to {@link StainingRule}.
* @author lepdou 2022-07-07
*/
public class StainingRuleManager {
private static final Logger LOGGER = LoggerFactory.getLogger(StainingRuleManager.class);
private final RuleStainingProperties stainingProperties;
private final ConfigFileService configFileService;
private StainingRule stainingRule;
public StainingRuleManager(RuleStainingProperties stainingProperties, ConfigFileService configFileService) {
this.stainingProperties = stainingProperties;
this.configFileService = configFileService;
initStainingRule();
}
private void initStainingRule() {
ConfigFile rulesFile = configFileService.getConfigFile(stainingProperties.getNamespace(), stainingProperties.getGroup(),
stainingProperties.getFileName());
rulesFile.addChangeListener(event -> {
LOGGER.info("[SCT] update scg staining rules. {}", event);
deserialize(event.getNewValue());
});
String ruleJson = rulesFile.getContent();
LOGGER.info("[SCT] init scg staining rules. {}", ruleJson);
deserialize(ruleJson);
}
private void deserialize(String ruleJsonStr) {
if (StringUtils.isBlank(ruleJsonStr)) {
stainingRule = null;
return;
}
try {
stainingRule = JacksonUtils.deserialize(ruleJsonStr, StainingRule.class);
}
catch (Exception e) {
LOGGER.error("[SCT] deserialize staining rule error.", e);
throw e;
}
}
public StainingRule getStainingRule() {
return stainingRule;
}
}

@ -0,0 +1,40 @@
{
"properties": [
{
"name": "spring.cloud.tencent.plugin.scg.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for spring cloud gateway plugin."
},
{
"name": "spring.cloud.tencent.plugin.scg.staining.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for spring cloud gateway staining plugin."
},
{
"name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for spring cloud gateway rule staining plugin."
},
{
"name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.namespace",
"type": "java.lang.String",
"defaultValue": "default",
"description": "The namespace used to config staining rules."
},
{
"name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.group",
"type": "java.lang.String",
"defaultValue": "${spring.application.name}",
"description": "The group used to config staining rules."
},
{
"name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.fileName",
"type": "java.lang.String",
"defaultValue": "rule/staining.json",
"description": "The file name used to config staining rules."
}
]
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.plugin.gateway.SCGPluginsAutoConfiguration

@ -0,0 +1,89 @@
/*
* 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.plugin.gateway.staining;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Test for {@link TrafficStainingGatewayFilter}.
* @author lepdou 2022-07-12
*/
@RunWith(MockitoJUnitRunner.class)
public class TrafficStainerGatewayFilterTest {
@Mock
private GatewayFilterChain chain;
@Mock
private ServerWebExchange exchange;
@Test
public void testNoneTrafficStainingImplement() {
TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(null);
when(chain.filter(exchange)).thenReturn(Mono.empty());
filter.filter(exchange, chain);
verify(chain).filter(exchange);
}
@Test
public void testMultiStaining() {
TrafficStainer trafficStainer1 = Mockito.mock(TrafficStainer.class);
TrafficStainer trafficStainer2 = Mockito.mock(TrafficStainer.class);
when(trafficStainer1.getOrder()).thenReturn(1);
when(trafficStainer2.getOrder()).thenReturn(2);
Map<String, String> labels1 = new HashMap<>();
labels1.put("k1", "v1");
labels1.put("k2", "v2");
when(trafficStainer1.apply(exchange)).thenReturn(labels1);
Map<String, String> labels2 = new HashMap<>();
labels2.put("k1", "v11");
labels2.put("k3", "v3");
when(trafficStainer2.apply(exchange)).thenReturn(labels2);
TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(Arrays.asList(trafficStainer1, trafficStainer2));
Map<String, String> result = filter.getStainedLabels(exchange);
Assert.assertFalse(CollectionUtils.isEmpty(result));
Assert.assertEquals("v1", result.get("k1"));
Assert.assertEquals("v2", result.get("k2"));
Assert.assertEquals("v3", result.get("k3"));
}
}

@ -0,0 +1,181 @@
/*
* 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.plugin.gateway.staining.rule;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import com.tencent.cloud.common.rule.Condition;
import com.tencent.cloud.common.rule.KVPair;
import com.tencent.cloud.common.rule.Operation;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
/**
* Test for {@link RuleStainingExecutor}.
* @author lepdou 2022-07-12
*/
@RunWith(MockitoJUnitRunner.class)
public class RuleStainingExecutorTest {
@Test
public void testMatchCondition() {
Condition condition1 = new Condition();
condition1.setKey("${http.header.uid}");
condition1.setOperation(Operation.EQUAL.toString());
condition1.setValues(Collections.singletonList("1000"));
Condition condition2 = new Condition();
condition2.setKey("${http.query.source}");
condition2.setOperation(Operation.IN.toString());
condition2.setValues(Collections.singletonList("wx"));
StainingRule.Rule rule = new StainingRule.Rule();
rule.setConditions(Arrays.asList(condition1, condition2));
KVPair kvPair = new KVPair();
kvPair.setKey("env");
kvPair.setValue("blue");
rule.setLabels(Collections.singletonList(kvPair));
StainingRule stainingRule = new StainingRule();
stainingRule.setRules(Collections.singletonList(rule));
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
.queryParam("source", "wx")
.header("uid", "1000").build();
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
RuleStainingExecutor executor = new RuleStainingExecutor();
Map<String, String> stainedLabels = executor.execute(exchange, stainingRule);
Assert.assertNotNull(stainedLabels);
Assert.assertEquals(1, stainedLabels.size());
Assert.assertEquals("blue", stainedLabels.get("env"));
}
@Test
public void testNotMatchCondition() {
Condition condition1 = new Condition();
condition1.setKey("${http.header.uid}");
condition1.setOperation(Operation.EQUAL.toString());
condition1.setValues(Collections.singletonList("1000"));
Condition condition2 = new Condition();
condition2.setKey("${http.query.source}");
condition2.setOperation(Operation.IN.toString());
condition2.setValues(Collections.singletonList("wx"));
StainingRule.Rule rule = new StainingRule.Rule();
rule.setConditions(Arrays.asList(condition1, condition2));
KVPair kvPair = new KVPair();
kvPair.setKey("env");
kvPair.setValue("blue");
rule.setLabels(Collections.singletonList(kvPair));
StainingRule stainingRule = new StainingRule();
stainingRule.setRules(Collections.singletonList(rule));
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
.queryParam("source", "wx")
.header("uid", "10001").build();
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
RuleStainingExecutor executor = new RuleStainingExecutor();
Map<String, String> stainedLabels = executor.execute(exchange, stainingRule);
Assert.assertNotNull(stainedLabels);
Assert.assertEquals(0, stainedLabels.size());
}
@Test
public void testMatchTwoRulesAndNotMatchOneRule() {
Condition condition1 = new Condition();
condition1.setKey("${http.header.uid}");
condition1.setOperation(Operation.EQUAL.toString());
condition1.setValues(Collections.singletonList("1000"));
Condition condition2 = new Condition();
condition2.setKey("${http.query.source}");
condition2.setOperation(Operation.IN.toString());
condition2.setValues(Collections.singletonList("wx"));
// rule1 matched
StainingRule.Rule rule1 = new StainingRule.Rule();
rule1.setConditions(Arrays.asList(condition1, condition2));
KVPair kvPair = new KVPair();
kvPair.setKey("env");
kvPair.setValue("blue");
rule1.setLabels(Collections.singletonList(kvPair));
// rule2 matched
StainingRule.Rule rule2 = new StainingRule.Rule();
rule2.setConditions(Collections.singletonList(condition1));
KVPair kvPair2 = new KVPair();
kvPair2.setKey("label1");
kvPair2.setValue("value1");
KVPair kvPair3 = new KVPair();
kvPair3.setKey("label2");
kvPair3.setValue("value2");
rule2.setLabels(Arrays.asList(kvPair2, kvPair3));
// rule3 not matched
Condition condition3 = new Condition();
condition3.setKey("${http.query.type}");
condition3.setOperation(Operation.IN.toString());
condition3.setValues(Collections.singletonList("wx"));
StainingRule.Rule rule3 = new StainingRule.Rule();
rule3.setConditions(Collections.singletonList(condition3));
KVPair kvPair4 = new KVPair();
kvPair4.setKey("label3");
kvPair4.setValue("value3");
rule3.setLabels(Collections.singletonList(kvPair4));
StainingRule stainingRule = new StainingRule();
stainingRule.setRules(Arrays.asList(rule1, rule2, rule3));
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
.queryParam("source", "wx")
.header("uid", "1000").build();
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
RuleStainingExecutor executor = new RuleStainingExecutor();
Map<String, String> stainedLabels = executor.execute(exchange, stainingRule);
Assert.assertNotNull(stainedLabels);
Assert.assertEquals(3, stainedLabels.size());
Assert.assertEquals("blue", stainedLabels.get("env"));
Assert.assertEquals("value1", stainedLabels.get("label1"));
Assert.assertEquals("value2", stainedLabels.get("label2"));
}
}

@ -0,0 +1,133 @@
/*
* 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.plugin.gateway.staining.rule;
import com.tencent.polaris.configuration.api.core.ConfigFile;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
/**
* Test for {@link StainingRuleManager}.
* @author lepdou 2022-07-12
*/
@RunWith(MockitoJUnitRunner.class)
public class StainingRuleManagerTest {
@Mock
private ConfigFileService configFileService;
private final String testNamespace = "testNamespace";
private final String testGroup = "testGroup";
private final String testFileName = "rule.json";
@Test
public void testNormalRule() {
RuleStainingProperties ruleStainingProperties = new RuleStainingProperties();
ruleStainingProperties.setNamespace(testNamespace);
ruleStainingProperties.setGroup(testGroup);
ruleStainingProperties.setFileName(testFileName);
ConfigFile configFile = Mockito.mock(ConfigFile.class);
when(configFile.getContent()).thenReturn("{\n"
+ " \"rules\":[\n"
+ " {\n"
+ " \"conditions\":[\n"
+ " {\n"
+ " \"key\":\"${http.query.uid}\",\n"
+ " \"values\":[\"1000\"],\n"
+ " \"operation\":\"EQUAL\"\n"
+ " }\n"
+ " ],\n"
+ " \"labels\":[\n"
+ " {\n"
+ " \"key\":\"env\",\n"
+ " \"value\":\"blue\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}");
when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile);
StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService);
StainingRule stainingRule = stainingRuleManager.getStainingRule();
Assert.assertNotNull(stainingRule);
Assert.assertEquals(1, stainingRule.getRules().size());
StainingRule.Rule rule = stainingRule.getRules().get(0);
Assert.assertEquals(1, rule.getConditions().size());
Assert.assertEquals(1, rule.getLabels().size());
}
@Test(expected = RuntimeException.class)
public void testWrongRule() {
RuleStainingProperties ruleStainingProperties = new RuleStainingProperties();
ruleStainingProperties.setNamespace(testNamespace);
ruleStainingProperties.setGroup(testGroup);
ruleStainingProperties.setFileName(testFileName);
ConfigFile configFile = Mockito.mock(ConfigFile.class);
when(configFile.getContent()).thenReturn("{\n"
+ " \"rules\":[\n"
+ " {\n"
+ " \"conditionsxxxx\":[\n"
+ " {\n"
+ " \"key\":\"${http.query.uid}\",\n"
+ " \"values\":[\"1000\"],\n"
+ " \"operation\":\"EQUAL\"\n"
+ " }\n"
+ " ],\n"
+ " \"labels\":[\n"
+ " {\n"
+ " \"key\":\"env\",\n"
+ " \"value\":\"blue\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}");
when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile);
new StainingRuleManager(ruleStainingProperties, configFileService);
}
@Test
public void testEmptyRule() {
RuleStainingProperties ruleStainingProperties = new RuleStainingProperties();
ruleStainingProperties.setNamespace(testNamespace);
ruleStainingProperties.setGroup(testGroup);
ruleStainingProperties.setFileName(testFileName);
ConfigFile configFile = Mockito.mock(ConfigFile.class);
when(configFile.getContent()).thenReturn(null);
when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile);
StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService);
Assert.assertNull(stainingRuleManager.getStainingRule());
}
}
Loading…
Cancel
Save