support spring-retry router (#631)

pull/638/head
lepdou 2 years ago committed by GitHub
parent 2c37f1a317
commit ecd97c33c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,3 +7,4 @@
- [Optimize: Maybe remove Chinese characters](https://github.com/Tencent/spring-cloud-tencent/pull/609)
- [Bugfix: fix feign report call result error when using feign direct call](https://github.com/Tencent/spring-cloud-tencent/pull/623)
- [optimize:optimize PolarisRouterContext and constants.](https://github.com/Tencent/spring-cloud-tencent/pull/628)
- [support spring-retry router](https://github.com/Tencent/spring-cloud-tencent/pull/631)

@ -62,6 +62,12 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-zuul</artifactId>

@ -32,8 +32,10 @@ import com.netflix.loadbalancer.RoundRobinRule;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.WeightedResponseTimeRule;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.pojo.PolarisServer;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils;
import com.tencent.cloud.polaris.loadbalancer.PolarisWeightedRule;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
@ -45,19 +47,20 @@ import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
import org.yaml.snakeyaml.util.UriEncoder;
import org.springframework.http.HttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
*
* Service routing entrance.
*
* <p>
* Rule routing needs to rely on request parameters for server filtering,
* and {@link com.netflix.loadbalancer.ServerListFilter#getFilteredListOfServers(List)}
* The interface cannot obtain the context object of the request granularity,
* so the routing capability cannot be achieved through ServerListFilter.
*
* <p>
* And {@link com.netflix.loadbalancer.IRule#choose(Object)} provides the ability to pass in context parameters,
* so routing capabilities are implemented through IRule.
*
@ -117,6 +120,7 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule {
ILoadBalancer loadBalancer = new SimpleLoadBalancer();
// 2. filter by router
if (key instanceof PolarisRouterContext) {
// router implement for Feign and scg
PolarisRouterContext routerContext = (PolarisRouterContext) key;
List<Server> serversAfterRouter = doRouter(allServers, routerContext);
// 3. filter by load balance.
@ -124,9 +128,26 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule {
// because the list of servers may be different after filtered by router
loadBalancer.addServers(serversAfterRouter);
}
else if (key instanceof HttpRequest) {
// router implement for rest template
HttpRequest request = (HttpRequest) key;
String routerContextStr = request.getHeaders().getFirst(RouterConstant.HEADER_ROUTER_CONTEXT);
if (StringUtils.isEmpty(routerContextStr)) {
loadBalancer.addServers(allServers);
}
else {
PolarisRouterContext routerContext = JacksonUtils.deserialize(UriEncoder.decode(routerContextStr),
PolarisRouterContext.class);
List<Server> serversAfterRouter = doRouter(allServers, routerContext);
loadBalancer.addServers(serversAfterRouter);
}
}
else {
loadBalancer.addServers(allServers);
}
delegateRule.setLoadBalancer(loadBalancer);
return delegateRule.choose(key);

@ -102,4 +102,12 @@ public class PolarisRouterContext {
}
labels.put(labelType, subLabels);
}
public Map<String, Map<String, String>> getLabels() {
return labels;
}
public void setLabels(Map<String, Map<String, String>> labels) {
this.labels = labels;
}
}

@ -18,13 +18,8 @@
package com.tencent.cloud.polaris.router.beanprocessor;
import java.util.List;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.BeanFactoryUtils;
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.resttemplate.RouterContextFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
@ -36,7 +31,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.lang.NonNull;
/**
* Replace LoadBalancerInterceptor with PolarisLoadBalancerInterceptor.
* Replace {@link LoadBalancerInterceptor} with {@link PolarisLoadBalancerInterceptor}.
* PolarisLoadBalancerInterceptor can pass routing context information.
*
* @author lepdou 2022-05-18
@ -57,12 +52,9 @@ public class LoadBalancerInterceptorBeanPostProcessor implements BeanPostProcess
// Replaces the default LoadBalancerInterceptor implementation and returns a custom PolarisLoadBalancerInterceptor
LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class);
LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class);
List<SpringWebRouterLabelResolver> routerLabelResolvers = BeanFactoryUtils.getBeans(factory, SpringWebRouterLabelResolver.class);
StaticMetadataManager staticMetadataManager = this.factory.getBean(StaticMetadataManager.class);
RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class);
RouterContextFactory routerContextFactory = this.factory.getBean(RouterContextFactory.class);
return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory,
routerLabelResolvers, staticMetadataManager, routerRuleLabelResolver);
return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory, routerContextFactory);
}
return bean;
}

@ -0,0 +1,68 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.beanprocessor;
import com.tencent.cloud.polaris.router.resttemplate.PolarisRetryLoadBalancerInterceptor;
import com.tencent.cloud.polaris.router.resttemplate.RouterContextFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRetryProperties;
import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor;
import org.springframework.lang.NonNull;
/**
* Replace {@link RetryLoadBalancerInterceptor}RetryLoadBalancerInterceptor with {@link PolarisRetryLoadBalancerInterceptor}.
* PolarisRetryLoadBalancerInterceptor can pass routing context information.
*
* @author lepdou 2022-10-09
*/
public class RetryLoadBalancerInterceptorBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private BeanFactory factory;
@Override
public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
this.factory = beanFactory;
}
@Override
public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
if (bean instanceof RetryLoadBalancerInterceptor) {
// Support rest template router.
// Replaces the default RetryLoadBalancerInterceptor implementation
// and returns a custom PolarisRetryLoadBalancerInterceptor
LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class);
LoadBalancerRetryProperties lbProperties = this.factory.getBean(LoadBalancerRetryProperties.class);
LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class);
LoadBalancedRetryFactory lbRetryFactory = this.factory.getBean(LoadBalancedRetryFactory.class);
RouterContextFactory routerContextFactory = this.factory.getBean(RouterContextFactory.class);
return new PolarisRetryLoadBalancerInterceptor(loadBalancerClient, lbProperties, requestFactory, lbRetryFactory,
routerContextFactory);
}
return bean;
}
}

@ -25,13 +25,16 @@ import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.beanprocessor.LoadBalancerClientFilterBeanPostProcessor;
import com.tencent.cloud.polaris.router.beanprocessor.LoadBalancerInterceptorBeanPostProcessor;
import com.tencent.cloud.polaris.router.beanprocessor.RetryLoadBalancerInterceptorBeanPostProcessor;
import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.resttemplate.RouterContextFactory;
import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import com.tencent.cloud.polaris.router.zuul.PolarisRibbonRoutingFilter;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -59,13 +62,6 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
@Import({PolarisNearByRouterProperties.class, PolarisMetadataRouterProperties.class, PolarisRuleBasedRouterProperties.class})
public class RouterAutoConfiguration {
@Bean
@Order(HIGHEST_PRECEDENCE)
@ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor")
public LoadBalancerInterceptorBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() {
return new LoadBalancerInterceptorBeanPostProcessor();
}
@Bean
@Order(HIGHEST_PRECEDENCE)
@ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.LoadBalancerClientFilter")
@ -96,6 +92,32 @@ public class RouterAutoConfiguration {
return new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
public static class RestTemplateAutoConfiguration {
@Bean
@Order(HIGHEST_PRECEDENCE)
@ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor")
public LoadBalancerInterceptorBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() {
return new LoadBalancerInterceptorBeanPostProcessor();
}
@Bean
@Order(HIGHEST_PRECEDENCE)
@ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor")
public RetryLoadBalancerInterceptorBeanPostProcessor retryLoadBalancerInterceptorBeanPostProcessor() {
return new RetryLoadBalancerInterceptorBeanPostProcessor();
}
@Bean
@Order(HIGHEST_PRECEDENCE)
public RouterContextFactory routerContextFactory(List<SpringWebRouterLabelResolver> routerLabelResolvers,
StaticMetadataManager staticMetadataManager,
RouterRuleLabelResolver routerRuleLabelResolver) {
return new RouterContextFactory(routerLabelResolvers, staticMetadataManager, routerRuleLabelResolver);
}
}
/**
* AutoConfiguration for router module integrate for zuul.
*/

@ -0,0 +1,85 @@
/*
* 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.resttemplate;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryContext;
/**
* Override InterceptorRetryPolicy for passing router context.
*
* @author lepdou 2022-10-09
*/
public class PolarisInterceptorRetryPolicy extends InterceptorRetryPolicy {
private final HttpRequest request;
private final LoadBalancedRetryPolicy policy;
private final ServiceInstanceChooser serviceInstanceChooser;
private final String serviceName;
private final PolarisRouterContext routerContext;
public PolarisInterceptorRetryPolicy(HttpRequest request, LoadBalancedRetryPolicy policy,
ServiceInstanceChooser serviceInstanceChooser, String serviceName,
PolarisRouterContext routerContext) {
super(request, policy, serviceInstanceChooser, serviceName);
this.request = request;
this.policy = policy;
this.serviceInstanceChooser = serviceInstanceChooser;
this.serviceName = serviceName;
this.routerContext = routerContext;
}
@Override
public boolean canRetry(RetryContext context) {
if (serviceInstanceChooser instanceof RibbonLoadBalancerClient) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
if (lbContext.getRetryCount() == 0 && lbContext.getServiceInstance() == null) {
loadbalancerWithRouterContext(lbContext, routerContext);
return true;
}
return policy.canRetryNextServer(lbContext);
}
else {
return super.canRetry(context);
}
}
private void loadbalancerWithRouterContext(LoadBalancedRetryContext lbContext, PolarisRouterContext routerContext) {
RibbonLoadBalancerClient ribbonLoadBalancerClient = (RibbonLoadBalancerClient) serviceInstanceChooser;
// set router context to request
RouterContextHelper.setRouterContextToRequest(request, routerContext);
ServiceInstance serviceInstance = ribbonLoadBalancerClient.choose(serviceName, request);
lbContext.setServiceInstance(serviceInstance);
}
}

@ -19,40 +19,18 @@
package com.tencent.cloud.polaris.router.resttemplate;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.core.Ordered;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
/**
* PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor capabilities.
@ -61,34 +39,19 @@ import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
* @author lepdou 2022-05-18
*/
public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisLoadBalancerInterceptor.class);
private final LoadBalancerClient loadBalancer;
private final LoadBalancerRequestFactory requestFactory;
private final List<SpringWebRouterLabelResolver> routerLabelResolvers;
private final StaticMetadataManager staticMetadataManager;
private final RouterRuleLabelResolver routerRuleLabelResolver;
private final RouterContextFactory routerContextFactory;
private final boolean isRibbonLoadBalanceClient;
public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory,
List<SpringWebRouterLabelResolver> routerLabelResolvers,
StaticMetadataManager staticMetadataManager,
RouterRuleLabelResolver routerRuleLabelResolver) {
RouterContextFactory routerContextFactory) {
super(loadBalancer, requestFactory);
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
this.staticMetadataManager = staticMetadataManager;
this.routerRuleLabelResolver = routerRuleLabelResolver;
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder));
this.routerLabelResolvers = routerLabelResolvers;
}
else {
this.routerLabelResolvers = null;
}
this.routerContextFactory = routerContextFactory;
this.isRibbonLoadBalanceClient = loadBalancer instanceof RibbonLoadBalancerClient;
}
@ -101,72 +64,21 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
"Request URI does not contain a valid hostname: " + originalUri);
if (isRibbonLoadBalanceClient) {
PolarisRouterContext routerContext = genRouterContext(request, body, peerServiceName);
//1. create router context
PolarisRouterContext routerContext = routerContextFactory.create(request, body, peerServiceName);
//2. set router context to request
RouterContextHelper.setRouterContextToRequest(request, routerContext);
//3. do loadbalancer and execute request
ClientHttpResponse response = ((RibbonLoadBalancerClient) loadBalancer).execute(peerServiceName,
this.requestFactory.createRequest(request, body, execution), routerContext);
Map<String, String> labels = routerContext.getLabels(RouterConstant.ROUTER_LABELS);
// put labels in header
String encodedLabelsContent;
try {
encodedLabelsContent = URLEncoder.encode(JacksonUtils.serialize2Json(labels), UTF_8);
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException("unsupported charset exception " + UTF_8);
}
response.getHeaders().add(RouterConstant.ROUTER_LABELS, encodedLabelsContent);
//4. set router context to response
RouterContextHelper.setRouterContextToResponse(routerContext, response);
return response;
}
return this.loadBalancer.execute(peerServiceName,
this.requestFactory.createRequest(request, body, execution));
}
PolarisRouterContext genRouterContext(HttpRequest request, byte[] body, String peerServiceName) {
// local service labels
Map<String, String> labels = new HashMap<>(staticMetadataManager.getMergedStaticMetadata());
// labels from rule expression
Set<String> expressionLabelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, peerServiceName);
Map<String, String> ruleExpressionLabels = getExpressionLabels(request, expressionLabelKeys);
if (!CollectionUtils.isEmpty(ruleExpressionLabels)) {
labels.putAll(ruleExpressionLabels);
}
// labels from request
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.forEach(resolver -> {
try {
Map<String, String> customResolvedLabels = resolver.resolve(request, body, expressionLabelKeys);
if (!CollectionUtils.isEmpty(customResolvedLabels)) {
labels.putAll(customResolvedLabels);
}
}
catch (Throwable t) {
LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t);
}
});
}
// labels from downstream
Map<String, String> transitiveLabels = MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
labels.putAll(transitiveLabels);
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.putLabels(RouterConstant.ROUTER_LABELS, labels);
routerContext.putLabels(RouterConstant.TRANSITIVE_LABELS, transitiveLabels);
return routerContext;
}
private Map<String, String> getExpressionLabels(HttpRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
return SpringWebExpressionLabelUtils.resolve(request, labelKeys);
return this.loadBalancer.execute(peerServiceName, this.requestFactory.createRequest(request, body, execution));
}
}

@ -0,0 +1,158 @@
/*
* 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.resttemplate;
import java.io.IOException;
import java.net.URI;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.ClientHttpResponseStatusCodeException;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRecoveryCallback;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRetryProperties;
import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.retry.RetryListener;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
/**
* Override {@link RetryLoadBalancerInterceptor} for passing router context.
*
* @author lepdou 2022-10-09
*/
public class PolarisRetryLoadBalancerInterceptor extends RetryLoadBalancerInterceptor {
private static final Log LOG = LogFactory.getLog(PolarisRetryLoadBalancerInterceptor.class);
private final LoadBalancerClient loadBalancer;
private final LoadBalancerRetryProperties lbProperties;
private final LoadBalancerRequestFactory requestFactory;
private final LoadBalancedRetryFactory lbRetryFactory;
private final RouterContextFactory routerContextFactory;
public PolarisRetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRetryProperties lbProperties,
LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory lbRetryFactory, RouterContextFactory routerContextFactory) {
super(loadBalancer, lbProperties, requestFactory, lbRetryFactory);
this.loadBalancer = loadBalancer;
this.lbProperties = lbProperties;
this.requestFactory = requestFactory;
this.lbRetryFactory = lbRetryFactory;
this.routerContextFactory = routerContextFactory;
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
final String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
final LoadBalancedRetryPolicy retryPolicy = lbRetryFactory.createRetryPolicy(serviceName, loadBalancer);
//1. create router context
PolarisRouterContext routerContext = routerContextFactory.create(request, body, serviceName);
RetryTemplate template = createRetryTemplate(serviceName, request, routerContext, retryPolicy);
//2. do loadbalancer is template.execute
return template.execute(context -> {
ServiceInstance serviceInstance = null; if (context instanceof LoadBalancedRetryContext) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
serviceInstance = lbContext.getServiceInstance();
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Retrieved service instance from LoadBalancedRetryContext: %s", serviceInstance));
}
}
//retry loadbalancer if LoadBalancedRetryContext does not has instance
if (serviceInstance == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. " + "Reattempting service instance selection");
}
serviceInstance = loadBalancer.choose(serviceName); if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Selected service instance: %s", serviceInstance));
}
}
//3. execute request
ClientHttpResponse response = loadBalancer.execute(serviceName, serviceInstance,
requestFactory.createRequest(request, body, execution));
//4. set router context to response
RouterContextHelper.setRouterContextToResponse(routerContext, response);
//5. handle response whether retry execute request
int statusCode = response.getRawStatusCode();
if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Retrying on status code: %d", statusCode));
}
byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody());
response.close();
throw new ClientHttpResponseStatusCodeException(serviceName, response, bodyCopy);
}
return response;
}, new LoadBalancedRecoveryCallback<ClientHttpResponse, ClientHttpResponse>() {
// This is a special case, where both parameters to
// LoadBalancedRecoveryCallback are
// the same. In most cases they would be different.
@Override
protected ClientHttpResponse createResponse(ClientHttpResponse response, URI uri) {
return response;
}
});
}
private RetryTemplate createRetryTemplate(String serviceName, HttpRequest request, PolarisRouterContext routerContext,
LoadBalancedRetryPolicy retryPolicy) {
RetryTemplate template = new RetryTemplate();
BackOffPolicy backOffPolicy = lbRetryFactory.createBackOffPolicy(serviceName);
template.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
template.setThrowLastExceptionOnExhausted(true);
RetryListener[] retryListeners = lbRetryFactory.createRetryListeners(serviceName);
if (retryListeners != null && retryListeners.length != 0) {
template.setListeners(retryListeners);
}
//Override: use PolarisInterceptorRetryPolicy for passing router context
template.setRetryPolicy(!lbProperties.isEnabled() || retryPolicy == null ? new NeverRetryPolicy() :
new PolarisInterceptorRetryPolicy(request, retryPolicy, loadBalancer, serviceName, routerContext));
return template;
}
}

@ -0,0 +1,117 @@
/*
* 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.resttemplate;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpRequest;
import org.springframework.util.CollectionUtils;
/**
* Polaris router context factory.
*
* @author lepdou 2022-10-09
*/
public class RouterContextFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(RouterContextFactory.class);
private final List<SpringWebRouterLabelResolver> routerLabelResolvers;
private final StaticMetadataManager staticMetadataManager;
private final RouterRuleLabelResolver routerRuleLabelResolver;
public RouterContextFactory(List<SpringWebRouterLabelResolver> routerLabelResolvers,
StaticMetadataManager staticMetadataManager,
RouterRuleLabelResolver routerRuleLabelResolver) {
this.staticMetadataManager = staticMetadataManager;
this.routerRuleLabelResolver = routerRuleLabelResolver;
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder));
this.routerLabelResolvers = routerLabelResolvers;
}
else {
this.routerLabelResolvers = null;
}
}
public PolarisRouterContext create(HttpRequest request, byte[] body, String peerServiceName) {
// local service labels
Map<String, String> labels = new HashMap<>(staticMetadataManager.getMergedStaticMetadata());
// labels from rule expression
Set<String> expressionLabelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, peerServiceName);
Map<String, String> ruleExpressionLabels = getExpressionLabels(request, expressionLabelKeys);
if (!CollectionUtils.isEmpty(ruleExpressionLabels)) {
labels.putAll(ruleExpressionLabels);
}
// labels from request
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.forEach(resolver -> {
try {
Map<String, String> customResolvedLabels = resolver.resolve(request, body, expressionLabelKeys);
if (!CollectionUtils.isEmpty(customResolvedLabels)) {
labels.putAll(customResolvedLabels);
}
}
catch (Throwable t) {
LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t);
}
});
}
// labels from downstream
Map<String, String> transitiveLabels = MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
labels.putAll(transitiveLabels);
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.putLabels(RouterConstant.ROUTER_LABELS, labels);
routerContext.putLabels(RouterConstant.TRANSITIVE_LABELS, transitiveLabels);
return routerContext;
}
private Map<String, String> getExpressionLabels(HttpRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
return SpringWebExpressionLabelUtils.resolve(request, labelKeys);
}
}

@ -0,0 +1,68 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.router.resttemplate;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
/**
* Set router context to request and response.
*
* @author lepdou 2022-10-09
*/
public final class RouterContextHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(RouterContextHelper.class);
private RouterContextHelper() {
}
public static void setRouterContextToRequest(HttpRequest request, PolarisRouterContext routerContext) {
try {
request.getHeaders().add(RouterConstant.HEADER_ROUTER_CONTEXT,
URLEncoder.encode(JacksonUtils.serialize2Json(routerContext), UTF_8));
}
catch (Exception e) {
LOGGER.error("[SCT] serialize router context error.", e);
}
}
public static void setRouterContextToResponse(PolarisRouterContext routerContext, ClientHttpResponse response) {
Map<String, String> labels = routerContext.getLabels(RouterConstant.ROUTER_LABELS);
try {
response.getHeaders().add(RouterConstant.ROUTER_LABELS,
URLEncoder.encode(JacksonUtils.serialize2Json(labels), UTF_8));
}
catch (UnsupportedEncodingException e) {
LOGGER.error("[SCT] add router label to response header error.", e);
}
}
}

@ -18,9 +18,7 @@
package com.tencent.cloud.polaris.router.resttemplate;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.BeanFactoryUtils;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.beanprocessor.LoadBalancerInterceptorBeanPostProcessor;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import org.junit.Assert;
@ -51,18 +49,12 @@ public class PolarisLoadBalancerBeanPostProcessorTest {
@Mock
private LoadBalancerRequestFactory loadBalancerRequestFactory;
@Mock
private StaticMetadataManager staticMetadataManager;
@Mock
private RouterRuleLabelResolver routerRuleLabelResolver;
@Mock
private BeanFactory beanFactory;
@Test
public void testWrapperLoadBalancerInterceptor() {
when(beanFactory.getBean(LoadBalancerRequestFactory.class)).thenReturn(loadBalancerRequestFactory);
when(beanFactory.getBean(LoadBalancerClient.class)).thenReturn(loadBalancerClient);
when(beanFactory.getBean(StaticMetadataManager.class)).thenReturn(staticMetadataManager);
when(beanFactory.getBean(RouterRuleLabelResolver.class)).thenReturn(routerRuleLabelResolver);
try (MockedStatic<BeanFactoryUtils> mockedBeanFactoryUtils = Mockito.mockStatic(BeanFactoryUtils.class)) {
mockedBeanFactoryUtils.when(() -> BeanFactoryUtils.getBeans(beanFactory, SpringWebRouterLabelResolver.class))

@ -22,22 +22,15 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import org.assertj.core.api.Assertions;
import org.junit.Assert;
import org.junit.Test;
@ -79,11 +72,7 @@ public class PolarisLoadBalancerInterceptorTest {
@Mock
private LoadBalancerRequestFactory loadBalancerRequestFactory;
@Mock
private SpringWebRouterLabelResolver routerLabelResolver;
@Mock
private StaticMetadataManager staticMetadataManager;
@Mock
private RouterRuleLabelResolver routerRuleLabelResolver;
private RouterContextFactory routerContextFactory;
@Test
public void testProxyRibbonLoadBalance() throws Exception {
@ -91,28 +80,13 @@ public class PolarisLoadBalancerInterceptorTest {
String calleeService = "calleeService";
HttpRequest request = new MockedHttpRequest("http://" + calleeService + "/user/get");
PolarisRouterContext routerContext = new PolarisRouterContext();
Map<String, String> routerLabels = new HashMap<>();
// mock local metadata
Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k1", "v1");
localMetadata.put("k2", "v2");
when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata);
routerLabels.putAll(localMetadata);
// mock expression rule labels
Set<String> expressionKeys = new HashSet<>();
expressionKeys.add("${http.method}");
expressionKeys.add("${http.uri}");
when(routerRuleLabelResolver.getExpressionLabelKeys(callerService, callerService, calleeService)).thenReturn(expressionKeys);
routerLabels.putAll(SpringWebExpressionLabelUtils.resolve(request, expressionKeys));
// mock custom resolved from request
Map<String, String> customResolvedLabels = new HashMap<>();
customResolvedLabels.put("k3", "v3");
customResolvedLabels.put("k4", "v4");
when(routerLabelResolver.resolve(request, null, expressionKeys)).thenReturn(customResolvedLabels);
routerLabels.putAll(customResolvedLabels);
routerLabels.put("k1", "v1");
routerLabels.put("k2", "v12");
routerContext.putLabels(RouterConstant.ROUTER_LABELS, routerLabels);
when(routerContextFactory.create(request, null, calleeService)).thenReturn(routerContext);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
@ -120,30 +94,21 @@ public class PolarisLoadBalancerInterceptorTest {
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
// mock transitive metadata
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
transitiveLabels.put("k2", "v22");
when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels);
routerLabels.putAll(transitiveLabels);
try (MockedStatic<MetadataContextHolder> mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class)) {
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
LoadBalancerRequest<ClientHttpResponse> loadBalancerRequest = new MockedLoadBalancerRequest<>();
when(loadBalancerRequestFactory.createRequest(request, null, null)).thenReturn(loadBalancerRequest);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient,
loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), staticMetadataManager, routerRuleLabelResolver);
loadBalancerRequestFactory, routerContextFactory);
ClientHttpResponse mockedResponse = new MockClientHttpResponse(new byte[] {}, HttpStatus.OK);
when(loadBalancerClient.execute(eq(calleeService), eq(loadBalancerRequest), any(PolarisRouterContext.class))).thenReturn(mockedResponse);
polarisLoadBalancerInterceptor.intercept(request, null, null);
verify(staticMetadataManager).getMergedStaticMetadata();
verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService);
verify(routerLabelResolver).resolve(request, null, expressionKeys);
String encodedLabelsContent;
try {
encodedLabelsContent = URLEncoder.encode(JacksonUtils.serialize2Json(routerLabels), UTF_8);
@ -170,9 +135,7 @@ public class PolarisLoadBalancerInterceptorTest {
when(notRibbonLoadBalancerClient.execute(calleeService, loadBalancerRequest)).thenReturn(mockedResponse);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(
notRibbonLoadBalancerClient, loadBalancerRequestFactory,
Collections.singletonList(routerLabelResolver), staticMetadataManager,
routerRuleLabelResolver);
notRibbonLoadBalancerClient, loadBalancerRequestFactory, routerContextFactory);
ClientHttpResponse response = polarisLoadBalancerInterceptor.intercept(request, null, null);
@ -182,69 +145,6 @@ public class PolarisLoadBalancerInterceptorTest {
}
@Test
public void testRouterContext() {
String callerService = "callerService";
String calleeService = "calleeService";
HttpRequest request = new MockedHttpRequest("http://" + calleeService + "/user/get");
// mock local metadata
Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k1", "v1");
localMetadata.put("k2", "v2");
when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata);
// mock expression rule labels
Set<String> expressionKeys = new HashSet<>();
expressionKeys.add("${http.method}");
expressionKeys.add("${http.uri}");
when(routerRuleLabelResolver.getExpressionLabelKeys(callerService, callerService, calleeService)).thenReturn(expressionKeys);
// mock custom resolved from request
Map<String, String> customResolvedLabels = new HashMap<>();
customResolvedLabels.put("k2", "v22");
customResolvedLabels.put("k4", "v4");
when(routerLabelResolver.resolve(request, null, expressionKeys)).thenReturn(customResolvedLabels);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(callerService);
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
// mock transitive metadata
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
transitiveLabels.put("k2", "v22");
when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels);
try (MockedStatic<MetadataContextHolder> mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class)) {
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor =
new PolarisLoadBalancerInterceptor(loadBalancerClient, loadBalancerRequestFactory,
Collections.singletonList(routerLabelResolver), staticMetadataManager,
routerRuleLabelResolver);
PolarisRouterContext routerContext = polarisLoadBalancerInterceptor.genRouterContext(request, null, calleeService);
verify(staticMetadataManager).getMergedStaticMetadata();
verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService);
verify(routerLabelResolver).resolve(request, null, expressionKeys);
Assert.assertEquals("v1", routerContext.getLabels(RouterConstant.TRANSITIVE_LABELS).get("k1"));
Assert.assertEquals("v22", routerContext.getLabels(RouterConstant.TRANSITIVE_LABELS).get("k2"));
Assert.assertEquals("v1", routerContext.getLabels(RouterConstant.ROUTER_LABELS).get("k1"));
Assert.assertEquals("v22", routerContext.getLabels(RouterConstant.ROUTER_LABELS).get("k2"));
Assert.assertEquals("v4", routerContext.getLabels(RouterConstant.ROUTER_LABELS).get("k4"));
Assert.assertEquals("GET", routerContext.getLabels(RouterConstant.ROUTER_LABELS)
.get("${http.method}"));
Assert.assertEquals("/user/get", routerContext.getLabels(RouterConstant.ROUTER_LABELS)
.get("${http.uri}"));
}
}
}
static class MockedLoadBalancerRequest<T> implements LoadBalancerRequest<T> {
@Override
@ -273,7 +173,7 @@ public class PolarisLoadBalancerInterceptorTest {
@Override
public HttpHeaders getHeaders() {
return null;
return new HttpHeaders();
}
}
}

@ -0,0 +1,150 @@
/*
* 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.resttemplate;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Test for {@link RouterContextFactory}
*
* @author lepdou 2022-10-09
*/
@RunWith(MockitoJUnitRunner.class)
public class RouterContextFactoryTest {
@Mock
private SpringWebRouterLabelResolver springWebRouterLabelResolver;
@Mock
private StaticMetadataManager staticMetadataManager;
@Mock
private RouterRuleLabelResolver routerRuleLabelResolver;
@Test
public void testRouterContext() {
String callerService = "callerService";
String calleeService = "calleeService";
HttpRequest request = new MockedHttpRequest("http://" + calleeService + "/user/get");
// mock local metadata
Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k1", "v1");
localMetadata.put("k2", "v2");
when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata);
// mock expression rule labels
Set<String> expressionKeys = new HashSet<>();
expressionKeys.add("${http.method}");
expressionKeys.add("${http.uri}");
when(routerRuleLabelResolver.getExpressionLabelKeys(callerService, callerService, calleeService)).thenReturn(expressionKeys);
// mock custom resolved from request
Map<String, String> customResolvedLabels = new HashMap<>();
customResolvedLabels.put("k2", "v22");
customResolvedLabels.put("k4", "v4");
when(springWebRouterLabelResolver.resolve(request, null, expressionKeys)).thenReturn(customResolvedLabels);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(callerService);
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
// mock transitive metadata
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
transitiveLabels.put("k2", "v22");
when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels);
try (MockedStatic<MetadataContextHolder> mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class)) {
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
RouterContextFactory routerContextFactory = new RouterContextFactory(Arrays.asList(springWebRouterLabelResolver),
staticMetadataManager, routerRuleLabelResolver);
PolarisRouterContext routerContext = routerContextFactory.create(request, null, calleeService);
verify(staticMetadataManager).getMergedStaticMetadata();
verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService);
verify(springWebRouterLabelResolver).resolve(request, null, expressionKeys);
Assert.assertEquals("v1", routerContext.getLabels(RouterConstant.TRANSITIVE_LABELS).get("k1"));
Assert.assertEquals("v22", routerContext.getLabels(RouterConstant.TRANSITIVE_LABELS).get("k2"));
Assert.assertEquals("v1", routerContext.getLabels(RouterConstant.ROUTER_LABELS).get("k1"));
Assert.assertEquals("v22", routerContext.getLabels(RouterConstant.ROUTER_LABELS).get("k2"));
Assert.assertEquals("v4", routerContext.getLabels(RouterConstant.ROUTER_LABELS).get("k4"));
Assert.assertEquals("GET", routerContext.getLabels(RouterConstant.ROUTER_LABELS)
.get("${http.method}"));
Assert.assertEquals("/user/get", routerContext.getLabels(RouterConstant.ROUTER_LABELS)
.get("${http.uri}"));
}
}
}
static class MockedHttpRequest implements HttpRequest {
private URI uri;
MockedHttpRequest(String url) {
this.uri = URI.create(url);
}
@Override
public String getMethodValue() {
return HttpMethod.GET.name();
}
@Override
public URI getURI() {
return uri;
}
@Override
public HttpHeaders getHeaders() {
return new HttpHeaders();
}
}
}

@ -38,6 +38,11 @@ public final class RouterConstant {
*/
public static final String ROUTER_LABEL_HEADER = "internal-router-label";
/**
*
*/
public static final String HEADER_ROUTER_CONTEXT = "routerContext";
/**
* Default Private Constructor.
*/

@ -23,6 +23,12 @@
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

Loading…
Cancel
Save