pull/1471/head
shedfreewu 11 months ago
parent 437aa29f65
commit bbf02912ac

@ -18,13 +18,10 @@
package com.tencent.cloud.metadata.core;
import java.net.URI;
import java.util.Arrays;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -79,15 +76,10 @@ public class EncodeTransferMedataRestTemplateInterceptorTest {
@Bean
public RestTemplate restTemplate() {
EncodeTransferMedataRestTemplateEnhancedPlugin plugin = new EncodeTransferMedataRestTemplateEnhancedPlugin();
EnhancedRestTemplateInterceptor interceptor = new EnhancedRestTemplateInterceptor(
new DefaultEnhancedPluginRunner(Arrays.asList(plugin), new MockRegistration(), null));
RestTemplate template = new RestTemplate();
template.setInterceptors(Arrays.asList(interceptor));
return template;
return new RestTemplate();
}
@RequestMapping("/test")
public String test() {
return MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "b");

@ -1,92 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker;
import java.util.function.Function;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisResultToErrorCode;
import com.tencent.cloud.polaris.circuitbreaker.reactor.PolarisCircuitBreakerReactorTransformer;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.circuitbreak.api.InvokeHandler;
import com.tencent.polaris.circuitbreak.api.pojo.FunctionalDecoratorRequest;
import com.tencent.polaris.circuitbreak.api.pojo.InvokeContext;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
/**
* ReactivePolarisCircuitBreaker.
*
* @author seanyu 2023-02-27
*/
public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker {
private final InvokeHandler invokeHandler;
private final ConsumerAPI consumerAPI;
private final PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf;
public ReactivePolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf,
ConsumerAPI consumerAPI,
CircuitBreakAPI circuitBreakAPI) {
InvokeContext.RequestContext requestContext = new FunctionalDecoratorRequest(
new ServiceKey(conf.getNamespace(), conf.getService()), conf.getProtocol(), conf.getMethod(), conf.getPath());
requestContext.setSourceService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService()));
requestContext.setResultToErrorCode(new PolarisResultToErrorCode());
this.consumerAPI = consumerAPI;
this.conf = conf;
this.invokeHandler = circuitBreakAPI.makeInvokeHandler(requestContext);
}
@Override
public <T> Mono<T> run(Mono<T> toRun, Function<Throwable, Mono<T>> fallback) {
Mono<T> toReturn = toRun.transform(new PolarisCircuitBreakerReactorTransformer<>(invokeHandler));
if (fallback != null) {
toReturn = toReturn.onErrorResume(throwable -> {
if (throwable instanceof CallAbortedException) {
PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, (CallAbortedException) throwable);
}
return fallback.apply(throwable);
});
}
return toReturn;
}
@Override
public <T> Flux<T> run(Flux<T> toRun, Function<Throwable, Flux<T>> fallback) {
Flux<T> toReturn = toRun.transform(new PolarisCircuitBreakerReactorTransformer<>(invokeHandler));
if (fallback != null) {
toReturn = toReturn.onErrorResume(throwable -> {
if (throwable instanceof CallAbortedException) {
PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, (CallAbortedException) throwable);
}
return fallback.apply(throwable);
});
}
return toReturn;
}
}

@ -1,100 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerProperties;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.utils.ThreadPoolUtils;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.client.util.NamedThreadFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
/**
* ReactivePolarisCircuitBreakerFactory.
*
* @author seanyu 2023-02-27
*/
public class ReactivePolarisCircuitBreakerFactory extends
ReactiveCircuitBreakerFactory<PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration, PolarisCircuitBreakerConfigBuilder> implements DisposableBean {
private final CircuitBreakAPI circuitBreakAPI;
private final ConsumerAPI consumerAPI;
private final ScheduledExecutorService cleanupService = Executors.newSingleThreadScheduledExecutor(
new NamedThreadFactory("sct-reactive-circuitbreaker-cleanup", true));
private Function<String, PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration> defaultConfiguration =
id -> {
String[] metadata = PolarisCircuitBreakerUtils.resolveCircuitBreakerId(id);
return new PolarisCircuitBreakerConfigBuilder()
.namespace(metadata[0])
.service(metadata[1])
.path(metadata[2])
.protocol(metadata[3])
.method(metadata[4])
.build();
};
public ReactivePolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI,
PolarisCircuitBreakerProperties polarisCircuitBreakerProperties) {
this.circuitBreakAPI = circuitBreakAPI;
this.consumerAPI = consumerAPI;
cleanupService.scheduleWithFixedDelay(
() -> {
getConfigurations().clear();
},
polarisCircuitBreakerProperties.getConfigurationCleanupInterval(),
polarisCircuitBreakerProperties.getConfigurationCleanupInterval(), TimeUnit.MILLISECONDS);
}
@Override
public ReactiveCircuitBreaker create(String id) {
PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = getConfigurations()
.computeIfAbsent(id, defaultConfiguration);
return new ReactivePolarisCircuitBreaker(conf, consumerAPI, circuitBreakAPI);
}
@Override
protected PolarisCircuitBreakerConfigBuilder configBuilder(String id) {
String[] metadata = PolarisCircuitBreakerUtils.resolveCircuitBreakerId(id);
return new PolarisCircuitBreakerConfigBuilder(metadata[0], metadata[1], metadata[2], metadata[3], metadata[4]);
}
@Override
public void configureDefault(
Function<String, PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration> defaultConfiguration) {
this.defaultConfiguration = defaultConfiguration;
}
@Override
public void destroy() {
ThreadPoolUtils.waitAndStopThreadPools(new ExecutorService[] {cleanupService});
}
}

@ -17,7 +17,7 @@
package com.tencent.cloud.polaris.circuitbreaker.common;
import com.tencent.cloud.polaris.circuitbreaker.zuul.PolarisCircuitBreakerPostZuulFilter;
import com.tencent.cloud.polaris.context.CircuitBreakerStatusCodeException;
import com.tencent.polaris.circuitbreak.api.pojo.ResultToErrorCode;
import feign.FeignException;
@ -50,8 +50,8 @@ public class PolarisResultToErrorCode implements ResultToErrorCode {
&& e instanceof WebClientResponseException) {
return ((WebClientResponseException) e).getRawStatusCode();
}
else if (e instanceof PolarisCircuitBreakerPostZuulFilter.CircuitBreakerStatusCodeException) {
return ((PolarisCircuitBreakerPostZuulFilter.CircuitBreakerStatusCodeException) e).getRawStatusCode();
else if (e instanceof CircuitBreakerStatusCodeException) {
return ((CircuitBreakerStatusCodeException) e).getRawStatusCode();
}
return -1;
}

@ -17,21 +17,11 @@
package com.tencent.cloud.polaris.circuitbreaker.config;
import com.tencent.cloud.polaris.circuitbreaker.ReactivePolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.gateway.PolarisCircuitBreakerFilterFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledFilter;
import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -44,23 +34,9 @@ import org.springframework.web.reactive.DispatcherHandler;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@AutoConfigureAfter({ReactivePolarisCircuitBreakerAutoConfiguration.class})
@ConditionalOnClass({DispatcherHandler.class, ReactivePolarisCircuitBreakerAutoConfiguration.class,
ReactiveCircuitBreakerFactory.class, ReactivePolarisCircuitBreakerFactory.class, GatewayAutoConfiguration.class})
@ConditionalOnClass({ DispatcherHandler.class, GatewayAutoConfiguration.class})
public class GatewayPolarisCircuitBreakerAutoConfiguration {
@Bean
@ConditionalOnBean(ReactiveCircuitBreakerFactory.class)
@ConditionalOnEnabledFilter
public PolarisCircuitBreakerFilterFactory polarisCircuitBreakerFilterFactory(
ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory,
ObjectProvider<DispatcherHandler> dispatcherHandler,
@Autowired(required = false) ReactiveDiscoveryClient discoveryClient,
@Autowired(required = false) DiscoveryLocatorProperties properties
) {
return new PolarisCircuitBreakerFilterFactory(reactiveCircuitBreakerFactory, dispatcherHandler, discoveryClient, properties);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledFilter

@ -24,11 +24,9 @@ import java.util.Set;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier;
import com.tencent.cloud.polaris.circuitbreaker.reporter.CircuitBreakerPlugin;
import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.reporter.SuccessCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerRestTemplateBeanPostProcessor;
import com.tencent.cloud.polaris.circuitbreaker.zuul.PolarisCircuitBreakerPostZuulFilter;
import com.tencent.cloud.polaris.circuitbreaker.zuul.PolarisCircuitBreakerZuulFilter;
import com.tencent.cloud.polaris.circuitbreaker.zuul.PolarisZuulFallbackFactory;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
@ -42,10 +40,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
@ -68,13 +64,6 @@ public class PolarisCircuitBreakerAutoConfiguration {
System.setProperty("hystrix.command.default.circuitBreaker.enabled", "false");
}
@Bean
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
public static PolarisCircuitBreakerRestTemplateBeanPostProcessor polarisCircuitBreakerRestTemplateBeanPostProcessor(
ApplicationContext applicationContext) {
return new PolarisCircuitBreakerRestTemplateBeanPostProcessor(applicationContext);
}
@Bean
@ConditionalOnMissingBean(SuccessCircuitBreakerReporter.class)
public SuccessCircuitBreakerReporter successCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
@ -82,6 +71,12 @@ public class PolarisCircuitBreakerAutoConfiguration {
return new SuccessCircuitBreakerReporter(properties, polarisSDKContextManager.getCircuitBreakAPI());
}
@Bean
@ConditionalOnMissingBean(CircuitBreakerPlugin.class)
public CircuitBreakerPlugin circuitBreakerPlugin(CircuitBreakerFactory circuitBreakerFactory) {
return new CircuitBreakerPlugin(circuitBreakerFactory);
}
@Bean
@ConditionalOnMissingBean(ExceptionCircuitBreakerReporter.class)
public ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
@ -116,20 +111,5 @@ public class PolarisCircuitBreakerAutoConfiguration {
public PolarisZuulFallbackFactory polarisZuulFallbackFactory() {
return new PolarisZuulFallbackFactory(zuulFallbackProviders);
}
@Bean
public PolarisCircuitBreakerZuulFilter polarisCircuitBreakerZuulFilter(
CircuitBreakerFactory circuitBreakerFactory,
PolarisZuulFallbackFactory polarisZuulFallbackFactory,
Environment environment) {
return new PolarisCircuitBreakerZuulFilter(circuitBreakerFactory, polarisZuulFallbackFactory, environment);
}
@Bean
public PolarisCircuitBreakerPostZuulFilter polarisCircuitBreakerPostZuulFilter(
PolarisZuulFallbackFactory polarisZuulFallbackFactory,
Environment environment) {
return new PolarisCircuitBreakerPostZuulFilter(polarisZuulFallbackFactory, environment);
}
}
}

@ -30,7 +30,6 @@ import org.springframework.context.annotation.Import;
@ConditionalOnProperty("spring.cloud.polaris.enabled")
@Import({
PolarisCircuitBreakerAutoConfiguration.class,
ReactivePolarisCircuitBreakerAutoConfiguration.class,
PolarisCircuitBreakerFeignClientAutoConfiguration.class,
GatewayPolarisCircuitBreakerAutoConfiguration.class
})

@ -1,86 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker.config;
import java.util.ArrayList;
import java.util.List;
import com.tencent.cloud.polaris.circuitbreaker.ReactivePolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier;
import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.reporter.SuccessCircuitBreakerReporter;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* AutoConfiguration for ReactivePolarisCircuitBreaker.
*
* @author seanyu 2023-02-27
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = {"reactor.core.publisher.Mono", "reactor.core.publisher.Flux"})
@ConditionalOnPolarisCircuitBreakerEnabled
@EnableConfigurationProperties(PolarisCircuitBreakerProperties.class)
@AutoConfigureAfter(RpcEnhancementAutoConfiguration.class)
public class ReactivePolarisCircuitBreakerAutoConfiguration {
@Autowired(required = false)
private List<Customizer<ReactivePolarisCircuitBreakerFactory>> customizers = new ArrayList<>();
@Bean
@ConditionalOnMissingBean(SuccessCircuitBreakerReporter.class)
public SuccessCircuitBreakerReporter successCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
PolarisSDKContextManager polarisSDKContextManager) {
return new SuccessCircuitBreakerReporter(properties, polarisSDKContextManager.getCircuitBreakAPI());
}
@Bean
@ConditionalOnMissingBean(ExceptionCircuitBreakerReporter.class)
public ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
PolarisSDKContextManager polarisSDKContextManager) {
return new ExceptionCircuitBreakerReporter(properties, polarisSDKContextManager.getCircuitBreakAPI());
}
@Bean
@ConditionalOnMissingBean(ReactiveCircuitBreakerFactory.class)
public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(PolarisSDKContextManager polarisSDKContextManager,
PolarisCircuitBreakerProperties polarisCircuitBreakerProperties) {
ReactivePolarisCircuitBreakerFactory factory = new ReactivePolarisCircuitBreakerFactory(
polarisSDKContextManager.getCircuitBreakAPI(), polarisSDKContextManager.getConsumerAPI(), polarisCircuitBreakerProperties);
customizers.forEach(customizer -> customizer.customize(factory));
return factory;
}
@Bean
@ConditionalOnMissingBean(CircuitBreakerConfigModifier.class)
public CircuitBreakerConfigModifier reactiveCircuitBreakerConfigModifier(RpcEnhancementReporterProperties properties) {
return new CircuitBreakerConfigModifier(properties);
}
}

@ -23,7 +23,6 @@ import feign.Feign;
import feign.Target;
import feign.codec.Decoder;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignCircuitBreaker;
import org.springframework.util.ReflectionUtils;
@ -51,28 +50,9 @@ public final class PolarisFeignCircuitBreaker {
*/
public static final class Builder extends Feign.Builder {
private CircuitBreakerFactory circuitBreakerFactory;
private String feignClientName;
private PolarisCircuitBreakerNameResolver circuitBreakerNameResolver;
public Builder() {
}
public Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) {
this.circuitBreakerFactory = circuitBreakerFactory;
return this;
}
public Builder feignClientName(String feignClientName) {
this.feignClientName = feignClientName;
return this;
}
public Builder circuitBreakerNameResolver(PolarisCircuitBreakerNameResolver circuitBreakerNameResolver) {
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
return this;
}
public <T> T target(Target<T> target, T fallback) {
return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target);
}
@ -91,7 +71,7 @@ public final class PolarisFeignCircuitBreaker {
field.setAccessible(true);
Decoder decoder = (Decoder) ReflectionUtils.getField(field, this);
this.invocationHandlerFactory((target, dispatch) -> new PolarisFeignCircuitBreakerInvocationHandler(
circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory, circuitBreakerNameResolver, decoder));
target, dispatch, nullableFallbackFactory, decoder));
return this.build();
}

@ -23,20 +23,14 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import com.tencent.cloud.polaris.circuitbreaker.exception.FallbackWrapperException;
import feign.InvocationHandlerFactory;
import feign.Target;
import feign.codec.Decoder;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import static feign.Util.checkNotNull;
@ -47,10 +41,6 @@ import static feign.Util.checkNotNull;
*/
public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHandler {
private final CircuitBreakerFactory factory;
private final String feignClientName;
private final Target<?> target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
@ -59,41 +49,19 @@ public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHa
private final Map<Method, Method> fallbackMethodMap;
private final PolarisCircuitBreakerNameResolver circuitBreakerNameResolver;
private final Decoder decoder;
public PolarisFeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target<?> target,
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, FallbackFactory<?> nullableFallbackFactory,
PolarisCircuitBreakerNameResolver circuitBreakerNameResolver, Decoder decoder) {
this.factory = factory;
this.feignClientName = feignClientName;
public PolarisFeignCircuitBreakerInvocationHandler(Target<?> target,
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
FallbackFactory<?> nullableFallbackFactory, Decoder decoder) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackMethodMap = toFallbackMethod(dispatch);
this.nullableFallbackFactory = nullableFallbackFactory;
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
this.decoder = decoder;
}
/**
* If the method param of {@link InvocationHandler#invoke(Object, Method, Object[])}
* is not accessible, i.e in a package-private interface, the fallback call will cause
* of access restrictions. But methods in dispatch are copied methods. So setting
* access to dispatch method doesn't take effect to the method in
* InvocationHandler.invoke. Use map to store a copy of method to invoke the fallback
* to bypass this and reducing the count of reflection calls.
* @return cached methods map for fallback invoking
*/
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
// early exit if the invoked method is from java.lang.Object
@ -113,18 +81,12 @@ public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHa
else if ("toString".equals(method.getName())) {
return toString();
}
// disable feign.hystrix
if (circuitBreakerNameResolver == null) {
try {
return this.dispatch.get(method).invoke(args);
}
String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);
CircuitBreaker circuitBreaker = factory.create(circuitName);
Supplier<Object> supplier = asSupplier(method, args);
Function<Throwable, Object> fallbackFunction;
if (this.nullableFallbackFactory != null) {
fallbackFunction = throwable -> {
Object fallback = this.nullableFallbackFactory.create(throwable);
catch (Exception e) {
if (this.nullableFallbackFactory != null) {
Object fallback = this.nullableFallbackFactory.create(e);
try {
return this.fallbackMethodMap.get(method).invoke(fallback, args);
}
@ -132,21 +94,12 @@ public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHa
unwrapAndRethrow(exception);
}
return null;
};
}
else {
fallbackFunction = throwable -> {
}
else {
PolarisCircuitBreakerFallbackFactory.DefaultFallback fallback =
(PolarisCircuitBreakerFallbackFactory.DefaultFallback) new PolarisCircuitBreakerFallbackFactory(this.decoder).create(throwable);
(PolarisCircuitBreakerFallbackFactory.DefaultFallback) new PolarisCircuitBreakerFallbackFactory(this.decoder).create(e);
return fallback.fallback(method);
};
}
try {
return circuitBreaker.run(supplier, fallbackFunction);
}
catch (FallbackWrapperException e) {
// unwrap And Rethrow
throw e.getCause();
}
}
}
@ -163,29 +116,22 @@ public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHa
}
}
private Supplier<Object> asSupplier(final Method method, final Object[] args) {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
final Thread caller = Thread.currentThread();
return () -> {
boolean isAsync = caller != Thread.currentThread();
try {
if (isAsync) {
RequestContextHolder.setRequestAttributes(requestAttributes);
}
return dispatch.get(method).invoke(args);
}
catch (RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
finally {
if (isAsync) {
RequestContextHolder.resetRequestAttributes();
}
}
};
/**
* If the method param of {@link InvocationHandler#invoke(Object, Method, Object[])}
* is not accessible, i.e in a package-private interface, the fallback call will cause
* of access restrictions. But methods in dispatch are copied methods. So setting
* access to dispatch method doesn't take effect to the method in
* InvocationHandler.invoke. Use map to store a copy of method to invoke the fallback
* to bypass this and reducing the count of reflection calls.
* @return cached methods map for fallback invoking
*/
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
@Override

@ -1,280 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker.gateway;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.HttpStatusHolder;
import org.springframework.cloud.gateway.support.ServiceUnavailableException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import static java.util.Optional.ofNullable;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.reset;
/**
* PolarisCircuitBreakerFilterFactory.
* mostly copy from SpringCloudCircuitBreakerFilterFactory, but create ReactiveCircuitBreaker per request to build method level CircuitBreaker.
*
* @author seanyu 2023-02-27
*/
public class PolarisCircuitBreakerFilterFactory extends SpringCloudCircuitBreakerFilterFactory {
private final ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory;
private final ObjectProvider<DispatcherHandler> dispatcherHandlerProvider;
private String routeIdPrefix;
// do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
private volatile DispatcherHandler dispatcherHandler;
public PolarisCircuitBreakerFilterFactory(
ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory,
ObjectProvider<DispatcherHandler> dispatcherHandlerProvider,
ReactiveDiscoveryClient discoveryClient,
DiscoveryLocatorProperties properties
) {
super(reactiveCircuitBreakerFactory, dispatcherHandlerProvider);
this.reactiveCircuitBreakerFactory = reactiveCircuitBreakerFactory;
this.dispatcherHandlerProvider = dispatcherHandlerProvider;
if (discoveryClient != null && properties != null) {
if (StringUtils.hasText(properties.getRouteIdPrefix())) {
routeIdPrefix = properties.getRouteIdPrefix();
}
else {
routeIdPrefix = discoveryClient.getClass().getSimpleName() + "_";
}
}
}
private void addExceptionDetails(Throwable t, ServerWebExchange exchange) {
ofNullable(t).ifPresent(
exception -> exchange.getAttributes().put(CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR, exception));
}
private DispatcherHandler getDispatcherHandler() {
if (dispatcherHandler == null) {
dispatcherHandler = dispatcherHandlerProvider.getIfAvailable();
}
return dispatcherHandler;
}
private String getCircuitBreakerId(Config config) {
if (!StringUtils.hasText(config.getName()) && StringUtils.hasText(config.getRouteId())) {
if (routeIdPrefix != null && config.getRouteId().startsWith(routeIdPrefix)) {
return config.getRouteId().replace(routeIdPrefix, "");
}
return config.getRouteId();
}
return config.getName();
}
private boolean isNumeric(String statusString) {
try {
Integer.parseInt(statusString);
return true;
}
catch (NumberFormatException e) {
return false;
}
}
private List<HttpStatus> getSeriesStatus(String series) {
if (!Arrays.asList("1**", "2**", "3**", "4**", "5**").contains(series)) {
throw new InvalidPropertyException(Config.class, "statusCodes", "polaris circuit breaker status code can only be a numeric http status, or a http series pattern, e.g. [\"1**\",\"2**\",\"3**\",\"4**\",\"5**\"]");
}
HttpStatus[] allHttpStatus = HttpStatus.values();
if (series.startsWith("1")) {
return Arrays.stream(allHttpStatus).filter(HttpStatus::is1xxInformational).collect(Collectors.toList());
}
else if (series.startsWith("2")) {
return Arrays.stream(allHttpStatus).filter(HttpStatus::is2xxSuccessful).collect(Collectors.toList());
}
else if (series.startsWith("3")) {
return Arrays.stream(allHttpStatus).filter(HttpStatus::is3xxRedirection).collect(Collectors.toList());
}
else if (series.startsWith("4")) {
return Arrays.stream(allHttpStatus).filter(HttpStatus::is4xxClientError).collect(Collectors.toList());
}
else if (series.startsWith("5")) {
return Arrays.stream(allHttpStatus).filter(HttpStatus::is5xxServerError).collect(Collectors.toList());
}
return Arrays.asList(allHttpStatus);
}
private Set<HttpStatus> getDefaultStatus() {
return Arrays.stream(HttpStatus.values())
.filter(HttpStatus::is5xxServerError)
.collect(Collectors.toSet());
}
@Override
public GatewayFilter apply(Config config) {
Set<HttpStatus> statuses = config.getStatusCodes().stream()
.flatMap(statusCode -> {
List<HttpStatus> httpStatuses = new ArrayList<>();
if (isNumeric(statusCode)) {
httpStatuses.add(HttpStatusHolder.parse(statusCode).getHttpStatus());
}
else {
httpStatuses.addAll(getSeriesStatus(statusCode));
}
return httpStatuses.stream();
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (CollectionUtils.isEmpty(statuses)) {
statuses.addAll(getDefaultStatus());
}
String circuitBreakerId = getCircuitBreakerId(config);
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
String serviceName = circuitBreakerId;
if (route != null) {
serviceName = route.getUri().getHost();
}
String path = exchange.getRequest().getPath().value();
String method = exchange.getRequest().getMethod() == null ?
"GET" : exchange.getRequest().getMethod().name();
ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(MetadataContext.LOCAL_NAMESPACE + "#" + serviceName + "#" + path + "#http#" + method);
return cb.run(
chain.filter(exchange).doOnSuccess(v -> {
// throw CircuitBreakerStatusCodeException by default for all need checking status
// so polaris can report right error status
Set<HttpStatus> statusNeedToCheck = new HashSet<>();
statusNeedToCheck.addAll(statuses);
statusNeedToCheck.addAll(getDefaultStatus());
HttpStatus status = exchange.getResponse().getStatusCode();
if (statusNeedToCheck.contains(status)) {
throw new CircuitBreakerStatusCodeException(status);
}
}),
t -> {
// pre-check CircuitBreakerStatusCodeException's status matches input status
if (t instanceof CircuitBreakerStatusCodeException) {
HttpStatus status = ((CircuitBreakerStatusCodeException) t).getStatusCode();
// no need to fallback
if (!statuses.contains(status)) {
return Mono.error(t);
}
}
// do fallback
if (config.getFallbackUri() == null) {
// polaris checking
if (t instanceof CallAbortedException) {
CircuitBreakerStatus.FallbackInfo fallbackInfo = ((CallAbortedException) t).getFallbackInfo();
if (fallbackInfo != null) {
ServerHttpResponse response = exchange.getResponse();
response.setRawStatusCode(fallbackInfo.getCode());
if (fallbackInfo.getHeaders() != null) {
fallbackInfo.getHeaders()
.forEach((k, v) -> response.getHeaders().add(k, v));
}
DataBuffer bodyBuffer = null;
if (fallbackInfo.getBody() != null) {
byte[] bytes = fallbackInfo.getBody().getBytes(StandardCharsets.UTF_8);
bodyBuffer = response.bufferFactory().wrap(bytes);
}
return bodyBuffer != null ? response.writeWith(Flux.just(bodyBuffer)) : response.setComplete();
}
}
return Mono.error(t);
}
exchange.getResponse().setStatusCode(null);
reset(exchange);
// TODO: copied from RouteToRequestUrlFilter
URI uri = exchange.getRequest().getURI();
// TODO: assume always?
boolean encoded = containsEncodedParts(uri);
URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null)
.uri(config.getFallbackUri()).scheme(null).build(encoded).toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
addExceptionDetails(t, exchange);
// Reset the exchange
reset(exchange);
ServerHttpRequest request = exchange.getRequest().mutate().uri(requestUrl).build();
return getDispatcherHandler().handle(exchange.mutate().request(request).build());
})
.onErrorResume(t -> handleErrorWithoutFallback(t));
}
@Override
public String toString() {
return filterToStringCreator(PolarisCircuitBreakerFilterFactory.this)
.append("name", config.getName()).append("fallback", config.getFallbackUri()).toString();
}
};
}
@Override
protected Mono<Void> handleErrorWithoutFallback(Throwable t) {
if (t instanceof java.util.concurrent.TimeoutException) {
return Mono.error(new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, t.getMessage(), t));
}
if (t instanceof CallAbortedException) {
return Mono.error(new ServiceUnavailableException());
}
if (t instanceof CircuitBreakerStatusCodeException) {
return Mono.empty();
}
return Mono.error(t);
}
}

@ -0,0 +1,100 @@
package com.tencent.cloud.polaris.circuitbreaker.reporter;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreaker;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerHttpResponse;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import com.tencent.polaris.metadata.core.MetadataType;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import static com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant.ClientPluginOrder.CIRCUIT_BREAKER_REPORTER_PLUGIN_ORDER;
/**
* CircuitBreakerPlugin, do circuit breaker in enhance plugin and record info into metadata.
*
* @author Shedfree Wu
*/
public class CircuitBreakerPlugin implements EnhancedPlugin {
private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class);
private CircuitBreakerFactory circuitBreakerFactory;
public CircuitBreakerPlugin(CircuitBreakerFactory circuitBreakerFactory) {
this.circuitBreakerFactory = circuitBreakerFactory;
}
@Override
public String getName() {
return CircuitBreakerPlugin.class.getName();
}
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.PRE;
}
@Override
public void run(EnhancedPluginContext context) throws Throwable {
EnhancedRequestContext request = context.getRequest();
EnhancedResponseContext response = context.getResponse();
String governanceNamespace = MetadataContextHolder.get().getDisposableMetadata().get(ContextConstant.POLARIS_GOVERNANCE_TARGET_NAMESPACE);
if (StringUtils.isEmpty(governanceNamespace)) {
governanceNamespace = MetadataContext.LOCAL_NAMESPACE;
}
String host = request.getServiceUrl() != null ? request.getServiceUrl().getHost() : request.getUrl().getHost();
String path = request.getServiceUrl() != null ? request.getServiceUrl().getPath() : request.getUrl().getPath();
String httpMethod = request.getHttpMethod().name();
CircuitBreaker circuitBreaker = circuitBreakerFactory.create(governanceNamespace + "#" + host + "#" + path + "#http#" + httpMethod);
if (circuitBreaker instanceof PolarisCircuitBreaker) {
PolarisCircuitBreaker polarisCircuitBreaker = (PolarisCircuitBreaker) circuitBreaker;
putMetadataObjectValue(ContextConstant.CircuitBreaker.POLARIS_CIRCUIT_BREAKER, polarisCircuitBreaker);
putMetadataObjectValue(ContextConstant.CircuitBreaker.CIRCUIT_BREAKER_START_TIME, System.currentTimeMillis());
try {
polarisCircuitBreaker.acquirePermission();
}
catch (CallAbortedException e) {
LOG.debug("[CircuitBreakerPlugin] request is aborted. request service url=[{}]", request.getServiceUrl());
if (e.getFallbackInfo() != null) {
Object fallbackResponse = new PolarisCircuitBreakerHttpResponse(e.getFallbackInfo());
putMetadataObjectValue(ContextConstant.CircuitBreaker.CIRCUIT_BREAKER_FALLBACK_HTTP_RESPONSE, fallbackResponse);
}
throw e;
}
}
}
@Override
public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
LOG.error("SuccessCircuitBreakerReporter runs failed. context=[{}].",
context, throwable);
}
@Override
public int getOrder() {
return CIRCUIT_BREAKER_REPORTER_PLUGIN_ORDER;
}
private void putMetadataObjectValue(String key, Object value) {
MetadataContextHolder.get().getMetadataContainer(MetadataType.APPLICATION, true).
putMetadataObjectValue(key, value);
}
}

@ -18,7 +18,11 @@
package com.tencent.cloud.polaris.circuitbreaker.reporter;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreaker;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
@ -27,6 +31,9 @@ import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.PolarisEnhancedPluginUtils;
import com.tencent.polaris.api.plugin.circuitbreaker.ResourceStat;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.circuitbreak.api.pojo.InvokeContext;
import com.tencent.polaris.metadata.core.MetadataObjectValue;
import com.tencent.polaris.metadata.core.MetadataType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -74,6 +81,10 @@ public class ExceptionCircuitBreakerReporter implements EnhancedPlugin {
ServiceInstance serviceInstance = Optional.ofNullable(context.getTargetServiceInstance())
.orElse(new DefaultServiceInstance());
if (LOG.isDebugEnabled()) {
String governanceNamespace = MetadataContextHolder.get().getDisposableMetadata().get(ContextConstant.POLARIS_GOVERNANCE_TARGET_NAMESPACE);
LOG.debug("governanceNamespace={}, serviceInstance:{}", governanceNamespace, serviceInstance);
}
ResourceStat resourceStat = PolarisEnhancedPluginUtils.createInstanceResourceStat(
serviceInstance.getServiceId(),
serviceInstance.getHost(),
@ -90,6 +101,34 @@ public class ExceptionCircuitBreakerReporter implements EnhancedPlugin {
circuitBreakAPI.report(resourceStat);
MetadataObjectValue<PolarisCircuitBreaker> circuitBreakerObject = MetadataContextHolder.get().
getMetadataContainer(MetadataType.APPLICATION, true).
getMetadataValue(ContextConstant.CircuitBreaker.POLARIS_CIRCUIT_BREAKER);
MetadataObjectValue<Long> startTimeMilliObject = MetadataContextHolder.get().
getMetadataContainer(MetadataType.APPLICATION, true).
getMetadataValue(ContextConstant.CircuitBreaker.CIRCUIT_BREAKER_START_TIME);
boolean existCircuitBreaker = existMetadataValue(circuitBreakerObject);
boolean existStartTime = existMetadataValue(startTimeMilliObject);
if (existCircuitBreaker && existStartTime) {
PolarisCircuitBreaker polarisCircuitBreaker = circuitBreakerObject.getObjectValue().get();
Long startTimeMillis = startTimeMilliObject.getObjectValue().get();
long delay = System.currentTimeMillis() - startTimeMillis;
InvokeContext.ResponseContext responseContext = new InvokeContext.ResponseContext();
responseContext.setDuration(delay);
responseContext.setDurationUnit(TimeUnit.MILLISECONDS);
responseContext.setError(context.getThrowable());
if (responseContext.getError() == null) {
polarisCircuitBreaker.onSuccess(responseContext);
}
else {
polarisCircuitBreaker.onError(responseContext);
}
}
}
@Override
@ -102,4 +141,10 @@ public class ExceptionCircuitBreakerReporter implements EnhancedPlugin {
public int getOrder() {
return CIRCUIT_BREAKER_REPORTER_PLUGIN_ORDER;
}
private static boolean existMetadataValue(MetadataObjectValue<?> metadataObjectValue) {
return Optional.ofNullable(metadataObjectValue).map(MetadataObjectValue::getObjectValue).
map(Optional::isPresent).orElse(false);
}
}

@ -18,7 +18,12 @@
package com.tencent.cloud.polaris.circuitbreaker.reporter;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreaker;
import com.tencent.cloud.polaris.context.CircuitBreakerStatusCodeException;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
@ -29,11 +34,15 @@ import com.tencent.cloud.rpc.enhancement.plugin.PolarisEnhancedPluginUtils;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter;
import com.tencent.polaris.api.plugin.circuitbreaker.ResourceStat;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.circuitbreak.api.pojo.InvokeContext;
import com.tencent.polaris.metadata.core.MetadataObjectValue;
import com.tencent.polaris.metadata.core.MetadataType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.http.HttpStatus;
import static com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant.ClientPluginOrder.CIRCUIT_BREAKER_REPORTER_PLUGIN_ORDER;
@ -75,7 +84,6 @@ public class SuccessCircuitBreakerReporter implements EnhancedPlugin {
EnhancedResponseContext response = context.getResponse();
ServiceInstance serviceInstance = Optional.ofNullable(context.getTargetServiceInstance())
.orElse(new DefaultServiceInstance());
ResourceStat resourceStat = PolarisEnhancedPluginUtils.createInstanceResourceStat(
serviceInstance.getServiceId(),
serviceInstance.getHost(),
@ -91,6 +99,37 @@ public class SuccessCircuitBreakerReporter implements EnhancedPlugin {
.getPath(), response.getHttpStatus(), context.getDelay());
circuitBreakAPI.report(resourceStat);
MetadataObjectValue<PolarisCircuitBreaker> circuitBreakerObject = MetadataContextHolder.get().
getMetadataContainer(MetadataType.APPLICATION, true).
getMetadataValue(ContextConstant.CircuitBreaker.POLARIS_CIRCUIT_BREAKER);
MetadataObjectValue<Long> startTimeMilliObject = MetadataContextHolder.get().
getMetadataContainer(MetadataType.APPLICATION, true).
getMetadataValue(ContextConstant.CircuitBreaker.CIRCUIT_BREAKER_START_TIME);
boolean existCircuitBreaker = existMetadataValue(circuitBreakerObject);
boolean existStartTime = existMetadataValue(startTimeMilliObject);
if (existCircuitBreaker && existStartTime) {
PolarisCircuitBreaker polarisCircuitBreaker = circuitBreakerObject.getObjectValue().get();
Long startTimeMillis = startTimeMilliObject.getObjectValue().get();
long delay = System.currentTimeMillis() - startTimeMillis;
InvokeContext.ResponseContext responseContext = new InvokeContext.ResponseContext();
responseContext.setDuration(delay);
responseContext.setDurationUnit(TimeUnit.MILLISECONDS);
HttpStatus status = HttpStatus.resolve(response.getHttpStatus());
if (status != null && (status.is5xxServerError() || status.is4xxClientError())) {
Throwable throwable = new CircuitBreakerStatusCodeException(status);
responseContext.setError(throwable);
}
if (responseContext.getError() == null) {
polarisCircuitBreaker.onSuccess(responseContext);
}
else {
polarisCircuitBreaker.onError(responseContext);
}
}
}
@Override
@ -103,4 +142,9 @@ public class SuccessCircuitBreakerReporter implements EnhancedPlugin {
public int getOrder() {
return CIRCUIT_BREAKER_REPORTER_PLUGIN_ORDER;
}
private static boolean existMetadataValue(MetadataObjectValue<?> metadataObjectValue) {
return Optional.ofNullable(metadataObjectValue).map(MetadataObjectValue::getObjectValue).
map(Optional::isPresent).orElse(false);
}
}

@ -1,125 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker.resttemplate;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
/**
* PolarisCircuitBreakerRestTemplateBeanPostProcessor.
*
* @author sean yu
*/
public class PolarisCircuitBreakerRestTemplateBeanPostProcessor implements MergedBeanDefinitionPostProcessor {
private final ApplicationContext applicationContext;
private final ConcurrentHashMap<String, PolarisCircuitBreaker> cache = new ConcurrentHashMap<>();
public PolarisCircuitBreakerRestTemplateBeanPostProcessor(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
private void checkPolarisCircuitBreakerRestTemplate(PolarisCircuitBreaker polarisCircuitBreaker) {
if (
StringUtils.hasText(polarisCircuitBreaker.fallback()) &&
!PolarisCircuitBreakerFallback.class.toGenericString()
.equals(polarisCircuitBreaker.fallbackClass().toGenericString())
) {
throw new IllegalArgumentException("PolarisCircuitBreaker's fallback and fallbackClass could not set at sametime !");
}
}
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
if (checkAnnotated(beanDefinition, beanType, beanName)) {
PolarisCircuitBreaker polarisCircuitBreaker;
if (beanDefinition.getSource() instanceof StandardMethodMetadata) {
polarisCircuitBreaker = ((StandardMethodMetadata) beanDefinition.getSource()).getIntrospectedMethod()
.getAnnotation(PolarisCircuitBreaker.class);
}
else {
polarisCircuitBreaker = beanDefinition.getResolvedFactoryMethod()
.getAnnotation(PolarisCircuitBreaker.class);
}
checkPolarisCircuitBreakerRestTemplate(polarisCircuitBreaker);
cache.put(beanName, polarisCircuitBreaker);
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (cache.containsKey(beanName)) {
// add interceptor for each RestTemplate with @PolarisCircuitBreaker annotation
StringBuilder interceptorBeanNamePrefix = new StringBuilder();
PolarisCircuitBreaker polarisCircuitBreaker = cache.get(beanName);
interceptorBeanNamePrefix
.append(StringUtils.uncapitalize(
PolarisCircuitBreaker.class.getSimpleName()))
.append("_")
.append(polarisCircuitBreaker.fallback())
.append("_")
.append(polarisCircuitBreaker.fallbackClass().getSimpleName());
RestTemplate restTemplate = (RestTemplate) bean;
String interceptorBeanName = interceptorBeanNamePrefix + "@" + bean;
CircuitBreakerFactory circuitBreakerFactory = this.applicationContext.getBean(CircuitBreakerFactory.class);
registerBean(interceptorBeanName, polarisCircuitBreaker, applicationContext, circuitBreakerFactory, restTemplate);
PolarisCircuitBreakerRestTemplateInterceptor polarisCircuitBreakerRestTemplateInterceptor = applicationContext
.getBean(interceptorBeanName, PolarisCircuitBreakerRestTemplateInterceptor.class);
restTemplate.getInterceptors().add(0, polarisCircuitBreakerRestTemplateInterceptor);
}
return bean;
}
private boolean checkAnnotated(RootBeanDefinition beanDefinition,
Class<?> beanType, String beanName) {
return beanName != null && beanType == RestTemplate.class
&& beanDefinition.getSource() instanceof MethodMetadata
&& ((MethodMetadata) beanDefinition.getSource())
.isAnnotated(PolarisCircuitBreaker.class.getName());
}
private void registerBean(String interceptorBeanName, PolarisCircuitBreaker polarisCircuitBreaker,
ApplicationContext applicationContext, CircuitBreakerFactory circuitBreakerFactory, RestTemplate restTemplate) {
// register PolarisCircuitBreakerRestTemplateInterceptor bean
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
.getAutowireCapableBeanFactory();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(PolarisCircuitBreakerRestTemplateInterceptor.class);
beanDefinitionBuilder.addConstructorArgValue(polarisCircuitBreaker);
beanDefinitionBuilder.addConstructorArgValue(applicationContext);
beanDefinitionBuilder.addConstructorArgValue(circuitBreakerFactory);
beanDefinitionBuilder.addConstructorArgValue(restTemplate);
BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder
.getRawBeanDefinition();
beanFactory.registerBeanDefinition(interceptorBeanName,
interceptorBeanDefinition);
}
}

@ -1,120 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker.resttemplate;
import java.io.IOException;
import java.lang.reflect.Method;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.circuitbreaker.exception.FallbackWrapperException;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
/**
* PolarisCircuitBreakerRestTemplateInterceptor.
*
* @author sean yu
*/
public class PolarisCircuitBreakerRestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final PolarisCircuitBreaker polarisCircuitBreaker;
private final ApplicationContext applicationContext;
private final CircuitBreakerFactory circuitBreakerFactory;
private final RestTemplate restTemplate;
public PolarisCircuitBreakerRestTemplateInterceptor(
PolarisCircuitBreaker polarisCircuitBreaker,
ApplicationContext applicationContext,
CircuitBreakerFactory circuitBreakerFactory,
RestTemplate restTemplate
) {
this.polarisCircuitBreaker = polarisCircuitBreaker;
this.applicationContext = applicationContext;
this.circuitBreakerFactory = circuitBreakerFactory;
this.restTemplate = restTemplate;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
try {
String httpMethod = "GET";
if (request.getMethod() != null) {
httpMethod = request.getMethod().name();
}
return circuitBreakerFactory.create(MetadataContext.LOCAL_NAMESPACE + "#" + request.getURI()
.getHost() + "#" + request.getURI().getPath() + "#http#" + httpMethod).run(
() -> {
try {
ClientHttpResponse response = execution.execute(request, body);
ResponseErrorHandler errorHandler = restTemplate.getErrorHandler();
if (errorHandler.hasError(response)) {
errorHandler.handleError(request.getURI(), request.getMethod(), response);
}
return response;
}
catch (IOException e) {
throw new IllegalStateException(e);
}
},
t -> {
if (StringUtils.hasText(polarisCircuitBreaker.fallback())) {
CircuitBreakerStatus.FallbackInfo fallbackInfo = new CircuitBreakerStatus.FallbackInfo(200, null, polarisCircuitBreaker.fallback());
return new PolarisCircuitBreakerHttpResponse(fallbackInfo);
}
if (!PolarisCircuitBreakerFallback.class.toGenericString()
.equals(polarisCircuitBreaker.fallbackClass().toGenericString())) {
Method method = ReflectionUtils.findMethod(PolarisCircuitBreakerFallback.class, "fallback");
PolarisCircuitBreakerFallback polarisCircuitBreakerFallback = applicationContext.getBean(polarisCircuitBreaker.fallbackClass());
return (PolarisCircuitBreakerHttpResponse) ReflectionUtils.invokeMethod(method, polarisCircuitBreakerFallback);
}
if (t instanceof CallAbortedException) {
CircuitBreakerStatus.FallbackInfo fallbackInfo = ((CallAbortedException) t).getFallbackInfo();
if (fallbackInfo != null) {
return new PolarisCircuitBreakerHttpResponse(fallbackInfo);
}
}
throw new FallbackWrapperException(t);
}
);
}
catch (FallbackWrapperException e) {
// unwrap And Rethrow
Throwable underlyingException = e.getCause();
if (underlyingException instanceof RuntimeException) {
throw (RuntimeException) underlyingException;
}
throw new IllegalStateException(underlyingException);
}
}
}

@ -1,152 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker.zuul;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletResponse;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.util.ZuulFilterUtils;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreaker;
import com.tencent.polaris.circuitbreak.api.pojo.InvokeContext;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpStatusCodeException;
import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_CIRCUIT_BREAKER;
import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_PRE_ROUTE_TIME;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE;
/**
* Polaris circuit breaker post-processing. Including reporting.
*
* @author Haotian Zhang
*/
public class PolarisCircuitBreakerPostZuulFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisCircuitBreakerPostZuulFilter.class);
private final PolarisZuulFallbackFactory polarisZuulFallbackFactory;
private final Environment environment;
public PolarisCircuitBreakerPostZuulFilter(PolarisZuulFallbackFactory polarisZuulFallbackFactory,
Environment environment) {
this.polarisZuulFallbackFactory = polarisZuulFallbackFactory;
this.environment = environment;
}
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return OrderConstant.Client.Zuul.CIRCUIT_BREAKER_POST_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
String enabled = environment.getProperty("spring.cloud.polaris.circuitbreaker.enabled");
return StringUtils.isEmpty(enabled) || enabled.equals("true");
}
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse response = context.getResponse();
HttpStatus status = HttpStatus.resolve(response.getStatus());
Object polarisCircuitBreakerObject = context.get(POLARIS_CIRCUIT_BREAKER);
Object startTimeMilliObject = context.get(POLARIS_PRE_ROUTE_TIME);
if (polarisCircuitBreakerObject != null && polarisCircuitBreakerObject instanceof PolarisCircuitBreaker
&& startTimeMilliObject != null && startTimeMilliObject instanceof Long) {
PolarisCircuitBreaker polarisCircuitBreaker = (PolarisCircuitBreaker) polarisCircuitBreakerObject;
Long startTimeMilli = (Long) startTimeMilliObject;
long delay = System.currentTimeMillis() - startTimeMilli;
InvokeContext.ResponseContext responseContext = new InvokeContext.ResponseContext();
responseContext.setDuration(delay);
responseContext.setDurationUnit(TimeUnit.MILLISECONDS);
if (status != null && status.is5xxServerError()) {
Throwable throwable = new CircuitBreakerStatusCodeException(status);
responseContext.setError(throwable);
// fallback if FallbackProvider is implemented.
String serviceId = ZuulFilterUtils.getServiceId(context);
FallbackProvider fallbackProvider = polarisZuulFallbackFactory.getFallbackProvider(serviceId);
if (fallbackProvider != null) {
ClientHttpResponse clientHttpResponse = fallbackProvider.fallbackResponse(serviceId, throwable);
try {
// set status code
context.setResponseStatusCode(clientHttpResponse.getRawStatusCode());
// set headers
HttpHeaders headers = clientHttpResponse.getHeaders();
for (String key : headers.keySet()) {
List<String> values = headers.get(key);
if (!CollectionUtils.isEmpty(values)) {
for (String value : values) {
context.addZuulResponseHeader(key, value);
}
}
}
// set body
context.getResponse().setCharacterEncoding("UTF-8");
context.setResponseBody(IOUtils.toString(clientHttpResponse.getBody(), StandardCharsets.UTF_8));
}
catch (Exception e) {
LOGGER.error("error filter exception", e);
}
}
}
if (responseContext.getError() == null) {
polarisCircuitBreaker.onSuccess(responseContext);
}
else {
polarisCircuitBreaker.onError(responseContext);
}
}
return null;
}
public class CircuitBreakerStatusCodeException extends HttpStatusCodeException {
public CircuitBreakerStatusCodeException(HttpStatus statusCode) {
super(statusCode);
}
}
}

@ -1,156 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker.zuul;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ZuulFilterUtils;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreaker;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerHttpResponse;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_CIRCUIT_BREAKER;
import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_IS_IN_ROUTING_STATE;
import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_PRE_ROUTE_TIME;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* Polaris circuit breaker implement in Zuul.
*
* @author Haotian Zhang
*/
public class PolarisCircuitBreakerZuulFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisCircuitBreakerZuulFilter.class);
private final CircuitBreakerFactory circuitBreakerFactory;
private final PolarisZuulFallbackFactory polarisZuulFallbackFactory;
private final Environment environment;
public PolarisCircuitBreakerZuulFilter(
CircuitBreakerFactory circuitBreakerFactory,
PolarisZuulFallbackFactory polarisZuulFallbackFactory,
Environment environment) {
this.circuitBreakerFactory = circuitBreakerFactory;
this.polarisZuulFallbackFactory = polarisZuulFallbackFactory;
this.environment = environment;
}
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* ServiceId is set after PreDecorationFilter.
*
* @return filter order
*/
@Override
public int filterOrder() {
return OrderConstant.Client.Zuul.CIRCUIT_BREAKER_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
String enabled = environment.getProperty("spring.cloud.polaris.circuitbreaker.enabled");
return StringUtils.isEmpty(enabled) || enabled.equals("true");
}
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
String serviceId = ZuulFilterUtils.getServiceId(context);
String path = ZuulFilterUtils.getPath(context);
String circuitName = com.tencent.polaris.api.utils.StringUtils.isBlank(path) ?
MetadataContext.LOCAL_NAMESPACE + "#" + serviceId :
MetadataContext.LOCAL_NAMESPACE + "#" + serviceId + "#" + path + "#http#" + context.getRequest()
.getMethod();
CircuitBreaker circuitBreaker = circuitBreakerFactory.create(circuitName);
if (circuitBreaker instanceof PolarisCircuitBreaker) {
PolarisCircuitBreaker polarisCircuitBreaker = (PolarisCircuitBreaker) circuitBreaker;
context.set(POLARIS_CIRCUIT_BREAKER, circuitBreaker);
try {
polarisCircuitBreaker.acquirePermission();
}
catch (CallAbortedException exception) {
FallbackProvider fallbackProvider = polarisZuulFallbackFactory.getFallbackProvider(serviceId);
ClientHttpResponse clientHttpResponse;
if (fallbackProvider != null) {
clientHttpResponse = fallbackProvider.fallbackResponse(serviceId, exception);
}
else if (exception.getFallbackInfo() != null) {
clientHttpResponse = new PolarisCircuitBreakerHttpResponse(exception.getFallbackInfo());
}
else {
throw new IllegalStateException(exception);
}
try {
context.setSendZuulResponse(false);
// set status code
context.setResponseStatusCode(clientHttpResponse.getRawStatusCode());
// set headers
HttpHeaders headers = clientHttpResponse.getHeaders();
for (String key : headers.keySet()) {
List<String> values = headers.get(key);
if (!CollectionUtils.isEmpty(values)) {
for (String value : values) {
context.addZuulResponseHeader(key, value);
}
}
}
// set body
context.getResponse().setCharacterEncoding("UTF-8");
context.setResponseBody(IOUtils.toString(clientHttpResponse.getBody(), StandardCharsets.UTF_8));
LOGGER.debug("PolarisCircuitBreaker CallAbortedException: {}", exception.getMessage());
PolarisCircuitBreakerUtils.reportStatus(polarisCircuitBreaker.getConsumerAPI(), polarisCircuitBreaker.getConf(), exception);
}
catch (IOException e) {
LOGGER.error("Return circuit breaker fallback info failed: {}", e.getMessage());
throw new IllegalStateException(e);
}
}
context.set(POLARIS_PRE_ROUTE_TIME, Long.valueOf(System.currentTimeMillis()));
context.set(POLARIS_IS_IN_ROUTING_STATE, true);
}
return null;
}
}

@ -17,12 +17,10 @@
package org.springframework.cloud.openfeign;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisCircuitBreakerNameResolver;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignCircuitBreaker;
import feign.Feign;
import feign.Target;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.util.StringUtils;
/**
@ -32,16 +30,6 @@ import org.springframework.util.StringUtils;
*/
public class PolarisFeignCircuitBreakerTargeter implements Targeter {
private final CircuitBreakerFactory circuitBreakerFactory;
private final PolarisCircuitBreakerNameResolver circuitBreakerNameResolver;
public PolarisFeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory,
PolarisCircuitBreakerNameResolver circuitBreakerNameResolver) {
this.circuitBreakerFactory = circuitBreakerFactory;
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
}
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
@ -58,20 +46,20 @@ public class PolarisFeignCircuitBreakerTargeter implements Targeter {
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
}
return builder(name, builder).target(target);
return builder.target(target);
}
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target, PolarisFeignCircuitBreaker.Builder builder, Class<?> fallbackFactoryClass) {
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext("fallbackFactory",
feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
return builder(feignClientName, builder).target(target, fallbackFactory);
return builder.target(target, fallbackFactory);
}
private <T> T targetWithFallback(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target,
PolarisFeignCircuitBreaker.Builder builder, Class<?> fallback) {
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
return builder(feignClientName, builder).target(target, fallbackInstance);
return builder.target(target, fallbackInstance);
}
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context,
@ -91,11 +79,4 @@ public class PolarisFeignCircuitBreakerTargeter implements Targeter {
return (T) fallbackInstance;
}
private PolarisFeignCircuitBreaker.Builder builder(String feignClientName, PolarisFeignCircuitBreaker.Builder builder) {
return builder
.circuitBreakerFactory(circuitBreakerFactory)
.feignClientName(feignClientName)
.circuitBreakerNameResolver(circuitBreakerNameResolver);
}
}

@ -18,7 +18,6 @@
package org.springframework.cloud.openfeign;
import com.tencent.cloud.polaris.circuitbreaker.config.ConditionalOnPolarisCircuitBreakerEnabled;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisCircuitBreakerNameResolver;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -46,7 +45,7 @@ public class PolarisFeignCircuitBreakerTargeterAutoConfiguration {
@Primary
@ConditionalOnBean(CircuitBreakerFactory.class)
@ConditionalOnMissingBean(Targeter.class)
public Targeter polarisFeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, PolarisCircuitBreakerNameResolver circuitBreakerNameResolver) {
return new PolarisFeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerNameResolver);
public Targeter polarisFeignCircuitBreakerTargeter() {
return new PolarisFeignCircuitBreakerTargeter();
}
}

@ -1,6 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration,\
com.tencent.cloud.polaris.circuitbreaker.config.ReactivePolarisCircuitBreakerAutoConfiguration,\
com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration,\
com.tencent.cloud.polaris.circuitbreaker.config.GatewayPolarisCircuitBreakerAutoConfiguration,\
org.springframework.cloud.openfeign.PolarisFeignCircuitBreakerTargeterAutoConfiguration,\

@ -24,7 +24,6 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ -50,11 +49,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_CIRCUIT_BREAKER;
@ -62,7 +58,7 @@ import static com.tencent.polaris.test.common.TestUtils.SERVER_ADDRESS_ENV;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisCircuitBreaker} and {@link ReactivePolarisCircuitBreaker} using real mock server.
* Test for {@link PolarisCircuitBreaker} using real mock server.
*
* @author sean yu
*/
@ -138,26 +134,6 @@ public class PolarisCircuitBreakerMockServerTest {
Utils.sleepUninterrupted(2000);
}
assertThat(resList).isEqualTo(Arrays.asList("invoke success", "fallback", "fallback", "fallback", "fallback"));
// always fallback
ReactivePolarisCircuitBreakerFactory reactivePolarisCircuitBreakerFactory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI, polarisCircuitBreakerProperties);
ReactiveCircuitBreaker rcb = reactivePolarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER);
assertThat(Mono.just("foobar").transform(it -> rcb.run(it, t -> Mono.just("fallback")))
.block()).isEqualTo("fallback");
assertThat(Mono.error(new RuntimeException("boom")).transform(it -> rcb.run(it, t -> Mono.just("fallback")))
.block()).isEqualTo("fallback");
assertThat(Flux.just("foobar", "hello world")
.transform(it -> rcb.run(it, t -> Flux.just("fallback", "fallback")))
.collectList().block())
.isEqualTo(Arrays.asList("fallback", "fallback"));
assertThat(Flux.error(new RuntimeException("boom")).transform(it -> rcb.run(it, t -> Flux.just("fallback")))
.collectList().block())
.isEqualTo(Collections.singletonList("fallback"));
}
}

@ -1,120 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.ReflectionUtils;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.config.ReactivePolarisCircuitBreakerAutoConfiguration;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.polaris.client.util.Utils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_CIRCUIT_BREAKER;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link ReactivePolarisCircuitBreaker}.
*
* @author sean yu
*/
@ExtendWith(MockitoExtension.class)
public class ReactivePolarisCircuitBreakerTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private final ApplicationContextRunner reactiveContextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
PolarisContextAutoConfiguration.class,
RpcEnhancementAutoConfiguration.class,
LoadBalancerAutoConfiguration.class,
ReactivePolarisCircuitBreakerAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true")
.withPropertyValues("spring.cloud.polaris.circuitbreaker.configuration-cleanup-interval=5000");
@BeforeAll
public static void beforeClass() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties("spring.cloud.polaris.namespace"))
.thenReturn(NAMESPACE_TEST);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties("spring.cloud.polaris.service"))
.thenReturn(SERVICE_CIRCUIT_BREAKER);
}
@AfterAll
public static void afterAll() {
mockedApplicationContextAwareUtils.close();
}
@Test
public void run() {
this.reactiveContextRunner.run(context -> {
ReactivePolarisCircuitBreakerFactory polarisCircuitBreakerFactory = context.getBean(ReactivePolarisCircuitBreakerFactory.class);
ReactiveCircuitBreaker cb = polarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER);
PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration configuration =
polarisCircuitBreakerFactory.configBuilder(SERVICE_CIRCUIT_BREAKER).build();
polarisCircuitBreakerFactory.configureDefault(id -> configuration);
assertThat(Mono.just("foobar").transform(cb::run).block()).isEqualTo("foobar");
assertThat(Mono.error(new RuntimeException("boom")).transform(it -> cb.run(it, t -> Mono.just("fallback")))
.block()).isEqualTo("fallback");
assertThat(Flux.just("foobar", "hello world").transform(cb::run).collectList().block())
.isEqualTo(Arrays.asList("foobar", "hello world"));
assertThat(Flux.error(new RuntimeException("boom")).transform(it -> cb.run(it, t -> Flux.just("fallback")))
.collectList().block()).isEqualTo(Collections.singletonList("fallback"));
Method getConfigurationsMethod = ReflectionUtils.findMethod(PolarisCircuitBreakerFactory.class,
"getConfigurations");
Assertions.assertNotNull(getConfigurationsMethod);
ReflectionUtils.makeAccessible(getConfigurationsMethod);
Map<?, ?> values = (Map<?, ?>) ReflectionUtils.invokeMethod(getConfigurationsMethod, polarisCircuitBreakerFactory);
Assertions.assertNotNull(values);
Assertions.assertTrue(values.size() >= 0);
Utils.sleepUninterrupted(10 * 1000);
// clear by cleanupService in ReactivePolarisCircuitBreakerFactory
Assertions.assertEquals(0, values.size());
});
}
}

@ -26,7 +26,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
@ -46,14 +45,6 @@ public class PolarisCircuitBreakerAutoConfigurationTest {
PolarisCircuitBreakerAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
private final ApplicationContextRunner reactiveContextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
PolarisContextAutoConfiguration.class,
RpcEnhancementAutoConfiguration.class,
LoadBalancerAutoConfiguration.class,
ReactivePolarisCircuitBreakerAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
@ -64,13 +55,4 @@ public class PolarisCircuitBreakerAutoConfigurationTest {
});
}
@Test
public void testReactiveInitialization() {
this.reactiveContextRunner.run(context -> {
assertThat(context).hasSingleBean(ReactivePolarisCircuitBreakerAutoConfiguration.class);
assertThat(context).hasSingleBean(ReactiveCircuitBreakerFactory.class);
assertThat(context).hasSingleBean(CircuitBreakerConfigModifier.class);
});
}
}

@ -54,7 +54,6 @@ public class PolarisCircuitBreakerBootstrapConfigurationTest {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(PolarisCircuitBreakerAutoConfiguration.class);
assertThat(context).hasSingleBean(PolarisCircuitBreakerFeignClientAutoConfiguration.class);
assertThat(context).hasSingleBean(ReactivePolarisCircuitBreakerAutoConfiguration.class);
});
}
}

@ -1,57 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker.feign;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* @author Shedfree Wu
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
classes = PolarisCircuitBreakerFeignIntegrationTest.TestConfig.class,
properties = {
"feign.hystrix.enabled=false",
"spring.cloud.gateway.enabled=false",
"feign.circuitbreaker.enabled=true",
"spring.cloud.polaris.namespace=" + NAMESPACE_TEST,
"spring.cloud.polaris.service=test"
})
public class PolarisCircuitBreakerFeignIntegrationDisableFeignHystrixTest {
@Autowired
private PolarisCircuitBreakerFeignIntegrationTest.EchoService echoService;
@Test
public void testFeignClient() {
assertThatThrownBy(() -> {
echoService.echo("test");
}).isInstanceOf(RuntimeException.class)
.hasMessageContaining("Load balancer does not have available server for client");
}
}

@ -1,228 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker.feign;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
import com.tencent.polaris.test.mock.discovery.NamingServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* @author sean yu
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
classes = PolarisCircuitBreakerFeignIntegrationTest.TestConfig.class,
properties = {
"feign.hystrix.enabled=true",
"spring.cloud.gateway.enabled=false",
"spring.cloud.polaris.address=grpc://127.0.0.1:10081",
"feign.circuitbreaker.enabled=true",
"spring.cloud.polaris.namespace=" + NAMESPACE_TEST,
"spring.cloud.polaris.service=test"
})
public class PolarisCircuitBreakerFeignIntegrationTest {
private static final String TEST_SERVICE_NAME = "test-service-callee";
private static NamingServer namingServer;
@Autowired
private EchoService echoService;
@Autowired
private FooService fooService;
@Autowired
private BarService barService;
@Autowired
private BazService bazService;
@BeforeAll
static void beforeAll() throws Exception {
PolarisSDKContextManager.innerDestroy();
namingServer = NamingServer.startNamingServer(10081);
ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, TEST_SERVICE_NAME);
CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder();
InputStream inputStream = PolarisCircuitBreakerFeignIntegrationTest.class.getClassLoader()
.getResourceAsStream("circuitBreakerRule.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder);
CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build();
CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
.addRules(circuitBreakerRule).build();
namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker);
}
@AfterAll
static void afterAll() {
if (null != namingServer) {
namingServer.terminate();
}
}
@Test
public void contextLoads() {
assertThat(echoService).isNotNull();
assertThat(fooService).isNotNull();
}
@Test
public void testFeignClient() throws InvocationTargetException {
assertThat(echoService.echo("test")).isEqualTo("echo fallback");
Utils.sleepUninterrupted(2000);
assertThatThrownBy(() -> {
echoService.echo(null);
}).isInstanceOf(InvocationTargetException.class);
assertThatThrownBy(() -> {
fooService.echo("test");
}).isInstanceOf(NoFallbackAvailableException.class);
Utils.sleepUninterrupted(2000);
assertThat(barService.bar()).isEqualTo("\"fallback from polaris server\"");
Utils.sleepUninterrupted(2000);
assertThat(bazService.baz()).isEqualTo("\"fallback from polaris server\"");
assertThat(fooService.toString()).isNotEqualTo(echoService.toString());
assertThat(fooService.hashCode()).isNotEqualTo(echoService.hashCode());
assertThat(echoService.equals(fooService)).isEqualTo(Boolean.FALSE);
}
@FeignClient(value = TEST_SERVICE_NAME, contextId = "1", fallback = EchoServiceFallback.class)
@Primary
public interface EchoService {
@RequestMapping(path = "echo/{str}")
String echo(@RequestParam("str") String param) throws InvocationTargetException;
}
@FeignClient(value = TEST_SERVICE_NAME, contextId = "2", fallbackFactory = CustomFallbackFactory.class)
public interface FooService {
@RequestMapping("echo/{str}")
String echo(@RequestParam("str") String param);
}
@FeignClient(value = TEST_SERVICE_NAME, contextId = "3")
public interface BarService {
@RequestMapping(path = "bar")
String bar();
}
public interface BazService {
@RequestMapping(path = "baz")
String baz();
}
@FeignClient(value = TEST_SERVICE_NAME, contextId = "4")
public interface BazClient extends BazService {
}
@Configuration
@EnableAutoConfiguration
@ImportAutoConfiguration({PolarisCircuitBreakerFeignClientAutoConfiguration.class})
@EnableFeignClients
public static class TestConfig {
@Bean
public EchoServiceFallback echoServiceFallback() {
return new EchoServiceFallback();
}
@Bean
public CustomFallbackFactory customFallbackFactory() {
return new CustomFallbackFactory();
}
}
public static class EchoServiceFallback implements EchoService {
@Override
public String echo(@RequestParam("str") String param) throws InvocationTargetException {
if (param == null) {
throw new InvocationTargetException(new Exception(), "test InvocationTargetException");
}
return "echo fallback";
}
}
public static class FooServiceFallback implements FooService {
@Override
public String echo(@RequestParam("str") String param) {
throw new NoFallbackAvailableException("fallback", new RuntimeException());
}
}
public static class CustomFallbackFactory
implements FallbackFactory<FooService> {
private final FooService fooService = new FooServiceFallback();
@Override
public FooService create(Throwable throwable) {
return fooService;
}
}
}

@ -50,13 +50,13 @@ public class PolarisFeignCircuitBreakerTargeterTest {
@Test
public void testTarget() {
PolarisFeignCircuitBreakerTargeter targeter = new PolarisFeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerNameResolver);
PolarisFeignCircuitBreakerTargeter targeter = new PolarisFeignCircuitBreakerTargeter();
targeter.target(new FeignClientFactoryBean(), new Feign.Builder(), new FeignContext(), new Target.HardCodedTarget<>(TestApi.class, "/test"));
}
@Test
public void testTarget2() {
PolarisFeignCircuitBreakerTargeter targeter = new PolarisFeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerNameResolver);
PolarisFeignCircuitBreakerTargeter targeter = new PolarisFeignCircuitBreakerTargeter();
FeignClientFactoryBean feignClientFactoryBean = mock(FeignClientFactoryBean.class);
doReturn(TestApi.class).when(feignClientFactoryBean).getFallback();
doReturn("test").when(feignClientFactoryBean).getName();
@ -69,7 +69,7 @@ public class PolarisFeignCircuitBreakerTargeterTest {
@Test
public void testTarget3() {
PolarisFeignCircuitBreakerTargeter targeter = new PolarisFeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerNameResolver);
PolarisFeignCircuitBreakerTargeter targeter = new PolarisFeignCircuitBreakerTargeter();
FeignClientFactoryBean feignClientFactoryBean = mock(FeignClientFactoryBean.class);
doReturn(void.class).when(feignClientFactoryBean).getFallback();
doReturn(TestApi.class).when(feignClientFactoryBean).getFallbackFactory();

@ -1,218 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker.gateway;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
import com.tencent.polaris.test.mock.discovery.NamingServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author sean yu
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {
"spring.cloud.gateway.enabled=true",
"spring.cloud.polaris.namespace=" + NAMESPACE_TEST,
"spring.cloud.polaris.service=test",
"spring.main.web-application-type=reactive"
},
classes = PolarisCircuitBreakerGatewayIntegrationTest.TestApplication.class
)
@ActiveProfiles("test-gateway")
@AutoConfigureWebTestClient(timeout = "1000000")
public class PolarisCircuitBreakerGatewayIntegrationTest {
private static final String TEST_SERVICE_NAME = "test-service-callee";
private static NamingServer namingServer;
@Autowired
private WebTestClient webClient;
@Autowired
private ApplicationContext applicationContext;
@BeforeAll
static void beforeAll() throws Exception {
PolarisSDKContextManager.innerDestroy();
namingServer = NamingServer.startNamingServer(10081);
ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, TEST_SERVICE_NAME);
CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder();
InputStream inputStream = PolarisCircuitBreakerGatewayIntegrationTest.class.getClassLoader()
.getResourceAsStream("circuitBreakerRule.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder);
CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build();
CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
.addRules(circuitBreakerRule).build();
namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker);
}
@AfterAll
static void afterAll() {
if (null != namingServer) {
namingServer.terminate();
}
}
@Test
public void fallback() throws Exception {
SpringCloudCircuitBreakerFilterFactory.Config config = new SpringCloudCircuitBreakerFilterFactory.Config();
applicationContext.getBean(PolarisCircuitBreakerFilterFactory.class).apply(config).toString();
webClient
.get().uri("/err")
.header("Host", "www.circuitbreaker.com")
.exchange()
.expectStatus().isOk()
.expectBody()
.consumeWith(
response -> assertThat(response.getResponseBody()).isEqualTo("fallback".getBytes()));
Utils.sleepUninterrupted(2000);
webClient
.get().uri("/err-skip-fallback")
.header("Host", "www.circuitbreaker-skip-fallback.com")
.exchange()
.expectStatus();
Utils.sleepUninterrupted(2000);
// this should be 200, but for some unknown reason, GitHub action run failed in windows, so we skip this check
webClient
.get().uri("/err-skip-fallback")
.header("Host", "www.circuitbreaker-skip-fallback.com")
.exchange()
.expectStatus();
Utils.sleepUninterrupted(2000);
webClient
.get().uri("/err-no-fallback")
.header("Host", "www.circuitbreaker-no-fallback.com")
.exchange()
.expectStatus();
Utils.sleepUninterrupted(2000);
webClient
.get().uri("/err-no-fallback")
.header("Host", "www.circuitbreaker-no-fallback.com")
.exchange()
.expectStatus();
Utils.sleepUninterrupted(2000);
webClient
.get().uri("/err-no-fallback")
.header("Host", "www.circuitbreaker-no-fallback.com")
.exchange()
.expectStatus();
}
@Configuration
@EnableAutoConfiguration
public static class TestApplication {
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
Set<String> codeSets = new HashSet<>();
codeSets.add("4**");
codeSets.add("5**");
return builder.routes()
.route(p -> p
.host("*.circuitbreaker.com")
.filters(f -> f
.circuitBreaker(config -> config
.setStatusCodes(codeSets)
.setFallbackUri("forward:/fallback")
.setName(TEST_SERVICE_NAME)
))
.uri("http://httpbin.org:80"))
.route(p -> p
.host("*.circuitbreaker-skip-fallback.com")
.filters(f -> f
.circuitBreaker(config -> config
.setStatusCodes(Collections.singleton("5**"))
.setName(TEST_SERVICE_NAME)
))
.uri("http://httpbin.org:80"))
.route(p -> p
.host("*.circuitbreaker-no-fallback.com")
.filters(f -> f
.circuitBreaker(config -> config
.setName(TEST_SERVICE_NAME)
))
.uri("lb://" + TEST_SERVICE_NAME))
.build();
}
@RestController
static class Controller {
@RequestMapping("/fallback")
public Mono<String> fallback() {
return Mono.just("fallback");
}
}
}
}

@ -17,9 +17,13 @@
package com.tencent.cloud.polaris.circuitbreaker.reporter;
import java.lang.reflect.Field;
import java.net.URI;
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.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
@ -70,8 +74,11 @@ public class SuccessCircuitBreakerReporterTest {
@Mock
private CircuitBreakAPI circuitBreakAPI;
@Mock
private MetadataLocalProperties metadataLocalProperties;
@BeforeAll
static void beforeAll() {
static void beforeAll() throws Exception {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
@ -81,6 +88,12 @@ public class SuccessCircuitBreakerReporterTest {
.when(applicationContext).getBean(RpcEnhancementReporterProperties.class);
mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext)
.thenReturn(applicationContext);
StaticMetadataManager metadataManager = new StaticMetadataManager(new MetadataLocalProperties(), null);
Field field = MetadataContextHolder.class.getDeclaredField("staticMetadataManager");
field.setAccessible(true);
field.set(null, metadataManager);
}
@AfterAll

@ -1,303 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.circuitbreaker.resttemplate;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.stream.Collectors;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
import com.tencent.polaris.test.mock.discovery.NamingServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.client.ExpectedCount;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
/**
* @author sean yu
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
classes = PolarisCircuitBreakerRestTemplateIntegrationTest.TestConfig.class,
properties = {
"spring.cloud.gateway.enabled=false",
"spring.cloud.polaris.address=grpc://127.0.0.1:10081",
"feign.circuitbreaker.enabled=true",
"spring.cloud.polaris.namespace=" + NAMESPACE_TEST,
"spring.cloud.polaris.service=test"
})
public class PolarisCircuitBreakerRestTemplateIntegrationTest {
private static final String TEST_SERVICE_NAME = "test-service-callee";
private static NamingServer namingServer;
@Autowired
@Qualifier("defaultRestTemplate")
private RestTemplate defaultRestTemplate;
@Autowired
@Qualifier("restTemplateFallbackFromPolaris")
private RestTemplate restTemplateFallbackFromPolaris;
@Autowired
@Qualifier("restTemplateFallbackFromCode")
private RestTemplate restTemplateFallbackFromCode;
@Autowired
@Qualifier("restTemplateFallbackFromCode2")
private RestTemplate restTemplateFallbackFromCode2;
@Autowired
@Qualifier("restTemplateFallbackFromCode3")
private RestTemplate restTemplateFallbackFromCode3;
@Autowired
@Qualifier("restTemplateFallbackFromCode4")
private RestTemplate restTemplateFallbackFromCode4;
@Autowired
private ApplicationContext applicationContext;
@BeforeAll
static void beforeAll() throws Exception {
PolarisSDKContextManager.innerDestroy();
namingServer = NamingServer.startNamingServer(10081);
ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, TEST_SERVICE_NAME);
CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder();
InputStream inputStream = PolarisCircuitBreakerRestTemplateIntegrationTest.class.getClassLoader()
.getResourceAsStream("circuitBreakerRule.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder);
CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build();
CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
.addRules(circuitBreakerRule).build();
namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker);
}
@AfterAll
static void afterAll() {
if (null != namingServer) {
namingServer.terminate();
}
}
@Test
public void testRestTemplate() throws URISyntaxException {
MockRestServiceServer mockServer = MockRestServiceServer.createServer(defaultRestTemplate);
mockServer
.expect(ExpectedCount.once(), requestTo(new URI("http://localhost:18001/example/service/b/info")))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.OK).body("OK"));
assertThat(defaultRestTemplate.getForObject("http://localhost:18001/example/service/b/info", String.class)).isEqualTo("OK");
mockServer.verify();
mockServer.reset();
HttpHeaders headers = new HttpHeaders();
mockServer
.expect(ExpectedCount.once(), requestTo(new URI("http://localhost:18001/example/service/b/info")))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.BAD_GATEWAY).headers(headers).body("BAD_GATEWAY"));
assertThat(defaultRestTemplate.getForObject("http://localhost:18001/example/service/b/info", String.class)).isEqualTo("fallback");
mockServer.verify();
mockServer.reset();
assertThatThrownBy(() -> {
restTemplateFallbackFromPolaris.getForObject("/example/service/b/info", String.class);
}).isInstanceOf(IllegalStateException.class);
assertThat(restTemplateFallbackFromCode.getForObject("/example/service/b/info", String.class)).isEqualTo("\"this is a fallback class\"");
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromCode2.getForObject("/example/service/b/info", String.class)).isEqualTo("\"this is a fallback class\"");
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromCode3.getForEntity("/example/service/b/info", String.class)
.getStatusCode()).isEqualTo(HttpStatus.OK);
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromCode4.getForObject("/example/service/b/info", String.class)).isEqualTo("fallback");
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromPolaris.getForObject("/example/service/b/info", String.class)).isEqualTo("\"fallback from polaris server\"");
// just for code coverage
PolarisCircuitBreakerHttpResponse response = ((CustomPolarisCircuitBreakerFallback) applicationContext.getBean("customPolarisCircuitBreakerFallback")).fallback();
assertThat(response.getStatusText()).isEqualTo("OK");
assertThat(response.getFallbackInfo().getCode()).isEqualTo(200);
}
@Configuration
@EnableAutoConfiguration
@ImportAutoConfiguration({PolarisCircuitBreakerFeignClientAutoConfiguration.class})
@EnableFeignClients
public static class TestConfig {
@Bean
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker(fallback = "fallback")
public RestTemplate defaultRestTemplate() {
return new RestTemplate();
}
@Bean
@LoadBalanced
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker
public RestTemplate restTemplateFallbackFromPolaris() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
@LoadBalanced
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker(fallbackClass = CustomPolarisCircuitBreakerFallback.class)
public RestTemplate restTemplateFallbackFromCode() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
@LoadBalanced
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker(fallbackClass = CustomPolarisCircuitBreakerFallback2.class)
public RestTemplate restTemplateFallbackFromCode2() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
@LoadBalanced
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker(fallbackClass = CustomPolarisCircuitBreakerFallback3.class)
public RestTemplate restTemplateFallbackFromCode3() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
@LoadBalanced
@PolarisCircuitBreaker(fallback = "fallback")
public RestTemplate restTemplateFallbackFromCode4() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
public CustomPolarisCircuitBreakerFallback customPolarisCircuitBreakerFallback() {
return new CustomPolarisCircuitBreakerFallback();
}
@Bean
public CustomPolarisCircuitBreakerFallback2 customPolarisCircuitBreakerFallback2() {
return new CustomPolarisCircuitBreakerFallback2();
}
@Bean
public CustomPolarisCircuitBreakerFallback3 customPolarisCircuitBreakerFallback3() {
return new CustomPolarisCircuitBreakerFallback3();
}
@RestController
@RequestMapping("/example/service/b")
public class ServiceBController {
/**
* Get service information.
*
* @return service information
*/
@GetMapping("/info")
public String info() {
return "hello world ! I'm a service B1";
}
}
}
public static class CustomPolarisCircuitBreakerFallback implements PolarisCircuitBreakerFallback {
@Override
public PolarisCircuitBreakerHttpResponse fallback() {
return new PolarisCircuitBreakerHttpResponse(
200,
new HashMap<String, String>() {{
put("xxx", "xxx");
}},
"\"this is a fallback class\"");
}
}
public static class CustomPolarisCircuitBreakerFallback2 implements PolarisCircuitBreakerFallback {
@Override
public PolarisCircuitBreakerHttpResponse fallback() {
return new PolarisCircuitBreakerHttpResponse(
200,
"\"this is a fallback class\""
);
}
}
public static class CustomPolarisCircuitBreakerFallback3 implements PolarisCircuitBreakerFallback {
@Override
public PolarisCircuitBreakerHttpResponse fallback() {
return new PolarisCircuitBreakerHttpResponse(
200
);
}
}
}

@ -57,7 +57,7 @@ public class PolarisRegistration implements Registration {
private final StaticMetadataManager staticMetadataManager;
private final String serviceId;
private String serviceId;
private final String host;
private final boolean isSecure;
private final ServletWebServerApplicationContext servletWebServerApplicationContext;
@ -164,6 +164,10 @@ public class PolarisRegistration implements Registration {
}
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
@Override
public String getServiceId() {
return serviceId;

@ -23,6 +23,7 @@ import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.util.OkHttpUtil;
import com.tencent.cloud.common.util.OtUtils;
@ -106,6 +107,7 @@ public class PolarisServiceRegistry implements ServiceRegistry<PolarisRegistrati
}
registration.customize();
String serviceId = registration.getServiceId();
MetadataContext.setLocalService(serviceId);
// Register instance.
InstanceRegisterRequest instanceRegisterRequest = new InstanceRegisterRequest();

@ -3,7 +3,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration,\
com.tencent.cloud.polaris.registry.PolarisServiceRegistryAutoConfiguration,\
com.tencent.cloud.polaris.endpoint.PolarisDiscoveryEndpointAutoConfiguration,\
com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerAutoConfiguration,\
com.tencent.cloud.polaris.tsf.registry.TsfDiscoveryRegistryAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.DiscoveryPropertiesBootstrapAutoConfiguration

@ -131,7 +131,6 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule {
}
PolarisLoadBalancer polarisLoadBalancer = (PolarisLoadBalancer) loadBalancer;
// 1. get all servers from polaris client
List<Server> allServers = polarisLoadBalancer.getReachableServersWithoutCache();
if (CollectionUtils.isEmpty(allServers)) {

@ -35,7 +35,7 @@ import org.springframework.util.LinkedCaseInsensitiveMap;
*/
public class PolarisRouterContext {
private Map<String, Map<String, String>> labels;
private Map<String, Map<String, String>> labels = new HashMap<>();
public Map<String, String> getLabels(String labelType) {
if (CollectionUtils.isEmpty(labels)) {

@ -19,6 +19,7 @@ package com.tencent.cloud.polaris.router.beanprocessor;
import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerInterceptor;
import com.tencent.cloud.polaris.router.resttemplate.RouterContextFactory;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
@ -27,6 +28,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.core.Ordered;
import org.springframework.lang.NonNull;
/**
@ -35,7 +37,11 @@ import org.springframework.lang.NonNull;
*
* @author lepdou 2022-05-18
*/
public class LoadBalancerInterceptorBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
public class LoadBalancerInterceptorBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, Ordered {
/**
* The order of the bean post processor. if user want to wrap it(CustomLoadBalancerInterceptor -> PolarisLoadBalancerInterceptor), CustomLoadBalancerInterceptorBeanPostProcessor's order should be bigger than ${@link POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER}.
*/
public static final int POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER = 0;
private BeanFactory factory;
@ -52,9 +58,14 @@ public class LoadBalancerInterceptorBeanPostProcessor implements BeanPostProcess
LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class);
LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class);
RouterContextFactory routerContextFactory = this.factory.getBean(RouterContextFactory.class);
return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory, routerContextFactory);
EnhancedPluginRunner pluginRunner = this.factory.getBean(EnhancedPluginRunner.class);
return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory, routerContextFactory, pluginRunner);
}
return bean;
}
@Override
public int getOrder() {
return POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER;
}
}

@ -43,7 +43,8 @@ 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 com.tencent.cloud.polaris.router.zuul.RouterLabelZuulFilter;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedZuulPluginRunner;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
@ -56,6 +57,7 @@ import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
@ -165,9 +167,12 @@ public class RouterAutoConfiguration {
RibbonCommandFactory<?> ribbonCommandFactory,
StaticMetadataManager staticMetadataManager,
RouterRuleLabelResolver routerRuleLabelResolver,
List<ServletRouterLabelResolver> routerLabelResolvers) {
List<ServletRouterLabelResolver> routerLabelResolvers,
@Lazy EnhancedPluginRunner pluginRunner) {
EnhancedZuulPluginRunner enhancedZuulPluginRunner = new EnhancedZuulPluginRunner(pluginRunner);
return new PolarisRibbonRoutingFilter(helper, ribbonCommandFactory, staticMetadataManager,
routerRuleLabelResolver, routerLabelResolvers);
routerRuleLabelResolver, routerLabelResolvers, enhancedZuulPluginRunner);
}
@Bean
@ -198,13 +203,7 @@ public class RouterAutoConfiguration {
public SmartInitializingSingleton addRouterLabelInterceptorForRestTemplate(RouterLabelRestTemplateInterceptor interceptor) {
return () -> restTemplates.forEach(restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
int addIndex = list.size();
for (int i = 0; i < list.size(); i++) {
if (list.get(i) instanceof EnhancedRestTemplateInterceptor) {
addIndex = i;
}
}
list.add(addIndex, interceptor);
list.add(interceptor);
restTemplate.setInterceptors(list);
});
}

@ -21,6 +21,8 @@ import java.io.IOException;
import java.net.URI;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateWrapInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
@ -42,15 +44,18 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
private final LoadBalancerClient loadBalancer;
private final LoadBalancerRequestFactory requestFactory;
private final RouterContextFactory routerContextFactory;
private final EnhancedPluginRunner enhancedPluginRunner;
private final boolean isRibbonLoadBalanceClient;
public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory,
RouterContextFactory routerContextFactory) {
public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory,
RouterContextFactory routerContextFactory, EnhancedPluginRunner enhancedPluginRunner) {
super(loadBalancer, requestFactory);
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
this.routerContextFactory = routerContextFactory;
this.enhancedPluginRunner = enhancedPluginRunner;
this.isRibbonLoadBalanceClient = loadBalancer instanceof RibbonLoadBalancerClient;
}
@ -70,9 +75,17 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
RouterContextHelper.setRouterContextToRequest(request, routerContext);
//3. do loadbalancer and execute request
ClientHttpResponse response = ((RibbonLoadBalancerClient) loadBalancer).execute(peerServiceName,
this.requestFactory.createRequest(request, body, execution), routerContext);
ClientHttpResponse response;
if (enhancedPluginRunner != null) {
EnhancedRestTemplateWrapInterceptor enhancedRestTemplatePluginRunnerInterceptor =
new EnhancedRestTemplateWrapInterceptor(enhancedPluginRunner, (RibbonLoadBalancerClient) loadBalancer);
response = enhancedRestTemplatePluginRunnerInterceptor.intercept(
request, peerServiceName, this.requestFactory.createRequest(request, body, execution), routerContext);
}
else {
response = ((RibbonLoadBalancerClient) loadBalancer).execute(peerServiceName,
this.requestFactory.createRequest(request, body, execution), routerContext);
}
//4. set router context to response
RouterContextHelper.setRouterContextToResponse(routerContext, response);
return response;

@ -37,6 +37,7 @@ import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedZuulPluginRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -71,11 +72,14 @@ public class PolarisRibbonRoutingFilter extends RibbonRoutingFilter implements B
private BeanFactory factory;
private boolean useServlet31 = true;
private EnhancedZuulPluginRunner enhancedZuulPluginRunner;
public PolarisRibbonRoutingFilter(ProxyRequestHelper helper,
RibbonCommandFactory<?> ribbonCommandFactory,
StaticMetadataManager staticMetadataManager,
RouterRuleLabelResolver routerRuleLabelResolver,
List<ServletRouterLabelResolver> routerLabelResolvers) {
List<ServletRouterLabelResolver> routerLabelResolvers,
EnhancedZuulPluginRunner enhancedZuulPluginRunner) {
super(helper, ribbonCommandFactory, Collections.emptyList());
this.staticMetadataManager = staticMetadataManager;
this.routerRuleLabelResolver = routerRuleLabelResolver;
@ -95,6 +99,7 @@ public class PolarisRibbonRoutingFilter extends RibbonRoutingFilter implements B
catch (NoSuchMethodException e) {
useServlet31 = false;
}
this.enhancedZuulPluginRunner = enhancedZuulPluginRunner;
}
@ -188,4 +193,10 @@ public class PolarisRibbonRoutingFilter extends RibbonRoutingFilter implements B
private void init() {
this.requestCustomizers = BeanFactoryUtils.getBeans(factory, RibbonRequestCustomizer.class);
}
@Override
public Object run() {
enhancedZuulPluginRunner.run();
return super.run();
}
}

@ -30,6 +30,7 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@ -69,6 +70,9 @@ public class PolarisLoadBalancerInterceptorTest {
private RibbonLoadBalancerClient loadBalancerClient;
@Mock
private LoadBalancerRequestFactory loadBalancerRequestFactory;
@Mock
private EnhancedPluginRunner enhancedPluginRunner;
@Mock
private RouterContextFactory routerContextFactory;
@ -90,7 +94,7 @@ public class PolarisLoadBalancerInterceptorTest {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(callerService);
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
MetadataContext metadataContext = new MetadataContext();
try (MockedStatic<MetadataContextHolder> mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class)) {
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
@ -100,7 +104,7 @@ public class PolarisLoadBalancerInterceptorTest {
when(loadBalancerRequestFactory.createRequest(request, null, null)).thenReturn(loadBalancerRequest);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient,
loadBalancerRequestFactory, routerContextFactory);
loadBalancerRequestFactory, routerContextFactory, enhancedPluginRunner);
ClientHttpResponse mockedResponse = new MockClientHttpResponse(new byte[] {}, HttpStatus.OK);
when(loadBalancerClient.execute(eq(calleeService), eq(loadBalancerRequest), any(PolarisRouterContext.class))).thenReturn(mockedResponse);
@ -133,7 +137,7 @@ public class PolarisLoadBalancerInterceptorTest {
when(notRibbonLoadBalancerClient.execute(calleeService, loadBalancerRequest)).thenReturn(mockedResponse);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(
notRibbonLoadBalancerClient, loadBalancerRequestFactory, routerContextFactory);
notRibbonLoadBalancerClient, loadBalancerRequestFactory, routerContextFactory, enhancedPluginRunner);
ClientHttpResponse response = polarisLoadBalancerInterceptor.intercept(request, null, null);

@ -37,6 +37,7 @@ import com.tencent.cloud.polaris.loadbalancer.PolarisLoadBalancer;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedZuulPluginRunner;
import okhttp3.OkHttpClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@ -95,6 +96,9 @@ public class PolarisRibbonRoutingFilterTest {
@Mock
private PolarisLoadBalancer polarisLoadBalancer;
@Mock
private EnhancedZuulPluginRunner enhancedZuulPluginRunner;
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
@ -123,7 +127,7 @@ public class PolarisRibbonRoutingFilterTest {
public void testGenRouterContext() {
PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter(proxyRequestHelper,
ribbonCommandFactory, staticMetadataManager, routerRuleLabelResolver,
Lists.newArrayList(routerLabelResolver));
Lists.newArrayList(routerLabelResolver), enhancedZuulPluginRunner);
Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("env", "blue");
@ -160,7 +164,7 @@ public class PolarisRibbonRoutingFilterTest {
PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter(
new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, staticMetadataManager,
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver));
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver), enhancedZuulPluginRunner);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("http://" + calleeService + "/users");
@ -195,7 +199,7 @@ public class PolarisRibbonRoutingFilterTest {
PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter(
new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, staticMetadataManager,
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver));
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver), enhancedZuulPluginRunner);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("http://" + calleeService + "/users");
@ -231,7 +235,7 @@ public class PolarisRibbonRoutingFilterTest {
PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter(
new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, staticMetadataManager,
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver));
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver), enhancedZuulPluginRunner);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("http://" + calleeService + "/users");
@ -266,7 +270,7 @@ public class PolarisRibbonRoutingFilterTest {
PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter(
new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, staticMetadataManager,
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver));
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver), enhancedZuulPluginRunner);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("http://" + calleeService + "/users");
@ -301,7 +305,7 @@ public class PolarisRibbonRoutingFilterTest {
PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter(
new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, staticMetadataManager,
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver));
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver), enhancedZuulPluginRunner);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("http://" + calleeService + "/users");
@ -338,7 +342,7 @@ public class PolarisRibbonRoutingFilterTest {
PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter(
new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, staticMetadataManager,
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver));
routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver), enhancedZuulPluginRunner);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("http://" + calleeService + "/users");

@ -30,6 +30,10 @@ public final class ContextConstant {
* Name of Polaris.
*/
public static final String POLARIS = "POLARIS";
/**
* Metadata key for target governance namespace.
*/
public static final String POLARIS_GOVERNANCE_TARGET_NAMESPACE = "POLARIS_GOVERNANCE_TARGET_NAMESPACE";
/**
* SCT Default Charset .
@ -46,26 +50,35 @@ public final class ContextConstant {
public static final class Zuul {
/**
* polaris circuit breaker.
*/
public static final String POLARIS_CIRCUIT_BREAKER = "PolarisCircuitBreaker";
/**
* timestamp before route.
*/
public static final String POLARIS_PRE_ROUTE_TIME = "PolarisPreRouteTime";
/**
* is in routing state.
* polaris circuit breaker.
*/
public static final String POLARIS_IS_IN_ROUTING_STATE = "PolarisIsInRoutingState";
public static final String ENHANCED_PLUGIN_CONTEXT = "EnhancedPluginContext";
private Zuul() {
}
}
public static final class CircuitBreaker {
/**
* polaris circuit breaker.
*/
public static final String ENHANCED_PLUGIN_CONTEXT = "EnhancedPluginContext";
public static final String POLARIS_CIRCUIT_BREAKER = "PolarisCircuitBreaker";
/**
* circuit breaker start time.
*/
public static final String CIRCUIT_BREAKER_START_TIME = "CIRCUIT_BREAKER_START_TIME";
/**
* circuit breaker fallback http response.
*/
public static final String CIRCUIT_BREAKER_FALLBACK_HTTP_RESPONSE = "CIRCUIT_BREAKER_FALLBACK_HTTP_RESPONSE";
private CircuitBreaker() {
private Zuul() {
}
}
}

@ -319,4 +319,8 @@ public class MetadataContext extends com.tencent.polaris.metadata.core.manager.M
break;
}
}
public static void setLocalService(String service) {
LOCAL_SERVICE = service;
}
}

@ -45,6 +45,9 @@ public @interface ConditionalOnTsfConsulEnabled {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
if (Boolean.TRUE.toString().equals(environment.getProperty("tsf_consul_enable"))) {
return true;
}
boolean tsfConsulEnable = false;
String tsfAppId = environment.getProperty("tsf_app_id");

@ -19,11 +19,9 @@ package com.tencent.cloud.plugin.discovery.adapter.config;
import com.tencent.cloud.plugin.discovery.adapter.transformer.NacosInstanceTransformer;
import com.tencent.cloud.plugin.discovery.adapter.transformer.NacosRegistrationTransformer;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerAutoConfiguration;
import com.tencent.cloud.polaris.loadbalancer.transformer.InstanceTransformer;
import com.tencent.cloud.rpc.enhancement.transformer.RegistrationTransformer;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
@ -37,7 +35,6 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore(PolarisLoadBalancerAutoConfiguration.class)
public class NacosDiscoveryAdapterAutoConfiguration {
@Bean

@ -0,0 +1,29 @@
/*
* 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.context;
import org.springframework.http.HttpStatus;
import org.springframework.web.client.HttpStatusCodeException;
public class CircuitBreakerStatusCodeException extends HttpStatusCodeException {
public CircuitBreakerStatusCodeException(HttpStatus statusCode) {
super(statusCode);
}
}

@ -109,6 +109,8 @@ public final class TsfCoreEnvironmentPostProcessor implements EnvironmentPostPro
LOGGER.error("tsf_namespace_id is empty");
}
defaultProperties.put("tsf_consul_enable", "true");
// context
defaultProperties.put("spring.cloud.polaris.enabled", "true");
defaultProperties.put("spring.cloud.polaris.discovery.enabled", "false");

@ -17,25 +17,15 @@
package com.tencent.cloud.polaris.loadbalancer.config;
import java.util.ArrayList;
import java.util.List;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.util.CollectionUtils;
/**
* Auto-configuration Ribbon for Polaris.
@ -55,42 +45,4 @@ public class PolarisLoadBalancerAutoConfiguration {
return new PolarisLoadBalancerProperties();
}
@Bean
public RestTemplateCustomizer polarisRestTemplateCustomizer(
@Autowired(required = false) RetryLoadBalancerInterceptor retryLoadBalancerInterceptor,
@Autowired(required = false) LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
// LoadBalancerInterceptor must invoke before EnhancedRestTemplateInterceptor
int addIndex = list.size();
if (CollectionUtils.containsInstance(list, retryLoadBalancerInterceptor) || CollectionUtils.containsInstance(list, loadBalancerInterceptor)) {
ClientHttpRequestInterceptor enhancedRestTemplateInterceptor = null;
for (int i = 0; i < list.size(); i++) {
if (list.get(i) instanceof EnhancedRestTemplateInterceptor) {
enhancedRestTemplateInterceptor = list.get(i);
addIndex = i;
}
}
if (enhancedRestTemplateInterceptor != null) {
list.remove(addIndex);
list.add(enhancedRestTemplateInterceptor);
}
}
else {
if (retryLoadBalancerInterceptor != null || loadBalancerInterceptor != null) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i) instanceof EnhancedRestTemplateInterceptor) {
addIndex = i;
}
}
list.add(addIndex,
retryLoadBalancerInterceptor != null
? retryLoadBalancerInterceptor
: loadBalancerInterceptor);
}
}
restTemplate.setInterceptors(list);
};
}
}

@ -32,7 +32,6 @@ import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.ExceptionPolarisReporter;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor;
import com.tencent.cloud.rpc.enhancement.resttemplate.PolarisLoadBalancerRequestTransformer;
import com.tencent.cloud.rpc.enhancement.scg.EnhancedGatewayGlobalFilter;
import com.tencent.cloud.rpc.enhancement.transformer.InstanceTransformer;
@ -44,7 +43,6 @@ import com.tencent.cloud.rpc.enhancement.webclient.PolarisLoadBalancerClientRequ
import com.tencent.cloud.rpc.enhancement.webclient.RibbonLoadBalancerClientAspect;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedErrorZuulFilter;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedPostZuulFilter;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedPreZuulFilter;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedRouteZuulFilter;
import org.springframework.beans.factory.SmartInitializingSingleton;
@ -187,19 +185,6 @@ public class RpcEnhancementAutoConfiguration {
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public EnhancedRestTemplateInterceptor enhancedPolarisRestTemplateReporter(@Lazy EnhancedPluginRunner pluginRunner) {
return new EnhancedRestTemplateInterceptor(pluginRunner);
}
@Bean
public SmartInitializingSingleton setPolarisReporterForRestTemplate(EnhancedRestTemplateInterceptor reporter) {
return () -> {
for (RestTemplate restTemplate : restTemplates) {
restTemplate.getInterceptors().add(reporter);
}
};
}
@Bean
@ConditionalOnMissingBean
@ -272,11 +257,6 @@ public class RpcEnhancementAutoConfiguration {
@ConditionalOnClass(name = "com.netflix.zuul.http.ZuulServlet")
protected static class PolarisCircuitBreakerZuulFilterConfig {
@Bean
public EnhancedPreZuulFilter enhancedPreZuulFilter(@Lazy EnhancedPluginRunner pluginRunner, Environment environment) {
return new EnhancedPreZuulFilter(pluginRunner, environment);
}
@Bean
public EnhancedRouteZuulFilter enhancedZuulRouteFilter(@Lazy EnhancedPluginRunner pluginRunner, Environment environment) {
return new EnhancedRouteZuulFilter(pluginRunner, environment);

@ -19,16 +19,24 @@ package com.tencent.cloud.rpc.enhancement.feign;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import feign.Client;
import feign.Request;
import feign.Request.Options;
import feign.RequestTemplate;
import feign.Response;
import org.springframework.cloud.client.DefaultServiceInstance;
@ -61,16 +69,19 @@ public class EnhancedFeignClient implements Client {
request.headers().forEach((s, strings) -> requestHeaders.addAll(s, new ArrayList<>(strings)));
URI url = URI.create(request.url());
URI serviceUrl = url.resolve(request.requestTemplate().url());
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
.httpHeaders(requestHeaders)
.httpMethod(HttpMethod.valueOf(request.httpMethod().name()))
.url(url)
.serviceUrl(serviceUrl)
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(request);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());
String svcName = request.requestTemplate().feignTarget().name();
String svcName = serviceUrl.getHost();
DefaultServiceInstance serviceInstance = new DefaultServiceInstance(
String.format("%s-%s-%d", svcName, url.getHost(), url.getPort()),
svcName, url.getHost(), url.getPort(), url.getScheme().equals("https"));
@ -82,11 +93,11 @@ public class EnhancedFeignClient implements Client {
enhancedPluginContext.setTargetServiceInstance(serviceInstance, url);
}
// Run pre enhanced plugins.
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);
long startMillis = System.currentTimeMillis();
try {
// Run pre enhanced plugins.
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);
Response response = delegate.execute(request, options);
enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis);
@ -103,6 +114,10 @@ public class EnhancedFeignClient implements Client {
pluginRunner.run(EnhancedPluginType.Client.POST, enhancedPluginContext);
return response;
}
catch (CallAbortedException callAbortedException) {
// circuit breaker fallback, not need to run post/exception enhanced plugins.
return getFallbackResponse(callAbortedException.getFallbackInfo());
}
catch (IOException origin) {
enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis);
enhancedPluginContext.setThrowable(origin);
@ -115,4 +130,25 @@ public class EnhancedFeignClient implements Client {
pluginRunner.run(EnhancedPluginType.Client.FINALLY, enhancedPluginContext);
}
}
private Response getFallbackResponse(CircuitBreakerStatus.FallbackInfo fallbackInfo) {
Response.Builder responseBuilder = Response.builder()
.status(fallbackInfo.getCode());
if (fallbackInfo.getHeaders() != null) {
Map<String, Collection<String>> headers = new HashMap<>();
fallbackInfo.getHeaders().forEach((k, v) -> headers.put(k, Collections.singleton(v)));
responseBuilder.headers(headers);
}
if (fallbackInfo.getBody() != null) {
responseBuilder.body(fallbackInfo.getBody(), StandardCharsets.UTF_8);
}
// Feign Response need a nonnull Request,
// which is not important in fallback response (no real request),
// so we create a fake one
Request fakeRequest = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), Request.Body.empty(), new RequestTemplate());
responseBuilder.request(fakeRequest);
return responseBuilder.build();
}
}

@ -23,6 +23,7 @@ import java.util.List;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import com.tencent.polaris.client.api.SDKContext;
import org.springframework.cloud.client.DefaultServiceInstance;
@ -74,6 +75,9 @@ public class DefaultEnhancedPluginRunner implements EnhancedPluginRunner {
try {
plugin.run(context);
}
catch (CallAbortedException callAbortedException) {
throw callAbortedException;
}
catch (Throwable throwable) {
plugin.handlerThrowable(context, throwable);
}

@ -35,6 +35,8 @@ public class EnhancedRequestContext {
private URI url;
private URI serviceUrl;
public HttpMethod getHttpMethod() {
return httpMethod;
}
@ -59,6 +61,14 @@ public class EnhancedRequestContext {
this.url = url;
}
public URI getServiceUrl() {
return serviceUrl;
}
public void setServiceUrl(URI serviceUrl) {
this.serviceUrl = serviceUrl;
}
public static EnhancedContextRequestBuilder builder() {
return new EnhancedContextRequestBuilder();
}
@ -76,6 +86,7 @@ public class EnhancedRequestContext {
private HttpMethod httpMethod;
private HttpHeaders httpHeaders;
private URI url;
private URI serviceUrl;
private EnhancedContextRequestBuilder() {
}
@ -95,11 +106,17 @@ public class EnhancedRequestContext {
return this;
}
public EnhancedContextRequestBuilder serviceUrl(URI serviceUrl) {
this.serviceUrl = serviceUrl;
return this;
}
public EnhancedRequestContext build() {
EnhancedRequestContext enhancedRequestContext = new EnhancedRequestContext();
enhancedRequestContext.httpMethod = this.httpMethod;
enhancedRequestContext.url = this.url;
enhancedRequestContext.httpHeaders = this.httpHeaders;
enhancedRequestContext.serviceUrl = this.serviceUrl;
return enhancedRequestContext;
}
}

@ -32,9 +32,11 @@ import java.util.Objects;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.common.constant.HeaderConstant;
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.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.RequestLabelUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
@ -133,7 +135,12 @@ public final class PolarisEnhancedPluginUtils {
public static ResourceStat createInstanceResourceStat(
@Nullable String calleeServiceName, @Nullable String calleeHost, @Nullable Integer calleePort,
URI uri, @Nullable Integer statusCode, long delay, @Nullable Throwable exception) {
ServiceKey calleeServiceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, StringUtils.isBlank(calleeServiceName) ? uri.getHost() : calleeServiceName);
String governanceNamespace = MetadataContextHolder.get().getDisposableMetadata().get(ContextConstant.POLARIS_GOVERNANCE_TARGET_NAMESPACE);
if (StringUtils.isBlank(governanceNamespace)) {
governanceNamespace = MetadataContext.LOCAL_NAMESPACE;
}
ServiceKey calleeServiceKey = new ServiceKey(governanceNamespace, StringUtils.isBlank(calleeServiceName) ? uri.getHost() : calleeServiceName);
ServiceKey callerServiceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE);
Resource resource = new InstanceResource(
calleeServiceKey,

@ -18,18 +18,24 @@
package com.tencent.cloud.rpc.enhancement.resttemplate;
import java.io.IOException;
import java.net.URI;
import java.util.Optional;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import com.tencent.polaris.metadata.core.MetadataObjectValue;
import com.tencent.polaris.metadata.core.MetadataType;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.cloud.client.loadbalancer.ServiceRequestWrapper;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import static com.tencent.cloud.rpc.enhancement.resttemplate.PolarisLoadBalancerRequestTransformer.LOAD_BALANCER_SERVICE_INSTANCE;
@ -39,37 +45,49 @@ import static com.tencent.cloud.rpc.enhancement.resttemplate.PolarisLoadBalancer
*
* @author sean yu
*/
public class EnhancedRestTemplateInterceptor implements ClientHttpRequestInterceptor {
public class EnhancedRestTemplateWrapInterceptor {
private final EnhancedPluginRunner pluginRunner;
public EnhancedRestTemplateInterceptor(EnhancedPluginRunner pluginRunner) {
private final RibbonLoadBalancerClient delegate;
public EnhancedRestTemplateWrapInterceptor(EnhancedPluginRunner pluginRunner,
RibbonLoadBalancerClient delegate) {
this.pluginRunner = pluginRunner;
this.delegate = delegate;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
public ClientHttpResponse intercept(HttpRequest request, String serviceId,
LoadBalancerRequest<ClientHttpResponse> loadBalancerRequest, Object hint) throws IOException {
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
URI serviceUrl = request.getURI();
if (request instanceof ServiceRequestWrapper) {
serviceUrl = ((ServiceRequestWrapper) request).getRequest().getURI();
}
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
.httpHeaders(request.getHeaders())
.httpMethod(request.getMethod())
.url(request.getURI())
.serviceUrl(serviceUrl)
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(request);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());
enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get()
.getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), request.getURI());
// Run pre enhanced plugins.
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);
long startMillis = System.currentTimeMillis();
try {
ClientHttpResponse response = execution.execute(request, body);
ClientHttpResponse response = delegate.execute(serviceId, loadBalancerRequest, hint);
// get target instance after execute
enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get()
.getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), request.getURI());
enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis);
EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder()
@ -80,6 +98,20 @@ public class EnhancedRestTemplateInterceptor implements ClientHttpRequestInterce
// Run post enhanced plugins.
pluginRunner.run(EnhancedPluginType.Client.POST, enhancedPluginContext);
MetadataObjectValue<Object> fallbackResponseValue = MetadataContextHolder.get().
getMetadataContainer(MetadataType.APPLICATION, true).
getMetadataValue(ContextConstant.CircuitBreaker.CIRCUIT_BREAKER_FALLBACK_HTTP_RESPONSE);
boolean existFallback = Optional.ofNullable(fallbackResponseValue).
map(MetadataObjectValue::getObjectValue).map(Optional::isPresent).orElse(false);
if (existFallback) {
Object fallbackResponse = fallbackResponseValue.getObjectValue().orElse(null);
if (fallbackResponse instanceof ClientHttpResponse) {
return (ClientHttpResponse) fallbackResponse;
}
}
return response;
}
catch (IOException e) {

@ -18,6 +18,9 @@
package com.tencent.cloud.rpc.enhancement.scg;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
@ -25,6 +28,8 @@ import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.DefaultServiceInstance;
@ -32,6 +37,9 @@ import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
@ -52,18 +60,37 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange originExchange, GatewayFilterChain chain) {
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
.httpHeaders(originExchange.getRequest().getHeaders())
.httpMethod(originExchange.getRequest().getMethod())
.url(originExchange.getRequest().getURI())
.serviceUrl(getServiceUri(originExchange))
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(originExchange);
// Run pre enhanced plugins.
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);
try {
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);
}
catch (CallAbortedException e) {
if (e.getFallbackInfo() == null) {
throw e;
}
// circuit breaker fallback, not need to run post/exception enhanced plugins.
ServerHttpResponse response = originExchange.getResponse();
HttpStatus httpStatus = HttpStatus.resolve(e.getFallbackInfo().getCode());
response.setStatusCode(httpStatus != null ? httpStatus : HttpStatus.INTERNAL_SERVER_ERROR);
if (CollectionUtils.isNotEmpty(e.getFallbackInfo().getHeaders())) {
e.getFallbackInfo().getHeaders().forEach(response.getHeaders()::set);
}
String body = Optional.of(e.getFallbackInfo().getBody()).orElse("");
DataBuffer dataBuffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}
// Exchange may be changed in plugin
ServerWebExchange exchange = (ServerWebExchange) enhancedPluginContext.getOriginRequest();
long startTime = System.currentTimeMillis();
@ -113,4 +140,15 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered {
public int getOrder() {
return OrderConstant.Client.Scg.ENHANCED_FILTER_ORDER;
}
private URI getServiceUri(ServerWebExchange originExchange) {
Route routeAttr = originExchange.getAttribute(GATEWAY_ROUTE_ATTR);
try {
return new URI(originExchange.getRequest().getURI().getScheme(),
routeAttr.getUri().getHost(), originExchange.getRequest().getURI().getPath(), originExchange.getRequest().getURI().getRawQuery());
}
catch (URISyntaxException e) {
return null;
}
}
}

@ -22,11 +22,8 @@ import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Enumeration;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.util.ZuulFilterUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
@ -35,49 +32,25 @@ import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.StringUtils;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* Polaris circuit breaker implement in Zuul.
*
* @author Haotian Zhang
*/
public class EnhancedPreZuulFilter extends ZuulFilter {
public class EnhancedZuulPluginRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedPreZuulFilter.class);
private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedZuulPluginRunner.class);
private final EnhancedPluginRunner pluginRunner;
private final Environment environment;
public EnhancedPreZuulFilter(EnhancedPluginRunner pluginRunner, Environment environment) {
public EnhancedZuulPluginRunner(EnhancedPluginRunner pluginRunner) {
this.pluginRunner = pluginRunner;
this.environment = environment;
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return OrderConstant.Client.Zuul.ENHANCED_ROUTE_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
String enabled = environment.getProperty("spring.cloud.tencent.rpc-enhancement.reporter");
return StringUtils.isEmpty(enabled) || enabled.equals("true");
}
@Override
public Object run() throws ZuulException {
public void run() {
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
RequestContext context = RequestContext.getCurrentContext();
context.set(ContextConstant.Zuul.ENHANCED_PLUGIN_CONTEXT, enhancedPluginContext);
@ -96,6 +69,7 @@ public class EnhancedPreZuulFilter extends ZuulFilter {
.httpHeaders(requestHeaders)
.httpMethod(HttpMethod.resolve(context.getRequest().getMethod()))
.url(uri)
.serviceUrl(uri)
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
@ -108,6 +82,5 @@ public class EnhancedPreZuulFilter extends ZuulFilter {
catch (URISyntaxException e) {
LOGGER.error("Generate URI failed.", e);
}
return null;
}
}

@ -22,7 +22,6 @@ import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignBeanPostProcessor;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.ExceptionPolarisReporter;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -58,7 +57,6 @@ public class RpcEnhancementAutoConfigurationTest {
assertThat(context).hasSingleBean(EnhancedFeignBeanPostProcessor.class);
assertThat(context).hasSingleBean(SuccessPolarisReporter.class);
assertThat(context).hasSingleBean(ExceptionPolarisReporter.class);
assertThat(context).hasSingleBean(EnhancedRestTemplateInterceptor.class);
assertThat(context).hasSingleBean(RestTemplate.class);
});
}

@ -18,6 +18,7 @@
package com.tencent.cloud.rpc.enhancement.plugin;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
@ -28,6 +29,9 @@ import java.util.HashMap;
import com.tencent.cloud.common.constant.HeaderConstant;
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.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.plugin.circuitbreaker.ResourceStat;
@ -70,7 +74,7 @@ public class PolarisEnhancedPluginUtilsTest {
private SDKContext sdkContext;
@BeforeAll
static void beforeAll() {
static void beforeAll() throws Exception {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
@ -80,6 +84,12 @@ public class PolarisEnhancedPluginUtilsTest {
.when(applicationContext).getBean(RpcEnhancementReporterProperties.class);
mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext)
.thenReturn(applicationContext);
StaticMetadataManager metadataManager = new StaticMetadataManager(new MetadataLocalProperties(), null);
Field field = MetadataContextHolder.class.getDeclaredField("staticMetadataManager");
field.setAccessible(true);
field.set(null, metadataManager);
}
@AfterAll

@ -1,126 +0,0 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.rpc.enhancement.resttemplate;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
@ExtendWith(MockitoExtension.class)
public class EnhancedRestTemplateInterceptorTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private RpcEnhancementReporterProperties reporterProperties;
@Mock
private SDKContext sdkContext;
@Mock
Registration registration;
@Mock
private ClientHttpRequestExecution mockClientHttpRequestExecution;
@Mock
private ClientHttpResponse mockClientHttpResponse;
@Mock
private HttpRequest mockHttpRequest;
@Mock
private HttpHeaders mockHttpHeaders;
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
ApplicationContext applicationContext = mock(ApplicationContext.class);
MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class);
StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class);
doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class);
doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class);
mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext).thenReturn(applicationContext);
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
}
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void testRun() throws IOException, URISyntaxException {
ClientHttpResponse actualResult;
final byte[] inputBody = null;
URI uri = new URI("http://0.0.0.0/");
doReturn(uri).when(mockHttpRequest).getURI();
doReturn(HttpMethod.GET).when(mockHttpRequest).getMethod();
doReturn(mockHttpHeaders).when(mockHttpRequest).getHeaders();
doReturn(mockClientHttpResponse).when(mockClientHttpRequestExecution).execute(mockHttpRequest, inputBody);
EnhancedRestTemplateInterceptor reporter = new EnhancedRestTemplateInterceptor(new DefaultEnhancedPluginRunner(new ArrayList<>(), registration, null));
actualResult = reporter.intercept(mockHttpRequest, inputBody, mockClientHttpRequestExecution);
assertThat(actualResult).isEqualTo(mockClientHttpResponse);
actualResult = reporter.intercept(mockHttpRequest, inputBody, mockClientHttpRequestExecution);
assertThat(actualResult).isEqualTo(mockClientHttpResponse);
doThrow(new SocketTimeoutException()).when(mockClientHttpRequestExecution).execute(mockHttpRequest, inputBody);
assertThatThrownBy(() -> reporter.intercept(mockHttpRequest, inputBody, mockClientHttpRequestExecution)).isInstanceOf(SocketTimeoutException.class);
}
}
Loading…
Cancel
Save