add feature-env plugin & add spring cloud gateway staining plugin. (#533)

pull/542/head
Haotian Zhang 2 years ago committed by GitHub
parent 371d18f8a4
commit 8484fc5f61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -23,3 +23,4 @@
- [Code optimization for rpc-enhancement module](https://github.com/Tencent/spring-cloud-tencent/pull/526) - [Code optimization for rpc-enhancement module](https://github.com/Tencent/spring-cloud-tencent/pull/526)
- [Feature: Optimized configuration update](https://github.com/Tencent/spring-cloud-tencent/pull/528) - [Feature: Optimized configuration update](https://github.com/Tencent/spring-cloud-tencent/pull/528)
- [Featuresupport pushGateway push metrics](https://github.com/Tencent/spring-cloud-tencent/pull/529) - [Featuresupport pushGateway push metrics](https://github.com/Tencent/spring-cloud-tencent/pull/529)
- [add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/533)

@ -25,6 +25,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import com.tencent.cloud.common.constant.MetadataConstant;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -37,8 +38,7 @@ import org.springframework.web.server.ServerWebExchange;
* @author lepdou 2022-05-20 * @author lepdou 2022-05-20
*/ */
public final class CustomTransitiveMetadataResolver { 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() { private CustomTransitiveMetadataResolver() {
} }
@ -48,10 +48,20 @@ public final class CustomTransitiveMetadataResolver {
HttpHeaders headers = exchange.getRequest().getHeaders(); HttpHeaders headers = exchange.getRequest().getHeaders();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) { for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
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));
}
if (StringUtils.isNotBlank(key) && StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX) //resolve polaris transitive header
if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX)
&& !CollectionUtils.isEmpty(entry.getValue())) { && !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)); result.put(sourceKey, entry.getValue().get(0));
} }
} }
@ -65,10 +75,21 @@ public final class CustomTransitiveMetadataResolver {
Enumeration<String> headers = request.getHeaderNames(); Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) { while (headers.hasMoreElements()) {
String key = headers.nextElement(); 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))) { && 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)); result.put(sourceKey, request.getHeader(key));
} }
} }

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

@ -24,7 +24,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto; import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RateLimitProto; import com.tencent.polaris.client.pb.RateLimitProto;

@ -28,7 +28,7 @@ import javax.annotation.PostConstruct;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.tencent.cloud.common.metadata.MetadataContext; 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.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -161,6 +161,6 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
private Map<String, String> getRuleExpressionLabels(ServerWebExchange exchange, String namespace, String service) { private Map<String, String> getRuleExpressionLabels(ServerWebExchange exchange, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, 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 javax.servlet.http.HttpServletResponse;
import com.tencent.cloud.common.metadata.MetadataContext; 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.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -158,6 +158,6 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
private Map<String, String> getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) { private Map<String, String> getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, 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.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils; 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.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -74,15 +74,15 @@ import static org.mockito.Mockito.when;
public class QuotaCheckReactiveFilterTest { public class QuotaCheckReactiveFilterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic; private static MockedStatic<SpringWebExpressionLabelUtils> expressionLabelUtilsMockedStatic;
private final PolarisRateLimiterLabelReactiveResolver labelResolver = private final PolarisRateLimiterLabelReactiveResolver labelResolver =
exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver"); exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver");
private QuotaCheckReactiveFilter quotaCheckReactiveFilter; private QuotaCheckReactiveFilter quotaCheckReactiveFilter;
@BeforeClass @BeforeClass
public static void beforeClass() { public static void beforeClass() {
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class); expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet()))
.thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver")); .thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); 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.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils; 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.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
@ -74,7 +74,7 @@ import static org.mockito.Mockito.when;
public class QuotaCheckServletFilterTest { public class QuotaCheckServletFilterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic; private static MockedStatic<SpringWebExpressionLabelUtils> expressionLabelUtilsMockedStatic;
private final PolarisRateLimiterLabelServletResolver labelResolver = private final PolarisRateLimiterLabelServletResolver labelResolver =
exchange -> Collections.singletonMap("ServletResolver", "ServletResolver"); exchange -> Collections.singletonMap("ServletResolver", "ServletResolver");
private QuotaCheckServletFilter quotaCheckServletFilter; private QuotaCheckServletFilter quotaCheckServletFilter;
@ -82,8 +82,8 @@ public class QuotaCheckServletFilterTest {
@BeforeClass @BeforeClass
public static void beforeClass() { public static void beforeClass() {
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class); expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet()))
.thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver")); .thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
@ -92,7 +92,7 @@ public class QuotaCheckServletFilterTest {
} }
@AfterClass @AfterClass
public static void afterClass() throws Exception { public static void afterClass() {
mockedApplicationContextAwareUtils.close(); mockedApplicationContextAwareUtils.close();
expressionLabelUtilsMockedStatic.close(); expressionLabelUtilsMockedStatic.close();
} }
@ -129,8 +129,7 @@ public class QuotaCheckServletFilterTest {
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class); RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.emptySet()); when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.emptySet());
this.quotaCheckServletFilter = new QuotaCheckServletFilter( this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
this.quotaCheckWithHtmlRejectTipsServletFilter = new QuotaCheckServletFilter( this.quotaCheckWithHtmlRejectTipsServletFilter = new QuotaCheckServletFilter(
limitAPI, labelResolver, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleLabelResolver); limitAPI, labelResolver, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleLabelResolver);
} }

@ -18,11 +18,17 @@
package com.tencent.cloud.polaris.router; package com.tencent.cloud.polaris.router;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
/** /**
* the context for router. * the context for router.
@ -32,13 +38,13 @@ import org.springframework.util.CollectionUtils;
public class PolarisRouterContext { 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. * transitive labels.
*/ */
public static final String TRANSITIVE_LABELS = "transitive"; public static final String TRANSITIVE_LABELS = "transitiveMetadata";
private Map<String, Map<String, String>> labels; private Map<String, Map<String, String>> labels;
@ -53,10 +59,62 @@ public class PolarisRouterContext {
return Collections.unmodifiableMap(subLabels); 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) { if (this.labels == null) {
this.labels = new HashMap<>(); this.labels = new HashMap<>();
} }
labels.put(labelType, subLabels);
Map<String, String> subLabelMap = new LinkedCaseInsensitiveMap<>();
if (!CollectionUtils.isEmpty(subLabels)) {
subLabelMap.putAll(subLabels);
}
labels.put(labelType, subLabelMap);
} }
} }

@ -31,23 +31,17 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.pojo.PolarisServiceInstance; import com.tencent.cloud.common.pojo.PolarisServiceInstance;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils; import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils;
import com.tencent.cloud.polaris.router.config.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerRequest; import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerRequest;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor;
import com.tencent.polaris.api.exception.ErrorCode; import com.tencent.polaris.api.exception.ErrorCode;
import com.tencent.polaris.api.exception.PolarisException; import com.tencent.polaris.api.exception.PolarisException;
import com.tencent.polaris.api.pojo.Instance; import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInfo; import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.pojo.ServiceInstances; 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.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
@ -59,6 +53,8 @@ import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
/** /**
* Service routing entrance. * Service routing entrance.
* *
@ -73,23 +69,17 @@ import org.springframework.util.CollectionUtils;
*/ */
public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier { public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisRouterServiceInstanceListSupplier.class);
private final PolarisNearByRouterProperties polarisNearByRouterProperties;
private final PolarisMetadataRouterProperties polarisMetadataRouterProperties;
private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
private final RouterAPI routerAPI; private final RouterAPI routerAPI;
private final List<RouterRequestInterceptor> requestInterceptors;
private final List<RouterResponseInterceptor> responseInterceptors;
public PolarisRouterServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, public PolarisRouterServiceInstanceListSupplier(ServiceInstanceListSupplier delegate,
RouterAPI routerAPI, RouterAPI routerAPI, List<RouterRequestInterceptor> requestInterceptors,
PolarisNearByRouterProperties polarisNearByRouterProperties, List<RouterResponseInterceptor> responseInterceptors) {
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
super(delegate); super(delegate);
this.routerAPI = routerAPI; this.routerAPI = routerAPI;
this.polarisNearByRouterProperties = polarisNearByRouterProperties; this.requestInterceptors = requestInterceptors;
this.polarisMetadataRouterProperties = polarisMetadataRouterProperties; this.responseInterceptors = responseInterceptors;
this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties;
} }
@Override @Override
@ -125,33 +115,37 @@ public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceI
PolarisRouterContext routerContext = new PolarisRouterContext(); PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get() routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)); .getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE));
labelHeaderValues.forEach(labelHeaderValue -> { Map<String, String> labelHeaderValuesMap = new HashMap<>();
try { try {
Map<String, String> labels = JacksonUtils.deserialize2Map(URLDecoder.decode(labelHeaderValue, "UTF-8")); String labelHeaderValuesContent = labelHeaderValues.stream().findFirst().get();
if (!CollectionUtils.isEmpty(labels)) { labelHeaderValuesMap.putAll(JacksonUtils.deserialize2Map(URLDecoder.decode(labelHeaderValuesContent, UTF_8)));
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels);
}
} }
catch (UnsupportedEncodingException e) { catch (UnsupportedEncodingException e) {
LOGGER.error("Decode header[{}] failed.", labelHeaderValue, e); throw new RuntimeException("unsupported charset exception " + UTF_8);
throw new RuntimeException(e);
} }
}); routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labelHeaderValuesMap);
return routerContext; return routerContext;
} }
Flux<List<ServiceInstance>> doRouter(Flux<List<ServiceInstance>> allServers, PolarisRouterContext key) { Flux<List<ServiceInstance>> doRouter(Flux<List<ServiceInstance>> allServers, PolarisRouterContext routerContext) {
ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers); ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers);
// filter instance by routers // filter instance by routers
ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, key); ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, routerContext);
// process request interceptors
processRouterRequestInterceptors(processRoutersRequest, routerContext);
// process router chain
ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest); ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest);
// process response interceptors
processRouterResponseInterceptors(routerContext, processRoutersResponse);
// transfer polaris server to ServiceInstance
List<ServiceInstance> filteredInstances = new ArrayList<>(); List<ServiceInstance> filteredInstances = new ArrayList<>();
ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances(); ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances();
for (Instance instance : filteredServiceInstances.getInstances()) { for (Instance instance : filteredServiceInstances.getInstances()) {
@ -163,42 +157,25 @@ public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceI
ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, PolarisRouterContext key) { ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, PolarisRouterContext key) {
ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest(); ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest();
processRoutersRequest.setDstInstances(serviceInstances); 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 serviceInfo = new ServiceInfo();
serviceInfo.setNamespace(MetadataContext.LOCAL_NAMESPACE); serviceInfo.setNamespace(MetadataContext.LOCAL_NAMESPACE);
serviceInfo.setService(MetadataContext.LOCAL_SERVICE); serviceInfo.setService(MetadataContext.LOCAL_SERVICE);
processRoutersRequest.setSourceService(serviceInfo);
if (ruleBasedRouterEnabled) { return processRoutersRequest;
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); void processRouterRequestInterceptors(ProcessRoutersRequest processRoutersRequest, PolarisRouterContext routerContext) {
for (RouterRequestInterceptor requestInterceptor : requestInterceptors) {
requestInterceptor.apply(processRoutersRequest, routerContext);
}
}
return processRoutersRequest; private void processRouterResponseInterceptors(PolarisRouterContext routerContext, ProcessRoutersResponse processRoutersResponse) {
if (!CollectionUtils.isEmpty(responseInterceptors)) {
for (RouterResponseInterceptor responseInterceptor : responseInterceptors) {
responseInterceptor.apply(processRoutersResponse, routerContext);
}
}
} }
private Map<String, String> getRouterLabels(PolarisRouterContext key, String type) { private Map<String, String> getRouterLabels(PolarisRouterContext key, String type) {

@ -24,7 +24,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto; import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RoutingProto; import com.tencent.polaris.client.pb.RoutingProto;

@ -13,16 +13,16 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.router.resttemplate; package com.tencent.cloud.polaris.router.beanprocessor;
import java.util.List; import java.util.List;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.BeanFactoryUtils; import com.tencent.cloud.common.util.BeanFactoryUtils;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerInterceptor;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@ -39,7 +39,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
* *
*@author lepdou 2022-05-18 *@author lepdou 2022-05-18
*/ */
public class PolarisLoadBalancerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { public class LoadBalancerInterceptorBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private BeanFactory factory; private BeanFactory factory;
@ -54,11 +54,11 @@ public class PolarisLoadBalancerBeanPostProcessor implements BeanPostProcessor,
LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class); LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class);
LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class); LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class);
List<SpringWebRouterLabelResolver> routerLabelResolvers = BeanFactoryUtils.getBeans(factory, SpringWebRouterLabelResolver.class); List<SpringWebRouterLabelResolver> routerLabelResolvers = BeanFactoryUtils.getBeans(factory, SpringWebRouterLabelResolver.class);
MetadataLocalProperties metadataLocalProperties = this.factory.getBean(MetadataLocalProperties.class); StaticMetadataManager staticMetadataManager = this.factory.getBean(StaticMetadataManager.class);
RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class); RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class);
return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory, return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory,
routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver); routerLabelResolvers, staticMetadataManager, routerRuleLabelResolver);
} }
return bean; return bean;
} }

@ -13,16 +13,16 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.router.scg; package com.tencent.cloud.polaris.router.beanprocessor;
import java.util.List; import java.util.List;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.BeanFactoryUtils; import com.tencent.cloud.common.util.BeanFactoryUtils;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.scg.PolarisReactiveLoadBalancerClientFilter;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@ -38,7 +38,7 @@ import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
* Replaced ReactiveLoadBalancerClientFilter with PolarisReactiveLoadBalancerClientFilter during creating bean phase. * Replaced ReactiveLoadBalancerClientFilter with PolarisReactiveLoadBalancerClientFilter during creating bean phase.
*@author lepdou 2022-06-20 *@author lepdou 2022-06-20
*/ */
public class PolarisLoadBalancerClientBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { public class ReactiveLoadBalancerClientFilterBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private BeanFactory factory; private BeanFactory factory;
@ -50,18 +50,19 @@ public class PolarisLoadBalancerClientBeanPostProcessor implements BeanPostProce
@Override @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// Support spring cloud gateway router. // Support spring cloud gateway router.
// Replaces the default LoadBalancerClientFilter implementation and returns a custom PolarisLoadBalancerClientFilter // Replaces the default ReactiveLoadBalancerClientFilter implementation
// and returns a custom PolarisReactiveLoadBalancerClientFilter
if (bean instanceof ReactiveLoadBalancerClientFilter) { if (bean instanceof ReactiveLoadBalancerClientFilter) {
LoadBalancerClientFactory loadBalancerClientFactory = this.factory.getBean(LoadBalancerClientFactory.class); LoadBalancerClientFactory loadBalancerClientFactory = this.factory.getBean(LoadBalancerClientFactory.class);
GatewayLoadBalancerProperties gatewayLoadBalancerProperties = this.factory.getBean(GatewayLoadBalancerProperties.class); GatewayLoadBalancerProperties gatewayLoadBalancerProperties = this.factory.getBean(GatewayLoadBalancerProperties.class);
LoadBalancerProperties loadBalancerProperties = this.factory.getBean(LoadBalancerProperties.class); LoadBalancerProperties loadBalancerProperties = this.factory.getBean(LoadBalancerProperties.class);
List<SpringWebRouterLabelResolver> routerLabelResolvers = BeanFactoryUtils.getBeans(factory, SpringWebRouterLabelResolver.class); List<SpringWebRouterLabelResolver> routerLabelResolvers = BeanFactoryUtils.getBeans(factory, SpringWebRouterLabelResolver.class);
MetadataLocalProperties metadataLocalProperties = this.factory.getBean(MetadataLocalProperties.class); StaticMetadataManager staticMetadataManager = this.factory.getBean(StaticMetadataManager.class);
RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class); RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class);
return new PolarisReactiveLoadBalancerClientFilter( return new PolarisReactiveLoadBalancerClientFilter(
loadBalancerClientFactory, gatewayLoadBalancerProperties, loadBalancerProperties, loadBalancerClientFactory, gatewayLoadBalancerProperties, loadBalancerProperties,
metadataLocalProperties, routerRuleLabelResolver, routerLabelResolvers); staticMetadataManager, routerRuleLabelResolver, routerLabelResolvers);
} }
return bean; return bean;
} }

@ -20,7 +20,7 @@ package com.tencent.cloud.polaris.router.config;
import java.util.List; import java.util.List;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.feign.RouterLabelFeignInterceptor; import com.tencent.cloud.polaris.router.feign.RouterLabelFeignInterceptor;
import com.tencent.cloud.polaris.router.spi.FeignRouterLabelResolver; import com.tencent.cloud.polaris.router.spi.FeignRouterLabelResolver;
@ -40,8 +40,8 @@ public class FeignAutoConfiguration {
@Bean @Bean
public RouterLabelFeignInterceptor routerLabelInterceptor(@Nullable List<FeignRouterLabelResolver> routerLabelResolvers, public RouterLabelFeignInterceptor routerLabelInterceptor(@Nullable List<FeignRouterLabelResolver> routerLabelResolvers,
MetadataLocalProperties metadataLocalProperties, StaticMetadataManager staticMetadataManager,
RouterRuleLabelResolver routerRuleLabelResolver) { RouterRuleLabelResolver routerRuleLabelResolver) {
return new RouterLabelFeignInterceptor(routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver); return new RouterLabelFeignInterceptor(routerLabelResolvers, staticMetadataManager, routerRuleLabelResolver);
} }
} }

@ -18,7 +18,11 @@
package com.tencent.cloud.polaris.router.config; package com.tencent.cloud.polaris.router.config;
import java.util.List;
import com.tencent.cloud.polaris.router.PolarisRouterServiceInstanceListSupplier; import com.tencent.cloud.polaris.router.PolarisRouterServiceInstanceListSupplier;
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 com.tencent.polaris.router.api.core.RouterAPI;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -55,16 +59,13 @@ public class LoadBalancerConfiguration {
@ConditionalOnBean(ReactiveDiscoveryClient.class) @ConditionalOnBean(ReactiveDiscoveryClient.class)
public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier( public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context, ConfigurableApplicationContext context,
RouterAPI routerAPI, RouterAPI routerAPI, List<RouterRequestInterceptor> requestInterceptors,
PolarisNearByRouterProperties polarisNearByRouterProperties, List<RouterResponseInterceptor> responseInterceptors) {
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
return new PolarisRouterServiceInstanceListSupplier( return new PolarisRouterServiceInstanceListSupplier(
ServiceInstanceListSupplier.builder().withDiscoveryClient().build(context), ServiceInstanceListSupplier.builder().withDiscoveryClient().build(context),
routerAPI, routerAPI,
polarisNearByRouterProperties, requestInterceptors,
polarisMetadataRouterProperties, responseInterceptors);
polarisRuleBasedRouterProperties);
} }
} }
@ -78,16 +79,13 @@ public class LoadBalancerConfiguration {
@ConditionalOnBean(DiscoveryClient.class) @ConditionalOnBean(DiscoveryClient.class)
public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier( public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context, ConfigurableApplicationContext context,
RouterAPI routerAPI, RouterAPI routerAPI, List<RouterRequestInterceptor> requestInterceptors,
PolarisNearByRouterProperties polarisNearByRouterProperties, List<RouterResponseInterceptor> responseInterceptors) {
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
return new PolarisRouterServiceInstanceListSupplier( return new PolarisRouterServiceInstanceListSupplier(
ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().build(context), ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().build(context),
routerAPI, routerAPI,
polarisNearByRouterProperties, requestInterceptors,
polarisMetadataRouterProperties, responseInterceptors);
polarisRuleBasedRouterProperties);
} }
} }
} }

@ -20,10 +20,17 @@ package com.tencent.cloud.polaris.router.config;
import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerBeanPostProcessor; import com.tencent.cloud.polaris.router.beanprocessor.LoadBalancerInterceptorBeanPostProcessor;
import com.tencent.cloud.polaris.router.scg.PolarisLoadBalancerClientBeanPostProcessor; import com.tencent.cloud.polaris.router.beanprocessor.ReactiveLoadBalancerClientFilterBeanPostProcessor;
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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -33,7 +40,7 @@ import org.springframework.core.annotation.Order;
import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
/** /**
* router module auto configuration. * configuration for router module singleton beans.
* *
*@author lepdou 2022-05-11 *@author lepdou 2022-05-11
*/ */
@ -45,19 +52,37 @@ public class RouterAutoConfiguration {
@Bean @Bean
@Order(HIGHEST_PRECEDENCE) @Order(HIGHEST_PRECEDENCE)
@ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor") @ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor")
public PolarisLoadBalancerBeanPostProcessor polarisLoadBalancerBeanPostProcessor() { public LoadBalancerInterceptorBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() {
return new PolarisLoadBalancerBeanPostProcessor(); return new LoadBalancerInterceptorBeanPostProcessor();
} }
@Bean @Bean
@Order(HIGHEST_PRECEDENCE) @Order(HIGHEST_PRECEDENCE)
@ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter") @ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter")
public PolarisLoadBalancerClientBeanPostProcessor polarisLoadBalancerClientBeanPostProcessor() { public ReactiveLoadBalancerClientFilterBeanPostProcessor loadBalancerClientFilterBeanPostProcessor() {
return new PolarisLoadBalancerClientBeanPostProcessor(); return new ReactiveLoadBalancerClientFilterBeanPostProcessor();
} }
@Bean @Bean
public RouterRuleLabelResolver routerRuleLabelResolver(ServiceRuleManager serviceRuleManager) { public RouterRuleLabelResolver routerRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
return new RouterRuleLabelResolver(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);
}
} }

@ -13,10 +13,9 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.router.config; package com.tencent.cloud.polaris.router.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;

@ -13,10 +13,9 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.router.config; package com.tencent.cloud.polaris.router.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;

@ -13,10 +13,9 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.router.config; package com.tencent.cloud.polaris.router.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;

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

@ -29,7 +29,7 @@ import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
@ -42,6 +42,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
/** /**
* Resolver labels from request. * Resolver labels from request.
* *
@ -51,11 +53,11 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered
private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelFeignInterceptor.class); private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelFeignInterceptor.class);
private final List<FeignRouterLabelResolver> routerLabelResolvers; private final List<FeignRouterLabelResolver> routerLabelResolvers;
private final MetadataLocalProperties metadataLocalProperties; private final StaticMetadataManager staticMetadataManager;
private final RouterRuleLabelResolver routerRuleLabelResolver; private final RouterRuleLabelResolver routerRuleLabelResolver;
public RouterLabelFeignInterceptor(List<FeignRouterLabelResolver> routerLabelResolvers, public RouterLabelFeignInterceptor(List<FeignRouterLabelResolver> routerLabelResolvers,
MetadataLocalProperties metadataLocalProperties, StaticMetadataManager staticMetadataManager,
RouterRuleLabelResolver routerRuleLabelResolver) { RouterRuleLabelResolver routerRuleLabelResolver) {
if (!CollectionUtils.isEmpty(routerLabelResolvers)) { if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder)); routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder));
@ -64,7 +66,7 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered
else { else {
this.routerLabelResolvers = null; this.routerLabelResolvers = null;
} }
this.metadataLocalProperties = metadataLocalProperties; this.staticMetadataManager = staticMetadataManager;
this.routerRuleLabelResolver = routerRuleLabelResolver; this.routerRuleLabelResolver = routerRuleLabelResolver;
} }
@ -76,7 +78,7 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered
@Override @Override
public void apply(RequestTemplate requestTemplate) { public void apply(RequestTemplate requestTemplate) {
// local service labels // local service labels
Map<String, String> labels = new HashMap<>(metadataLocalProperties.getContent()); Map<String, String> labels = new HashMap<>(staticMetadataManager.getMergedStaticMetadata());
// labels from rule expression // labels from rule expression
String peerServiceName = requestTemplate.feignTarget().name(); String peerServiceName = requestTemplate.feignTarget().name();
@ -107,18 +109,14 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered
labels.putAll(transitiveLabels); labels.putAll(transitiveLabels);
// pass label by header // pass label by header
if (labels.size() == 0) { String encodedLabelsContent;
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER);
return;
}
try { try {
String headerMetadataStr = URLEncoder.encode(JacksonUtils.serialize2Json(labels), "UTF-8"); encodedLabelsContent = URLEncoder.encode(JacksonUtils.serialize2Json(labels), UTF_8);
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, headerMetadataStr);
} }
catch (UnsupportedEncodingException e) { catch (UnsupportedEncodingException e) {
LOGGER.error("Set header failed.", e); throw new RuntimeException("unsupported charset exception " + UTF_8);
throw new RuntimeException(e);
} }
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, encodedLabelsContent);
} }
private Map<String, String> getRuleExpressionLabels(RequestTemplate requestTemplate, Set<String> labelKeys) { private Map<String, String> getRuleExpressionLabels(RequestTemplate requestTemplate, Set<String> labelKeys) {
@ -128,5 +126,4 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered
return FeignExpressionLabelUtils.resolve(requestTemplate, labelKeys); return FeignExpressionLabelUtils.resolve(requestTemplate, labelKeys);
} }
} }

@ -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);
}
}
}

@ -31,9 +31,9 @@ import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
@ -62,18 +62,18 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
private final LoadBalancerClient loadBalancer; private final LoadBalancerClient loadBalancer;
private final LoadBalancerRequestFactory requestFactory; private final LoadBalancerRequestFactory requestFactory;
private final List<SpringWebRouterLabelResolver> routerLabelResolvers; private final List<SpringWebRouterLabelResolver> routerLabelResolvers;
private final MetadataLocalProperties metadataLocalProperties; private final StaticMetadataManager staticMetadataManager;
private final RouterRuleLabelResolver routerRuleLabelResolver; private final RouterRuleLabelResolver routerRuleLabelResolver;
public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer, public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory, LoadBalancerRequestFactory requestFactory,
List<SpringWebRouterLabelResolver> routerLabelResolvers, List<SpringWebRouterLabelResolver> routerLabelResolvers,
MetadataLocalProperties metadataLocalProperties, StaticMetadataManager staticMetadataManager,
RouterRuleLabelResolver routerRuleLabelResolver) { RouterRuleLabelResolver routerRuleLabelResolver) {
super(loadBalancer, requestFactory); super(loadBalancer, requestFactory);
this.loadBalancer = loadBalancer; this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory; this.requestFactory = requestFactory;
this.metadataLocalProperties = metadataLocalProperties; this.staticMetadataManager = staticMetadataManager;
this.routerRuleLabelResolver = routerRuleLabelResolver; this.routerRuleLabelResolver = routerRuleLabelResolver;
if (!CollectionUtils.isEmpty(routerLabelResolvers)) { if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
@ -100,7 +100,7 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
void setLabelsToHeaders(HttpRequest request, byte[] body, String peerServiceName) { void setLabelsToHeaders(HttpRequest request, byte[] body, String peerServiceName) {
// local service labels // local service labels
Map<String, String> labels = new HashMap<>(metadataLocalProperties.getContent()); Map<String, String> labels = new HashMap<>(staticMetadataManager.getMergedStaticMetadata());
// labels from rule expression // labels from rule expression
Set<String> expressionLabelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE, Set<String> expressionLabelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
@ -151,6 +151,6 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
return Collections.emptyMap(); return Collections.emptyMap();
} }
return ExpressionLabelUtils.resolve(request, labelKeys); return SpringWebExpressionLabelUtils.resolve(request, labelKeys);
} }
} }

@ -29,9 +29,10 @@ import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterServiceInstanceListSupplier;
import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
@ -71,24 +72,26 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
/** /**
* ReactiveLoadBalancerClientFilter does not have the ability to pass route labels, so it is replaced with PolarisReactiveLoadBalancerClientFilter. * ReactiveLoadBalancerClientFilter does not have the ability to pass route labels, so it is replaced
* The passed route labels are used in {@link com.tencent.cloud.polaris.router.PolarisRouterServiceInstanceListSupplier}. * with PolarisReactiveLoadBalancerClientFilter. The passed route labels are used in
* {@link PolarisRouterServiceInstanceListSupplier}.
*
* @author lepdou 2022-06-20 * @author lepdou 2022-06-20
*/ */
public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter { public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisReactiveLoadBalancerClientFilter.class); private static final Logger log = LoggerFactory.getLogger(PolarisReactiveLoadBalancerClientFilter.class);
private final LoadBalancerClientFactory clientFactory; private final LoadBalancerClientFactory clientFactory;
private final GatewayLoadBalancerProperties gatewayLoadBalancerProperties; private final GatewayLoadBalancerProperties gatewayLoadBalancerProperties;
private final LoadBalancerProperties loadBalancerProperties; private final LoadBalancerProperties loadBalancerProperties;
private final MetadataLocalProperties metadataLocalProperties; private final StaticMetadataManager staticMetadataManager;
private final RouterRuleLabelResolver routerRuleLabelResolver; private final RouterRuleLabelResolver routerRuleLabelResolver;
private final List<SpringWebRouterLabelResolver> routerLabelResolvers; private final List<SpringWebRouterLabelResolver> routerLabelResolvers;
public PolarisReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, public PolarisReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
GatewayLoadBalancerProperties gatewayLoadBalancerProperties, GatewayLoadBalancerProperties gatewayLoadBalancerProperties,
LoadBalancerProperties loadBalancerProperties, LoadBalancerProperties loadBalancerProperties,
MetadataLocalProperties metadataLocalProperties, StaticMetadataManager staticMetadataManager,
RouterRuleLabelResolver routerRuleLabelResolver, RouterRuleLabelResolver routerRuleLabelResolver,
List<SpringWebRouterLabelResolver> routerLabelResolvers) { List<SpringWebRouterLabelResolver> routerLabelResolvers) {
super(clientFactory, gatewayLoadBalancerProperties, loadBalancerProperties); super(clientFactory, gatewayLoadBalancerProperties, loadBalancerProperties);
@ -96,7 +99,7 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance
this.clientFactory = clientFactory; this.clientFactory = clientFactory;
this.gatewayLoadBalancerProperties = gatewayLoadBalancerProperties; this.gatewayLoadBalancerProperties = gatewayLoadBalancerProperties;
this.loadBalancerProperties = loadBalancerProperties; this.loadBalancerProperties = loadBalancerProperties;
this.metadataLocalProperties = metadataLocalProperties; this.staticMetadataManager = staticMetadataManager;
this.routerRuleLabelResolver = routerRuleLabelResolver; this.routerRuleLabelResolver = routerRuleLabelResolver;
this.routerLabelResolvers = routerLabelResolvers; this.routerLabelResolvers = routerLabelResolvers;
} }
@ -114,8 +117,8 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance
// preserve the original url // preserve the original url
addOriginalRequestUrl(exchange, url); addOriginalRequestUrl(exchange, url);
if (LOGGER.isTraceEnabled()) { if (log.isTraceEnabled()) {
LOGGER.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url); log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
} }
URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
@ -158,8 +161,8 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance
URI requestUrl = reconstructURI(serviceInstance, uri); URI requestUrl = reconstructURI(serviceInstance, uri);
if (LOGGER.isTraceEnabled()) { if (log.isTraceEnabled()) {
LOGGER.trace("LoadBalancerClientFilter url chosen: " + requestUrl); log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
} }
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response); exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response);
@ -206,7 +209,7 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance
return headers; return headers;
} }
String genRouterHint(ServerWebExchange exchange, String peerServiceName) { private String genRouterHint(ServerWebExchange exchange, String peerServiceName) {
Map<String, String> routerLabels = genRouterLabels(exchange, peerServiceName); Map<String, String> routerLabels = genRouterLabels(exchange, peerServiceName);
String encodedLabelsContent; String encodedLabelsContent;
try { try {
@ -218,9 +221,9 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance
return encodedLabelsContent; return encodedLabelsContent;
} }
Map<String, String> genRouterLabels(ServerWebExchange exchange, String peerServiceName) { private Map<String, String> genRouterLabels(ServerWebExchange exchange, String peerServiceName) {
// local service labels // local service labels
Map<String, String> labels = new HashMap<>(metadataLocalProperties.getContent()); Map<String, String> labels = new HashMap<>(staticMetadataManager.getMergedStaticMetadata());
// labels from rule expression // labels from rule expression
Set<String> expressionLabelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE, Set<String> expressionLabelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
@ -241,7 +244,7 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance
} }
} }
catch (Throwable t) { catch (Throwable t) {
LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t); log.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t);
} }
}); });
} }
@ -259,6 +262,6 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance
return Collections.emptyMap(); 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);
}

@ -20,7 +20,9 @@ package com.tencent.cloud.polaris.router;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
import com.google.common.collect.Sets;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -38,27 +40,76 @@ public class PolarisRouterContextTest {
labels.put("k2", "v2"); labels.put("k2", "v2");
PolarisRouterContext routerContext = new PolarisRouterContext(); 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(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size());
Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size()); Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size());
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k1")); Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k1"));
Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k2")); Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k2"));
Assert.assertNull(routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k3")); Assert.assertNull(routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k3"));
} }
@Test @Test
public void testSetNull() { public void testSetNull() {
PolarisRouterContext routerContext = new PolarisRouterContext(); 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.TRANSITIVE_LABELS).size());
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size()); Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size());
} }
@Test @Test
public void testGetEmptyRouterContext() { public void testGetEmptyRouterContext() {
PolarisRouterContext routerContext = new PolarisRouterContext(); PolarisRouterContext routerContext = new PolarisRouterContext();
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size()); 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"));
} }
} }

@ -31,9 +31,13 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.pojo.PolarisServiceInstance; import com.tencent.cloud.common.pojo.PolarisServiceInstance;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties; import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
import com.tencent.cloud.polaris.router.config.PolarisMetadataRouterProperties; import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisNearByRouterProperties; import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisRuleBasedRouterProperties; 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.DefaultInstance;
import com.tencent.polaris.api.pojo.DefaultServiceInstances; import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.Instance; import com.tencent.polaris.api.pojo.Instance;
@ -46,6 +50,7 @@ import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
@ -62,13 +67,18 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* test for {@link PolarisRouterServiceInstanceListSupplier} * test for {@link PolarisRouterServiceInstanceListSupplier}.
*
* @author lepdou 2022-05-26 * @author lepdou 2022-05-26
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class PolarisRouterServiceInstanceListSupplierTest { public class PolarisRouterServiceInstanceListSupplierTest {
private static AtomicBoolean initTransitiveMetadata = new AtomicBoolean(false); private static final AtomicBoolean initTransitiveMetadata = new AtomicBoolean(false);
private final String testNamespace = "testNamespace";
private final String testCallerService = "testCallerService";
private final String testCalleeService = "testCalleeService";
private final List<RouterRequestInterceptor> requestInterceptors = new ArrayList<>();
@Mock @Mock
private ServiceInstanceListSupplier delegate; private ServiceInstanceListSupplier delegate;
@Mock @Mock
@ -81,9 +91,13 @@ public class PolarisRouterServiceInstanceListSupplierTest {
private PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties; private PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
@Mock @Mock
private RouterAPI routerAPI; private RouterAPI routerAPI;
private String testNamespace = "testNamespace";
private String testCallerService = "testCallerService"; @Before
private String testCalleeService = "testCalleeService"; public void before() {
requestInterceptors.add(new MetadataRouterRequestInterceptor(polarisMetadataRouterProperties));
requestInterceptors.add(new NearbyRouterRequestInterceptor(polarisNearByRouterProperties));
requestInterceptors.add(new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties));
}
@Test @Test
public void testBuildMetadataRouteRequest() { public void testBuildMetadataRouteRequest() {
@ -95,14 +109,19 @@ public class PolarisRouterServiceInstanceListSupplierTest {
setTransitiveMetadata(); setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier( PolarisRouterServiceInstanceListSupplier polarisSupplier = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties, delegate, routerAPI, requestInterceptors, null);
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ServiceInstances serviceInstances = assembleServiceInstances(); ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext(); 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 = polarisSupplier.buildProcessRoutersRequest(serviceInstances, routerContext);
polarisSupplier.processRouterRequestInterceptors(request, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA); Map<String, String> routerMetadata = request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA);
@ -124,14 +143,14 @@ public class PolarisRouterServiceInstanceListSupplierTest {
setTransitiveMetadata(); setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier( PolarisRouterServiceInstanceListSupplier polarisSupplier = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties, delegate, routerAPI, requestInterceptors, null);
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ServiceInstances serviceInstances = assembleServiceInstances(); ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext(); PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext); ProcessRoutersRequest request = polarisSupplier.buildProcessRoutersRequest(serviceInstances, routerContext);
polarisSupplier.processRouterRequestInterceptors(request, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY); Map<String, String> routerMetadata = request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY);
@ -154,14 +173,14 @@ public class PolarisRouterServiceInstanceListSupplierTest {
setTransitiveMetadata(); setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier( PolarisRouterServiceInstanceListSupplier polarisSupplier = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties, delegate, routerAPI, requestInterceptors, null);
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ServiceInstances serviceInstances = assembleServiceInstances(); ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext(); PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext); ProcessRoutersRequest request = polarisSupplier.buildProcessRoutersRequest(serviceInstances, routerContext);
polarisSupplier.processRouterRequestInterceptors(request, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED); Map<String, String> routerMetadata = request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED);
@ -184,14 +203,13 @@ public class PolarisRouterServiceInstanceListSupplierTest {
setTransitiveMetadata(); setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier( PolarisRouterServiceInstanceListSupplier polarisSupplier = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties, delegate, routerAPI, requestInterceptors, null);
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ProcessRoutersResponse assembleResponse = assembleProcessRoutersResponse(); ProcessRoutersResponse assembleResponse = assembleProcessRoutersResponse();
when(routerAPI.processRouters(any())).thenReturn(assembleResponse); when(routerAPI.processRouters(any())).thenReturn(assembleResponse);
Flux<List<ServiceInstance>> servers = compositeRule.doRouter(assembleServers(), assembleRouterContext()); Flux<List<ServiceInstance>> servers = polarisSupplier.doRouter(assembleServers(), assembleRouterContext());
Assert.assertEquals(assembleResponse.getServiceInstances().getInstances().size(), Assert.assertEquals(assembleResponse.getServiceInstances().getInstances().size(),
@ -228,8 +246,8 @@ public class PolarisRouterServiceInstanceListSupplierTest {
Map<String, String> routerLabels = new HashMap<>(); Map<String, String> routerLabels = new HashMap<>();
routerLabels.put("k2", "v2"); routerLabels.put("k2", "v2");
routerLabels.put("k3", "v3"); routerLabels.put("k3", "v3");
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, routerLabels); routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, routerLabels);
return routerContext; return routerContext;
} }

@ -13,14 +13,14 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.router.resttemplate; package com.tencent.cloud.polaris.router.beanprocessor;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.BeanFactoryUtils; import com.tencent.cloud.common.util.BeanFactoryUtils;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerInterceptor;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -38,18 +38,19 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* Test for ${@link PolarisLoadBalancerBeanPostProcessor} * Test for ${@link LoadBalancerInterceptorBeanPostProcessor}.
*
* @author lepdou 2022-05-26 * @author lepdou 2022-05-26
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class PolarisLoadBalancerBeanPostProcessorTest { public class LoadBalancerInterceptorBeanPostProcessorTest {
@Mock @Mock
private LoadBalancerClient loadBalancerClient; private LoadBalancerClient loadBalancerClient;
@Mock @Mock
private LoadBalancerRequestFactory loadBalancerRequestFactory; private LoadBalancerRequestFactory loadBalancerRequestFactory;
@Mock @Mock
private MetadataLocalProperties metadataLocalProperties; private StaticMetadataManager staticMetadataManager;
@Mock @Mock
private RouterRuleLabelResolver routerRuleLabelResolver; private RouterRuleLabelResolver routerRuleLabelResolver;
@Mock @Mock
@ -59,7 +60,7 @@ public class PolarisLoadBalancerBeanPostProcessorTest {
public void testWrapperLoadBalancerInterceptor() { public void testWrapperLoadBalancerInterceptor() {
when(beanFactory.getBean(LoadBalancerRequestFactory.class)).thenReturn(loadBalancerRequestFactory); when(beanFactory.getBean(LoadBalancerRequestFactory.class)).thenReturn(loadBalancerRequestFactory);
when(beanFactory.getBean(LoadBalancerClient.class)).thenReturn(loadBalancerClient); when(beanFactory.getBean(LoadBalancerClient.class)).thenReturn(loadBalancerClient);
when(beanFactory.getBean(MetadataLocalProperties.class)).thenReturn(metadataLocalProperties); when(beanFactory.getBean(StaticMetadataManager.class)).thenReturn(staticMetadataManager);
when(beanFactory.getBean(RouterRuleLabelResolver.class)).thenReturn(routerRuleLabelResolver); when(beanFactory.getBean(RouterRuleLabelResolver.class)).thenReturn(routerRuleLabelResolver);
try (MockedStatic<BeanFactoryUtils> mockedBeanFactoryUtils = Mockito.mockStatic(BeanFactoryUtils.class)) { try (MockedStatic<BeanFactoryUtils> mockedBeanFactoryUtils = Mockito.mockStatic(BeanFactoryUtils.class)) {
@ -67,7 +68,7 @@ public class PolarisLoadBalancerBeanPostProcessorTest {
.thenReturn(null); .thenReturn(null);
LoadBalancerInterceptor loadBalancerInterceptor = new LoadBalancerInterceptor(loadBalancerClient, loadBalancerRequestFactory); LoadBalancerInterceptor loadBalancerInterceptor = new LoadBalancerInterceptor(loadBalancerClient, loadBalancerRequestFactory);
PolarisLoadBalancerBeanPostProcessor processor = new PolarisLoadBalancerBeanPostProcessor(); LoadBalancerInterceptorBeanPostProcessor processor = new LoadBalancerInterceptorBeanPostProcessor();
processor.setBeanFactory(beanFactory); processor.setBeanFactory(beanFactory);
Object bean = processor.postProcessBeforeInitialization(loadBalancerInterceptor, ""); Object bean = processor.postProcessBeforeInitialization(loadBalancerInterceptor, "");
@ -78,7 +79,7 @@ public class PolarisLoadBalancerBeanPostProcessorTest {
@Test @Test
public void testNotWrapperLoadBalancerInterceptor() { public void testNotWrapperLoadBalancerInterceptor() {
PolarisLoadBalancerBeanPostProcessor processor = new PolarisLoadBalancerBeanPostProcessor(); LoadBalancerInterceptorBeanPostProcessor processor = new LoadBalancerInterceptorBeanPostProcessor();
processor.setBeanFactory(beanFactory); processor.setBeanFactory(beanFactory);
OtherBean otherBean = new OtherBean(); OtherBean otherBean = new OtherBean();

@ -13,14 +13,14 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.router.scg; package com.tencent.cloud.polaris.router.beanprocessor;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.BeanFactoryUtils; import com.tencent.cloud.common.util.BeanFactoryUtils;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.scg.PolarisReactiveLoadBalancerClientFilter;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -40,11 +40,12 @@ import static org.mockito.Mockito.when;
/** /**
* Test for ${@link PolarisLoadBalancerClientBeanPostProcessor} * Test for ${@link ReactiveLoadBalancerClientFilterBeanPostProcessor}.
*
* @author lepdou 2022-07-04 * @author lepdou 2022-07-04
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class PolarisLoadBalancerClientBeanPostProcessorTest { public class ReactiveLoadBalancerClientFilterBeanPostProcessorTest {
@Mock @Mock
private BeanFactory beanFactory; private BeanFactory beanFactory;
@ -55,7 +56,7 @@ public class PolarisLoadBalancerClientBeanPostProcessorTest {
@Mock @Mock
private LoadBalancerProperties loadBalancerProperties; private LoadBalancerProperties loadBalancerProperties;
@Mock @Mock
private MetadataLocalProperties metadataLocalProperties; private StaticMetadataManager staticMetadataManager;
@Mock @Mock
private RouterRuleLabelResolver routerRuleLabelResolver; private RouterRuleLabelResolver routerRuleLabelResolver;
@ -64,7 +65,7 @@ public class PolarisLoadBalancerClientBeanPostProcessorTest {
when(beanFactory.getBean(LoadBalancerClientFactory.class)).thenReturn(loadBalancerClientFactory); when(beanFactory.getBean(LoadBalancerClientFactory.class)).thenReturn(loadBalancerClientFactory);
when(beanFactory.getBean(GatewayLoadBalancerProperties.class)).thenReturn(gatewayLoadBalancerProperties); when(beanFactory.getBean(GatewayLoadBalancerProperties.class)).thenReturn(gatewayLoadBalancerProperties);
when(beanFactory.getBean(LoadBalancerProperties.class)).thenReturn(loadBalancerProperties); when(beanFactory.getBean(LoadBalancerProperties.class)).thenReturn(loadBalancerProperties);
when(beanFactory.getBean(MetadataLocalProperties.class)).thenReturn(metadataLocalProperties); when(beanFactory.getBean(StaticMetadataManager.class)).thenReturn(staticMetadataManager);
when(beanFactory.getBean(RouterRuleLabelResolver.class)).thenReturn(routerRuleLabelResolver); when(beanFactory.getBean(RouterRuleLabelResolver.class)).thenReturn(routerRuleLabelResolver);
try (MockedStatic<BeanFactoryUtils> mockedBeanFactoryUtils = Mockito.mockStatic(BeanFactoryUtils.class)) { try (MockedStatic<BeanFactoryUtils> mockedBeanFactoryUtils = Mockito.mockStatic(BeanFactoryUtils.class)) {
@ -74,7 +75,7 @@ public class PolarisLoadBalancerClientBeanPostProcessorTest {
ReactiveLoadBalancerClientFilter reactiveLoadBalancerClientFilter = new ReactiveLoadBalancerClientFilter( ReactiveLoadBalancerClientFilter reactiveLoadBalancerClientFilter = new ReactiveLoadBalancerClientFilter(
loadBalancerClientFactory, gatewayLoadBalancerProperties, loadBalancerProperties); loadBalancerClientFactory, gatewayLoadBalancerProperties, loadBalancerProperties);
PolarisLoadBalancerClientBeanPostProcessor processor = new PolarisLoadBalancerClientBeanPostProcessor(); ReactiveLoadBalancerClientFilterBeanPostProcessor processor = new ReactiveLoadBalancerClientFilterBeanPostProcessor();
processor.setBeanFactory(beanFactory); processor.setBeanFactory(beanFactory);
Object bean = processor.postProcessBeforeInitialization(reactiveLoadBalancerClientFilter, ""); Object bean = processor.postProcessBeforeInitialization(reactiveLoadBalancerClientFilter, "");
@ -85,7 +86,7 @@ public class PolarisLoadBalancerClientBeanPostProcessorTest {
@Test @Test
public void testNotWrapLoadBalancerInterceptor() { public void testNotWrapLoadBalancerInterceptor() {
PolarisLoadBalancerClientBeanPostProcessor processor = new PolarisLoadBalancerClientBeanPostProcessor(); ReactiveLoadBalancerClientFilterBeanPostProcessor processor = new ReactiveLoadBalancerClientFilterBeanPostProcessor();
processor.setBeanFactory(beanFactory); processor.setBeanFactory(beanFactory);
OtherBean otherBean = new OtherBean(); OtherBean otherBean = new OtherBean();

@ -29,7 +29,7 @@ import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterConstants;
@ -56,7 +56,7 @@ import static org.mockito.Mockito.when;
public class RouterLabelFeignInterceptorTest { public class RouterLabelFeignInterceptorTest {
@Mock @Mock
private MetadataLocalProperties metadataLocalProperties; private StaticMetadataManager staticMetadataManager;
@Mock @Mock
private RouterRuleLabelResolver routerRuleLabelResolver; private RouterRuleLabelResolver routerRuleLabelResolver;
@Mock @Mock
@ -66,7 +66,7 @@ public class RouterLabelFeignInterceptorTest {
public void testResolveRouterLabel() throws UnsupportedEncodingException { public void testResolveRouterLabel() throws UnsupportedEncodingException {
RouterLabelFeignInterceptor routerLabelFeignInterceptor = new RouterLabelFeignInterceptor( RouterLabelFeignInterceptor routerLabelFeignInterceptor = new RouterLabelFeignInterceptor(
Collections.singletonList(routerLabelResolver), Collections.singletonList(routerLabelResolver),
metadataLocalProperties, routerRuleLabelResolver); staticMetadataManager, routerRuleLabelResolver);
// mock request template // mock request template
RequestTemplate requestTemplate = new RequestTemplate(); RequestTemplate requestTemplate = new RequestTemplate();
@ -111,7 +111,7 @@ public class RouterLabelFeignInterceptorTest {
Map<String, String> localMetadata = new HashMap<>(); Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k3", "v31"); localMetadata.put("k3", "v31");
localMetadata.put("k4", "v4"); localMetadata.put("k4", "v4");
when(metadataLocalProperties.getContent()).thenReturn(localMetadata); when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata);
routerLabelFeignInterceptor.apply(requestTemplate); routerLabelFeignInterceptor.apply(requestTemplate);

@ -29,7 +29,7 @@ import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterConstants;
@ -74,7 +74,7 @@ public class PolarisLoadBalancerInterceptorTest {
@Mock @Mock
private SpringWebRouterLabelResolver routerLabelResolver; private SpringWebRouterLabelResolver routerLabelResolver;
@Mock @Mock
private MetadataLocalProperties metadataLocalProperties; private StaticMetadataManager staticMetadataManager;
@Mock @Mock
private RouterRuleLabelResolver routerRuleLabelResolver; private RouterRuleLabelResolver routerRuleLabelResolver;
@ -103,7 +103,7 @@ public class PolarisLoadBalancerInterceptorTest {
Map<String, String> localMetadata = new HashMap<>(); Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k1", "v1"); localMetadata.put("k1", "v1");
localMetadata.put("k2", "v2"); localMetadata.put("k2", "v2");
when(metadataLocalProperties.getContent()).thenReturn(localMetadata); when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata);
// mock expression rule labels // mock expression rule labels
@ -132,11 +132,11 @@ public class PolarisLoadBalancerInterceptorTest {
when(loadBalancerRequestFactory.createRequest(request, null, null)).thenReturn(loadBalancerRequest); when(loadBalancerRequestFactory.createRequest(request, null, null)).thenReturn(loadBalancerRequest);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient, PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient,
loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), metadataLocalProperties, routerRuleLabelResolver); loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), staticMetadataManager, routerRuleLabelResolver);
polarisLoadBalancerInterceptor.intercept(request, null, null); polarisLoadBalancerInterceptor.intercept(request, null, null);
verify(metadataLocalProperties).getContent(); verify(staticMetadataManager).getMergedStaticMetadata();
verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService); verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService);
verify(routerLabelResolver).resolve(request, null, expressionKeys); verify(routerLabelResolver).resolve(request, null, expressionKeys);
} }
@ -151,7 +151,7 @@ public class PolarisLoadBalancerInterceptorTest {
Map<String, String> localMetadata = new HashMap<>(); Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k1", "v1"); localMetadata.put("k1", "v1");
localMetadata.put("k2", "v2"); localMetadata.put("k2", "v2");
when(metadataLocalProperties.getContent()).thenReturn(localMetadata); when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata);
// mock expression rule labels // mock expression rule labels
@ -177,11 +177,11 @@ public class PolarisLoadBalancerInterceptorTest {
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext); mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient, PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient,
loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), metadataLocalProperties, routerRuleLabelResolver); loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), staticMetadataManager, routerRuleLabelResolver);
polarisLoadBalancerInterceptor.setLabelsToHeaders(request, null, calleeService); polarisLoadBalancerInterceptor.setLabelsToHeaders(request, null, calleeService);
verify(metadataLocalProperties).getContent(); verify(staticMetadataManager).getMergedStaticMetadata();
verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService); verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService);
verify(routerLabelResolver).resolve(request, null, expressionKeys); verify(routerLabelResolver).resolve(request, null, expressionKeys);

@ -29,7 +29,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterConstants;
@ -70,7 +70,7 @@ public class PolarisReactiveLoadBalancerClientFilterTest {
private static MockedStatic<MetadataContextHolder> mockedMetadataContextHolder; private static MockedStatic<MetadataContextHolder> mockedMetadataContextHolder;
@Mock @Mock
private MetadataLocalProperties metadataLocalProperties; private StaticMetadataManager staticMetadataManager;
@Mock @Mock
private SpringWebRouterLabelResolver routerLabelResolver; private SpringWebRouterLabelResolver routerLabelResolver;
@Mock @Mock
@ -109,12 +109,12 @@ public class PolarisReactiveLoadBalancerClientFilterTest {
@Test @Test
public void testGenRouterHttpHeaders() throws UnsupportedEncodingException { public void testGenRouterHttpHeaders() throws UnsupportedEncodingException {
PolarisReactiveLoadBalancerClientFilter filter = new PolarisReactiveLoadBalancerClientFilter(loadBalancerClientFactory, PolarisReactiveLoadBalancerClientFilter filter = new PolarisReactiveLoadBalancerClientFilter(loadBalancerClientFactory,
gatewayLoadBalancerProperties, loadBalancerProperties, metadataLocalProperties, routerRuleLabelResolver, gatewayLoadBalancerProperties, loadBalancerProperties, staticMetadataManager, routerRuleLabelResolver,
Lists.newArrayList(routerLabelResolver)); Lists.newArrayList(routerLabelResolver));
Map<String, String> localMetadata = new HashMap<>(); Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("env", "blue"); localMetadata.put("env", "blue");
when(metadataLocalProperties.getContent()).thenReturn(localMetadata); when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata);
Set<String> expressionLabelKeys = Sets.newHashSet("${http.header.k1}", "${http.query.userid}"); Set<String> expressionLabelKeys = Sets.newHashSet("${http.header.k1}", "${http.query.userid}");
when(routerRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString(), anyString())).thenReturn(expressionLabelKeys); when(routerRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString(), anyString())).thenReturn(expressionLabelKeys);

@ -27,11 +27,26 @@ import org.springframework.core.Ordered;
public final class MetadataConstant { public final class MetadataConstant {
/** /**
* Default Private Constructor. * 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() { private MetadataConstant() {
}
}
/** /**
* Order of filter, interceptor, ... * Order of filter, interceptor, ...
*/ */

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

@ -1,64 +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.
*/
EQUALS("EQUALS"),
/**
* case sensitive string not equals.
*/
NOT_EQUALS("NOT_EQUALS"),
/**
* 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 EQUALS:
return firstExpectedValue != null && StringUtils.equals(actualValue, firstExpectedValue);
case NOT_EQUALS:
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, EQUALS.value)) {
return EQUALS;
}
if (StringUtils.equalsIgnoreCase(operation, NOT_EQUALS.value)) {
return NOT_EQUALS;
}
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,316 +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
*/
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 prefix of expression.
*/
public static final String LABEL_PREFIX = "${";
/**
* the suffix of expression.
*/
public static final String LABEL_SUFFIX = "}";
private ExpressionLabelUtils() {
}
public static boolean isExpressionLabel(String labelKey) {
if (StringUtils.isEmpty(labelKey)) {
return false;
}
return StringUtils.startsWith(labelKey, LABEL_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;
}
}

@ -78,6 +78,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. * Json to Map.
* @param jsonStr Json String * @param jsonStr Json String

@ -0,0 +1,135 @@
/*
* 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 prefix of expression.
*/
public static final String LABEL_PREFIX = "${";
/**
* the suffix of expression.
*/
public static final String LABEL_SUFFIX = "}";
private ExpressionLabelUtils() {
}
public static boolean isExpressionLabel(String labelKey) {
if (StringUtils.isEmpty(labelKey)) {
return false;
}
return StringUtils.startsWith(labelKey, LABEL_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; package com.tencent.cloud.common.metadata.config;
import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.Test; import org.junit.Test;
@ -49,9 +48,6 @@ public class MetadataAutoConfigurationTest {
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> { .run(context -> {
Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
}); });
} }
@ -64,9 +60,6 @@ public class MetadataAutoConfigurationTest {
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> { .run(context -> {
Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
}); });
} }
@ -79,9 +72,6 @@ public class MetadataAutoConfigurationTest {
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> { .run(context -> {
Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); 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.EQUALS.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v2", Operation.EQUALS.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList(""), "v2", Operation.EQUALS.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "", Operation.EQUALS.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), null, Operation.EQUALS.getValue()));
Assert.assertFalse(Operation.match(Collections.emptyList(), "v1", Operation.EQUALS.getValue()));
}
@Test
public void testNotEqual() {
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v1", Operation.NOT_EQUALS.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "v2", Operation.NOT_EQUALS.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList(""), "v2", Operation.NOT_EQUALS.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "", Operation.NOT_EQUALS.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), null, Operation.NOT_EQUALS.getValue()));
Assert.assertTrue(Operation.match(Collections.emptyList(), "v1", Operation.NOT_EQUALS.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 java.util.Set;
import com.google.common.collect.Sets; 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.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -105,7 +108,7 @@ public class ExpressionLabelUtilsTest {
request.setMethod(HttpMethod.GET.name()); request.setMethod(HttpMethod.GET.name());
request.setRequestURI("/users"); 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(validLabel1));
Assert.assertEquals("zhangsan", result.get(validLabel2)); Assert.assertEquals("zhangsan", result.get(validLabel2));
@ -149,7 +152,7 @@ public class ExpressionLabelUtilsTest {
.cookie(new HttpCookie("uid", "zhangsan")).build(); .cookie(new HttpCookie("uid", "zhangsan")).build();
MockServerWebExchange exchange = new MockServerWebExchange.Builder(httpRequest).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(validLabel1));
Assert.assertEquals("zhangsan", result.get(validLabel2)); Assert.assertEquals("zhangsan", result.get(validLabel2));
@ -193,7 +196,7 @@ public class ExpressionLabelUtilsTest {
request.setURI(URI.create("http://calleeService/user/get?uid=zhangsan")); request.setURI(URI.create("http://calleeService/user/get?uid=zhangsan"));
request.getHeaders().add("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(validLabel1));
Assert.assertEquals("zhangsan", result.get(validLabel2)); Assert.assertEquals("zhangsan", result.get(validLabel2));

@ -69,6 +69,16 @@
<artifactId>spring-cloud-starter-tencent-polaris-config</artifactId> <artifactId>spring-cloud-starter-tencent-polaris-config</artifactId>
</dependency> </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>
<dependency> <dependency>
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-pushgateway-plugin</artifactId> <artifactId>spring-cloud-tencent-pushgateway-plugin</artifactId>

@ -159,6 +159,19 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </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>
<dependency> <dependency>
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-pushgateway-plugin</artifactId> <artifactId>spring-cloud-tencent-pushgateway-plugin</artifactId>

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

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

@ -29,6 +29,16 @@
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId> <artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency> </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> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId> <artifactId>spring-cloud-starter-gateway</artifactId>

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

@ -0,0 +1,174 @@
# 多测试环境样例说明
[English](./README.md) | 简体中文
## 一、部署结构
<img src="./imgs/structs.png"/>
如上图所示,一共有三个环境:
1. 基线环境,包含 FrontService、MiddleService、BackendService
2. feature1 环境,包含 MiddleService、BackendService
3. feature2 环境,包含 FrontService、BackendService
并且在入口处,部署网关服务。
三条请求链路:
1. 基线环境链路Gateway -> FrontService(基线) -> MiddleService(基线) -> BackendService(基线)
2. feature1 环境链路Gateway -> FrontService(基线) -> MiddleService(feature1) -> BackendService(feature1)
3. feature2 环境链路Gateway -> FrontService(feature2) -> MiddleService(基线) -> BackendService(feature2)
## 二、运行样例
无需任何代码变更,直接启动 base、feature1、feature2、featureenv-gateway 下所有应用即可。
应用默认指向北极星官方的体验环境,启动成功后可直接到体验站点查看服务注册数据。
- 管控台地址: http://14.116.241.63:8080/
- 账号polaris
- 密码polaris
## 三、测试
### 方式一:客户端打标
#### 基线环境链路
````
curl http://127.0.0.1:9999/featureenv-front-example/router/rest
````
响应结果base 表示基线环境)
````
featureenv-front-example[base] -> featureenv-middle-example[base] -> featureenv-backend-example[base]
````
#### feature1 环境链路
通过 X-Polaris-Metadata-Transitive-featureenv 请求头指定特性环境。
````
curl -H'X-Polaris-Metadata-Transitive-featureenv:feature1' http://127.0.0.1:9999/featureenv-front-example/router/rest
````
响应结果
````
featureenv-front-example[base] -> featureenv-middle-example[feature1] -> featureenv-backend-example[feature1]
````
#### feature2 环境链路
通过 X-Polaris-Metadata-Transitive-featureenv 请求头指定特性环境。
````
curl -H'X-Polaris-Metadata-Transitive-featureenv:feature2' http://127.0.0.1:9999/featureenv-front-example/router/rest
````
响应结果
````
featureenv-front-example[feature2] -> featureenv-middle-example[base] -> featureenv-backend-example[feature2]
````
### 方式二:网关流量染色
模拟一种实际的场景,假设客户端请求有一个 uid 请求参数,期望:
1. uid=1000 的请求打到 feature1 环境
2. uid=2000 的请求打到 feature2 环境
3. 其它 uid 的请求打到基线环境
**配置染色规则**
配置地址http://14.116.241.63:8080/#/filegroup-detail?group=featureenv-gateway&namespace=default
修改 rule/staining.json 配置文件,填写以下规则:
````json
{
"rules": [
{
"conditions": [
{
"key": "${http.query.uid}",
"values": [
"1000"
],
"operation": "EQUALS"
}
],
"labels": [
{
"key": "featureenv",
"value": "feature1"
}
]
},
{
"conditions": [
{
"key": "${http.query.uid}",
"values": [
"2000"
],
"operation": "EQUALS"
}
],
"labels": [
{
"key": "featureenv",
"value": "feature2"
}
]
}
]
}
````
填写完后发布配置即可。
#### 基线环境链路
````
curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=3000
````
响应结果base 表示基线环境)
````
featureenv-front-example[base] -> featureenv-middle-example[base] -> featureenv-backend-example[base]
````
#### feature1 环境链路
通过 X-Polaris-Metadata-Transitive-featureenv 请求头指定特性环境。
````
curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=1000
````
响应结果
````
featureenv-front-example[base] -> featureenv-middle-example[feature1] -> featureenv-backend-example[feature1]
````
#### feature2 环境链路
通过 X-Polaris-Metadata-Transitive-featureenv 请求头指定特性环境。
````
curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=2000
````
响应结果
````
featureenv-front-example[feature2] -> featureenv-middle-example[base] -> featureenv-backend-example[feature2]
````

@ -0,0 +1,179 @@
## A Multi-Feature Environment Example
English | [简体中文](./README-zh.md)
## I. Deployment Structure
<img src="./imgs/structs.png" alt="multi-feature environment structure"/>
As shown in the figure above, there are three environments.
1. `baseline` environment, including `FrontService`, `MiddleService`, `BackendService`
2. `feature1` environment, including `MiddleService`, `BackendService`
3. `feature2` environment, including `FrontService`, `BackendService`
And at the entrance, deploy the `gateway` service.
Three request links.
1. `baseline` environment link, `Gateway` -> `FrontService`(baseline) -> `MiddleService`(baseline) -> `BackendService`(
baseline)
2. `feature1` environment link, `Gateway` -> `FrontService`(baseline) -> `MiddleService`(feature1) -> `BackendService`(
feature1)
3. `feature2` environment link, `Gateway` -> `FrontService`(feature2) -> `MiddleService`(baseline) -> `BackendService`(
feature2)
## II. Running
Without any code changes, just start all the applications under `base`, `feature1`, `feature2`, `featureenv-gateway`
directly.
By default, the applications point to the official Polaris experience environment, and you can directly view the service
registration data at the experience site after a successful launch.
- Console address: http://14.116.241.63:8080/
- Accountpolaris
- Password: polaris
## III. Testing
### Mode 1: Client Request With `featureenv` Label
#### `baseline` environment link
````
curl http://127.0.0.1:9999/featureenv-front-example/router/rest
````
Response results (base indicates baseline environment)
````
featureenv-front-example[base] -> featureenv-middle-example[base] -> featureenv-backend-example[base]
````
#### `feature1` environment link
Specify the feature environment via the `X-Polaris-Metadata-Transitive-featureenv` request header.
````
curl -H'X-Polaris-Metadata-Transitive-featureenv:feature1' http://127.0.0.1:9999/featureenv-front-example/router/rest
````
Response results
````
featureenv-front-example[base] -> featureenv-middle-example[feature1] -> featureenv-backend-example[feature1]
````
#### `feature2` environment link
Specify the feature environment via the `X-Polaris-Metadata-Transitive-featureenv` request header.
````
curl -H'X-Polaris-Metadata-Transitive-featureenv:feature2' http://127.0.0.1:9999/featureenv-front-example/router/rest
````
Response results
````
featureenv-front-example[feature2] -> featureenv-middle-example[base] -> featureenv-backend-example[feature2]
````
### Mode 2: Gateway traffic staining
Simulate a real-world scenario, assuming that the client request has a uid request parameter and expects:
1. `uid=1000` requests hit the `feature1` environment
2. `uid=2000` requests hit the `feature2` environment
3. requests with other uid hit the `baseline` environment
**Configure coloring rules**
Polaris Configuration Addresshttp://14.116.241.63:8080/#/filegroup-detail?group=featureenv-gateway&namespace=default
Modify the `rule/staining.json` configuration file and fill in the following rule:
````json
{
"rules": [
{
"conditions": [
{
"key": "${http.query.uid}",
"values": [
"1000"
],
"operation": "EQUALS"
}
],
"labels": [
{
"key": "featureenv",
"value": "feature1"
}
]
},
{
"conditions": [
{
"key": "${http.query.uid}",
"values": [
"2000"
],
"operation": "EQUALS"
}
],
"labels": [
{
"key": "featureenv",
"value": "feature2"
}
]
}
]
}
````
Just fill out and publish the configuration.
#### `baseline` Environment Link
````
curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=3000
````
Response results (base indicates baseline environment)
````
featureenv-front-example[base] -> featureenv-middle-example[base] -> featureenv-backend-example[base]
````
#### `feature1` Environment Link
Specify the feature environment via the `X-Polaris-Metadata-Transitive-featureenv` request header.
````
curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=1000
````
Response results
````
featureenv-front-example[base] -> featureenv-middle-example[feature1] -> featureenv-backend-example[feature1]
````
#### `feature2` Environment Link
Specify the feature environment via the `X-Polaris-Metadata-Transitive-featureenv` request header.
````
curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=2000
````
Response results
````
featureenv-front-example[feature2] -> featureenv-middle-example[base] -> featureenv-backend-example[feature2]
````

@ -0,0 +1,16 @@
<?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>base</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-backend</artifactId>
</project>

@ -0,0 +1,44 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.featureenv.basebackend;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lepdou 2022-07-20
*/
@RestController
@RequestMapping("/router")
public class BackendController {
@Value("${spring.application.name}")
private String appName;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
return appName + "[base]";
}
}

@ -0,0 +1,34 @@
/*
* 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.featureenv.basebackend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author lepdou 2022-07-20
*/
@SpringBootApplication
@EnableFeignClients
public class BaseBackendApplication {
public static void main(String[] args) {
SpringApplication.run(BaseBackendApplication.class, args);
}
}

@ -0,0 +1,15 @@
server:
session-timeout: 1800
port: 10002
spring:
application:
name: featureenv-backend-example
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -0,0 +1,16 @@
<?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>base</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-front</artifactId>
</project>

@ -0,0 +1,34 @@
/*
* 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.featureenv.basefront;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author lepdou 2022-07-20
*/
@SpringBootApplication
@EnableFeignClients
public class BaseFrontApplication {
public static void main(String[] args) {
SpringApplication.run(BaseFrontApplication.class, args);
}
}

@ -0,0 +1,51 @@
/*
* 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.featureenv.basefront;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lepdou 2022-07-20
*/
@RestController
@RequestMapping("/router")
public class FrontController {
@Value("${spring.application.name}")
private String appName;
@Autowired
private MiddleService middleService;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
String curName = appName + "[base]";
String resp = middleService.rest();
return curName + " -> " + resp;
}
}

@ -0,0 +1,33 @@
/*
* 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.featureenv.basefront;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author lepdou 2022-07-20
*/
@FeignClient("featureenv-middle-example")
public interface MiddleService {
@GetMapping("/router/rest")
String rest();
}

@ -0,0 +1,15 @@
server:
session-timeout: 1800
port: 10000
spring:
application:
name: featureenv-front-example
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -0,0 +1,18 @@
<?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>base</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base-middle</artifactId>
</project>

@ -0,0 +1,33 @@
/*
* 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.featureenv.basemiddle;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author lepdou 2022-07-20
*/
@FeignClient("featureenv-backend-example")
public interface BackendService {
@GetMapping("/router/rest")
String rest();
}

@ -0,0 +1,34 @@
/*
* 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.featureenv.basemiddle;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author lepdou 2022-07-20
*/
@SpringBootApplication
@EnableFeignClients
public class BaseMiddleApplication {
public static void main(String[] args) {
SpringApplication.run(BaseMiddleApplication.class, args);
}
}

@ -0,0 +1,51 @@
/*
* 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.featureenv.basemiddle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lepdou 2022-07-20
*/
@RestController
@RequestMapping("/router")
public class MiddleController {
@Value("${spring.application.name}")
private String appName;
@Autowired
private BackendService backendService;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
String curName = appName + "[base]";
String resp = backendService.rest();
return curName + " -> " + resp;
}
}

@ -0,0 +1,15 @@
server:
session-timeout: 1800
port: 10001
spring:
application:
name: featureenv-middle-example
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>polaris-router-featureenv-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>base</artifactId>
<packaging>pom</packaging>
<modules>
<module>base-front</module>
<module>base-middle</module>
<module>base-backend</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,16 @@
<?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>feature1</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feature1-backend</artifactId>
</project>

@ -0,0 +1,51 @@
/*
* 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.featureenv.feature1backend;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lepdou 2022-07-20
*/
@RestController
@RequestMapping("/router")
public class BackendController {
@Autowired
private StaticMetadataManager staticMetadataManager;
@Value("${spring.application.name}")
private String appName;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
String featureEnv = staticMetadataManager.getMergedStaticMetadata().get("featureenv");
return appName + "[" + featureEnv + "]";
}
}

@ -0,0 +1,34 @@
/*
* 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.featureenv.feature1backend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author lepdou 2022-07-20
*/
@SpringBootApplication
@EnableFeignClients
public class Feature1BackendApplication {
public static void main(String[] args) {
SpringApplication.run(Feature1BackendApplication.class, args);
}
}

@ -0,0 +1,19 @@
server:
session-timeout: 1800
port: 11002
spring:
application:
name: featureenv-backend-example
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
tencent:
metadata:
content:
featureenv: feature1
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -0,0 +1,16 @@
<?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>feature1</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feature1-middle</artifactId>
</project>

@ -0,0 +1,33 @@
/*
* 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.featureenv.feature1middle;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author lepdou 2022-07-20
*/
@FeignClient("featureenv-backend-example")
public interface BackendService {
@GetMapping("/router/rest")
String rest();
}

@ -0,0 +1,34 @@
/*
* 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.featureenv.feature1middle;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author lepdou 2022-07-20
*/
@SpringBootApplication
@EnableFeignClients
public class Feature1MiddleApplication {
public static void main(String[] args) {
SpringApplication.run(Feature1MiddleApplication.class, args);
}
}

@ -0,0 +1,58 @@
/*
* 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.featureenv.feature1middle;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lepdou 2022-07-20
*/
@RestController
@RequestMapping("/router")
public class MiddleController {
@Value("${spring.application.name}")
private String appName;
@Autowired
private BackendService backendService;
@Autowired
private StaticMetadataManager staticMetadataManager;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
String featureEnv = staticMetadataManager.getMergedStaticMetadata().get("featureenv");
String curName = appName + "[" + featureEnv + "]";
String resp = backendService.rest();
return curName + " -> " + resp;
}
}

@ -0,0 +1,19 @@
server:
session-timeout: 1800
port: 11001
spring:
application:
name: featureenv-middle-example
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
tencent:
metadata:
content:
featureenv: feature1
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -0,0 +1,32 @@
<?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>polaris-router-featureenv-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feature1</artifactId>
<packaging>pom</packaging>
<modules>
<module>feature1-middle</module>
<module>feature1-backend</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,16 @@
<?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>feature2</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feature2-backend</artifactId>
</project>

@ -0,0 +1,51 @@
/*
* 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.featureenv.feature2backend;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lepdou 2022-07-20
*/
@RestController
@RequestMapping("/router")
public class BackendController {
@Autowired
private StaticMetadataManager staticMetadataManager;
@Value("${spring.application.name}")
private String appName;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
String featureEnv = staticMetadataManager.getMergedStaticMetadata().get("featureenv");
return appName + "[" + featureEnv + "]";
}
}

@ -0,0 +1,34 @@
/*
* 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.featureenv.feature2backend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author lepdou 2022-07-20
*/
@SpringBootApplication
@EnableFeignClients
public class Feature2BackendApplication {
public static void main(String[] args) {
SpringApplication.run(Feature2BackendApplication.class, args);
}
}

@ -0,0 +1,19 @@
server:
session-timeout: 1800
port: 12002
spring:
application:
name: featureenv-backend-example
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
tencent:
metadata:
content:
featureenv: feature2
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -0,0 +1,17 @@
<?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>feature2</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feature2-front</artifactId>
</project>

@ -0,0 +1,34 @@
/*
* 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.featureenv.feature2front;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author lepdou 2022-07-20
*/
@SpringBootApplication
@EnableFeignClients
public class Feature2FrontApplication {
public static void main(String[] args) {
SpringApplication.run(Feature2FrontApplication.class, args);
}
}

@ -0,0 +1,58 @@
/*
* 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.featureenv.feature2front;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lepdou 2022-07-20
*/
@RestController
@RequestMapping("/router")
public class FrontController {
@Autowired
private StaticMetadataManager staticMetadataManager;
@Value("${spring.application.name}")
private String appName;
@Autowired
private MiddleService middleService;
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/rest")
public String rest() {
String featureEnv = staticMetadataManager.getMergedStaticMetadata().get("featureenv");
String curName = appName + "[" + featureEnv + "]";
String resp = middleService.rest();
return curName + " -> " + resp;
}
}

@ -0,0 +1,33 @@
/*
* 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.featureenv.feature2front;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author lepdou 2022-07-20
*/
@FeignClient("featureenv-middle-example")
public interface MiddleService {
@GetMapping("/router/rest")
String rest();
}

@ -0,0 +1,19 @@
server:
session-timeout: 1800
port: 12000
spring:
application:
name: featureenv-front-example
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
tencent:
metadata:
content:
featureenv: feature2
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -0,0 +1,32 @@
<?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>polaris-router-featureenv-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feature2</artifactId>
<packaging>pom</packaging>
<modules>
<module>feature2-front</module>
<module>feature2-backend</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>polaris-router-featureenv-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>featureenv-gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</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>
</dependencies>
</project>

@ -0,0 +1,33 @@
/*
* 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.featureenv.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author lepdou 2022-07-20
*/
@SpringBootApplication
public class FeatureEnvScgApplication {
public static void main(String[] args) {
SpringApplication.run(FeatureEnvScgApplication.class, args);
}
}

@ -0,0 +1,68 @@
server:
session-timeout: 1800
port: 9999
spring:
application:
name: featureenv-gateway
cloud:
tencent:
plugin:
scg:
staining:
enabled: true
rule-staining:
enabled: true
router:
feature-env:
enabled: true
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
gateway:
discovery:
locator:
enabled: true
'predicates[0]':
name: Path
args:
patterns: '''/'' + serviceId + ''/**'''
'filters[0]':
name: RewritePath
args:
regexp: '''/'' + serviceId + ''/(?<remaining>.*)'''
replacement: '''/$\{remaining}'''
'filters[1]':
name: Retry
args:
retries: 3
exceptions:
'[0]': '''java.net.ConnectException'''
'[1]': '''java.io.IOException'''
statuses:
'[0]': '''BAD_GATEWAY'''
'[1]': '''SERVICE_UNAVAILABLE'''
series:
'[0]': '''CLIENT_ERROR'''
methods:
'[0]': '''GET'''
'[1]': '''POST'''
'[2]': '''PUT'''
'[3]': '''DELETE'''
backoff:
firstBackoff: '''100ms'''
maxBackoff: '''500ms'''
factor: 2
basedOnPreviousValue: false
routes:
- id: featureenv-front-example
uri: lb://featureenv-front-example
predicates:
- Path=/featureenv-front-example/**
filters:
- StripPrefix=1
logging:
level:
org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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-examples</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>polaris-router-featureenv-example</artifactId>
<packaging>pom</packaging>
<modules>
<module>base</module>
<module>feature1</module>
<module>feature2</module>
<module>featureenv-gateway</module>
</modules>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</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-bootstrap</artifactId>
</dependency>
</dependencies>
</project>

@ -24,6 +24,7 @@
<module>polaris-router-example</module> <module>polaris-router-example</module>
<module>metadata-transfer-example</module> <module>metadata-transfer-example</module>
<module>polaris-router-grayrelease-example</module> <module>polaris-router-grayrelease-example</module>
<module>polaris-router-featureenv-example</module>
<module>polaris-config-data-example</module> <module>polaris-config-data-example</module>
</modules> </modules>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save