support spring-retry router (#631)
parent
2c37f1a317
commit
ecd97c33c7
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue