feat:support webclient and gateway report call metrics (#924)

Co-authored-by: Haotian Zhang <928016560@qq.com>
pull/960/head
liaochuntao 2 years ago committed by GitHub
parent f82856fda2
commit bcc7b6635f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,6 @@
# Change Log
---
- [feat:support webclient and gateway report call metrics](https://github.com/Tencent/spring-cloud-tencent/pull/924)
- [feature: optimize polaris-discovery-example/discovery-callee-service, add client-ip return.](https://github.com/Tencent/spring-cloud-tencent/pull/939)
- [docs:prevent the release of the final version of the sdk.](https://github.com/Tencent/spring-cloud-tencent/pull/943)

@ -22,6 +22,8 @@ import java.util.function.Supplier;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisResultToErrorCode;
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.FunctionalDecorator;
@ -43,10 +45,18 @@ public class PolarisCircuitBreaker implements CircuitBreaker {
private final FunctionalDecorator decorator;
public PolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CircuitBreakAPI circuitBreakAPI) {
private final PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf;
private final ConsumerAPI consumerAPI;
public PolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf,
ConsumerAPI consumerAPI,
CircuitBreakAPI circuitBreakAPI) {
FunctionalDecoratorRequest makeDecoratorRequest = new FunctionalDecoratorRequest(new ServiceKey(conf.getNamespace(), conf.getService()), conf.getMethod());
makeDecoratorRequest.setSourceService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService()));
makeDecoratorRequest.setResultToErrorCode(new PolarisResultToErrorCode());
this.consumerAPI = consumerAPI;
this.conf = conf;
this.decorator = circuitBreakAPI.makeFunctionalDecorator(makeDecoratorRequest);
}
@ -58,6 +68,7 @@ public class PolarisCircuitBreaker implements CircuitBreaker {
}
catch (CallAbortedException e) {
LOGGER.debug("PolarisCircuitBreaker CallAbortedException: {}", e.getMessage());
PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, e);
return fallback.apply(e);
}
catch (Exception e) {

@ -21,6 +21,7 @@ import java.util.function.Function;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
@ -47,15 +48,18 @@ public class PolarisCircuitBreakerFactory
private final CircuitBreakAPI circuitBreakAPI;
public PolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) {
private final ConsumerAPI consumerAPI;
public PolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {
this.circuitBreakAPI = circuitBreakAPI;
this.consumerAPI = consumerAPI;
}
@Override
public CircuitBreaker create(String id) {
PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = getConfigurations()
.computeIfAbsent(id, defaultConfiguration);
return new PolarisCircuitBreaker(conf, circuitBreakAPI);
return new PolarisCircuitBreaker(conf, consumerAPI, circuitBreakAPI);
}
@Override

@ -22,11 +22,14 @@ 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;
@ -41,10 +44,18 @@ public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker {
private final InvokeHandler invokeHandler;
public ReactivePolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CircuitBreakAPI circuitBreakAPI) {
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.getMethod());
requestContext.setSourceService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService()));
requestContext.setResultToErrorCode(new PolarisResultToErrorCode());
this.consumerAPI = consumerAPI;
this.conf = conf;
this.invokeHandler = circuitBreakAPI.makeInvokeHandler(requestContext);
}
@ -53,7 +64,12 @@ public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker {
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(fallback);
toReturn = toReturn.onErrorResume(throwable -> {
if (throwable instanceof CallAbortedException) {
PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, (CallAbortedException) throwable);
}
return fallback.apply(throwable);
});
}
return toReturn;
}
@ -62,7 +78,12 @@ public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker {
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(fallback);
toReturn = toReturn.onErrorResume(throwable -> {
if (throwable instanceof CallAbortedException) {
PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, (CallAbortedException) throwable);
}
return fallback.apply(throwable);
});
}
return toReturn;
}

@ -21,6 +21,7 @@ import java.util.function.Function;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
@ -46,8 +47,11 @@ public class ReactivePolarisCircuitBreakerFactory extends
private final CircuitBreakAPI circuitBreakAPI;
public ReactivePolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) {
private final ConsumerAPI consumerAPI;
public ReactivePolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {
this.circuitBreakAPI = circuitBreakAPI;
this.consumerAPI = consumerAPI;
}
@ -55,7 +59,7 @@ public class ReactivePolarisCircuitBreakerFactory extends
public ReactiveCircuitBreaker create(String id) {
PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = getConfigurations()
.computeIfAbsent(id, defaultConfiguration);
return new ReactivePolarisCircuitBreaker(conf, circuitBreakAPI);
return new ReactivePolarisCircuitBreaker(conf, consumerAPI, circuitBreakAPI);
}
@Override

@ -25,6 +25,7 @@ import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModif
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerRestTemplateBeanPostProcessor;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory;
import com.tencent.polaris.client.api.SDKContext;
@ -62,8 +63,8 @@ public class PolarisCircuitBreakerAutoConfiguration {
@Bean
@ConditionalOnMissingBean(CircuitBreakerFactory.class)
public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) {
PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(circuitBreakAPI);
public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {
PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI);
customizers.forEach(customizer -> customizer.customize(factory));
return factory;
}

@ -24,6 +24,7 @@ import com.tencent.cloud.polaris.circuitbreaker.ReactivePolarisCircuitBreakerFac
import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory;
import com.tencent.polaris.client.api.SDKContext;
@ -60,8 +61,8 @@ public class ReactivePolarisCircuitBreakerAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ReactiveCircuitBreakerFactory.class)
public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) {
ReactivePolarisCircuitBreakerFactory factory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI);
public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {
ReactivePolarisCircuitBreakerFactory factory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI);
customizers.forEach(customizer -> customizer.customize(factory));
return factory;
}

@ -17,7 +17,17 @@
package com.tencent.cloud.polaris.circuitbreaker.util;
import java.util.Objects;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
@ -28,6 +38,8 @@ import org.springframework.util.Assert;
*/
public final class PolarisCircuitBreakerUtils {
private static final Logger LOG = LoggerFactory.getLogger(PolarisCircuitBreakerUtils.class);
private PolarisCircuitBreakerUtils() {
}
@ -43,12 +55,33 @@ public final class PolarisCircuitBreakerUtils {
Assert.hasText(id, "A CircuitBreaker must have an id. Id could be : namespace#service#method or service#method or service");
String[] polarisCircuitBreakerMetaData = id.split("#");
if (polarisCircuitBreakerMetaData.length == 2) {
return new String[]{MetadataContext.LOCAL_NAMESPACE, polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1]};
return new String[] {MetadataContext.LOCAL_NAMESPACE, polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1]};
}
if (polarisCircuitBreakerMetaData.length == 3) {
return new String[]{polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1], polarisCircuitBreakerMetaData[2]};
return new String[] {polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1], polarisCircuitBreakerMetaData[2]};
}
return new String[] {MetadataContext.LOCAL_NAMESPACE, id, ""};
}
public static void reportStatus(ConsumerAPI consumerAPI,
PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CallAbortedException e) {
try {
ServiceCallResult result = new ServiceCallResult();
result.setMethod(conf.getMethod());
result.setNamespace(conf.getNamespace());
result.setService(conf.getService());
result.setRuleName(e.getRuleName());
result.setRetStatus(RetStatus.RetReject);
result.setCallerService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService()));
if (Objects.nonNull(e.getFallbackInfo())) {
result.setRetCode(e.getFallbackInfo().getCode());
}
consumerAPI.updateServiceCallResult(result);
}
catch (Throwable ex) {
LOG.error("[CircuitBreaker] report circuitbreaker call result fail ", ex);
}
return new String[]{MetadataContext.LOCAL_NAMESPACE, id, ""};
}
}

@ -39,6 +39,7 @@ 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.factory.CircuitBreakAPIFactory;
import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
import com.tencent.polaris.test.common.TestUtils;
@ -175,9 +176,9 @@ public class PolarisCircuitBreakerIntegrationTest {
@Bean
@PolarisCircuitBreaker(fallback = "fallback")
public RestTemplate defaultRestTemplate(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) {
public RestTemplate defaultRestTemplate(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
RestTemplate defaultRestTemplate = new RestTemplate();
EnhancedRestTemplateReporter enhancedRestTemplateReporter = new EnhancedRestTemplateReporter(properties, consumerAPI);
EnhancedRestTemplateReporter enhancedRestTemplateReporter = new EnhancedRestTemplateReporter(properties, context, consumerAPI);
defaultRestTemplate.setErrorHandler(enhancedRestTemplateReporter);
return defaultRestTemplate;
}

@ -31,10 +31,12 @@ import java.util.stream.Collectors;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.polaris.api.config.Configuration;
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.factory.CircuitBreakAPIFactory;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.factory.api.DiscoveryAPIFactory;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
import com.tencent.polaris.test.common.TestUtils;
import com.tencent.polaris.test.mock.discovery.NamingServer;
@ -104,8 +106,9 @@ public class PolarisCircuitBreakerMockServerTest {
public void testCircuitBreaker() {
Configuration configuration = TestUtils.configWithEnvAddress();
CircuitBreakAPI circuitBreakAPI = CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration);
ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPIByConfig(configuration);
PolarisCircuitBreakerFactory polarisCircuitBreakerFactory = new PolarisCircuitBreakerFactory(circuitBreakAPI);
PolarisCircuitBreakerFactory polarisCircuitBreakerFactory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI);
CircuitBreaker cb = polarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER);
// trigger fallback for 5 times
@ -126,7 +129,7 @@ public class PolarisCircuitBreakerMockServerTest {
assertThat(resList).isEqualTo(Arrays.asList("invoke success", "fallback", "fallback", "fallback", "fallback"));
// always fallback
ReactivePolarisCircuitBreakerFactory reactivePolarisCircuitBreakerFactory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI);
ReactivePolarisCircuitBreakerFactory reactivePolarisCircuitBreakerFactory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI);
ReactiveCircuitBreaker rcb = reactivePolarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER);
assertThat(Mono.just("foobar").transform(it -> rcb.run(it, t -> Mono.just("fallback")))

@ -0,0 +1,71 @@
/*
* 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.circuitbreaker.util;
import java.util.HashMap;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
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.mockito.MockedStatic;
import org.mockito.Mockito;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.mockito.ArgumentMatchers.anyString;
public class PolarisCircuitBreakerUtilsTests {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
}
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void testReportStatus() {
ConsumerAPI consumerAPI = Mockito.mock(ConsumerAPI.class);
Mockito.doNothing().when(consumerAPI).updateServiceCallResult(new ServiceCallResult());
PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = new PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration();
PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, new CallAbortedException("mock", new CircuitBreakerStatus.FallbackInfo(0, new HashMap<>(), "")));
}
}

@ -18,6 +18,10 @@
package com.tencent.cloud.polaris.loadbalancer;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.loadbalancer.reactive.PolarisLoadBalancerClientRequestTransformer;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.factory.api.DiscoveryAPIFactory;
import com.tencent.polaris.router.api.core.RouterAPI;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -29,6 +33,7 @@ import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerClientRequestTransformer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
@ -65,6 +70,13 @@ public class PolarisLoadBalancerClientConfiguration {
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), routerAPI);
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerClientRequestTransformer polarisLoadBalancerClientRequestTransformer(SDKContext sdkContext) {
ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPIByContext(sdkContext);
return new PolarisLoadBalancerClientRequestTransformer(consumerAPI);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnReactiveDiscoveryEnabled
@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER)

@ -0,0 +1,44 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.loadbalancer.reactive;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.polaris.api.core.ConsumerAPI;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerClientRequestTransformer;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.client.ClientRequest;
public class PolarisLoadBalancerClientRequestTransformer implements LoadBalancerClientRequestTransformer {
private final ConsumerAPI consumerAPI;
public PolarisLoadBalancerClientRequestTransformer(ConsumerAPI consumerAPI) {
this.consumerAPI = consumerAPI;
}
@Override
public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) {
if (instance != null) {
HttpHeaders headers = request.headers();
headers.add(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, instance.getServiceId());
}
return request;
}
}

@ -25,6 +25,7 @@ import java.util.Set;
import javax.annotation.PostConstruct;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -32,6 +33,7 @@ import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiv
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback;
import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils;
import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
@ -116,6 +118,10 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
dataBuffer = response.bufferFactory().allocateBuffer()
.write(rejectTips.getBytes(StandardCharsets.UTF_8));
}
response.getHeaders().add(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc());
if (Objects.nonNull(quotaResponse.getActiveRule())) {
response.getHeaders().add(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, quotaResponse.getActiveRule().getName().getValue());
}
return response.writeWith(Mono.just(dataBuffer));
}
// Unirate

@ -28,6 +28,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -35,6 +36,7 @@ import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServlet
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback;
import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils;
import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
@ -111,6 +113,10 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(rejectTips);
}
response.addHeader(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc());
if (Objects.nonNull(quotaResponse.getActiveRule())) {
response.addHeader(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, quotaResponse.getActiveRule().getName().getValue());
}
return;
}
// Unirate

@ -94,7 +94,9 @@ public class QuotaCheckReactiveFilterTest {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
QuotaResponse response = new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
response.setActiveRule(RateLimitProto.Rule.newBuilder().build());
return response;
}
else {
return new QuotaResponse(new QuotaResult(null, 0, null));

@ -94,7 +94,9 @@ public class QuotaCheckServletFilterTest {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
QuotaResponse response = new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
response.setActiveRule(RateLimitProto.Rule.newBuilder().build());
return response;
}
else {
return new QuotaResponse(new QuotaResult(null, 0, null));

@ -0,0 +1,52 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.common.constant;
/**
* Built-in system http header fields.
*/
public final class HeaderConstant {
/**
* The called service returns the real call result of its own processing request.
*/
public static final String INTERNAL_CALLEE_RET_STATUS = "internal-callee-retstatus";
/**
* The name of the rule that the current limit/circiutbreaker rule takes effect.
*/
public static final String INTERNAL_ACTIVE_RULE_NAME = "internal-callee-activerule";
/**
* The name information of the called service.
*/
public static final String INTERNAL_CALLEE_SERVICE_ID = "internal-callee-serviceid";
/**
* The name information of the called instance host.
*/
public static final String INTERNAL_CALLEE_INSTANCE_HOST = "internal-callee-instance-host";
/**
* The name information of the called instance port.
*/
public static final String INTERNAL_CALLEE_INSTANCE_PORT = "internal-callee-instance-port";
private HeaderConstant() {
}
}

@ -0,0 +1,34 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.common.util;
/**
* Request Label Utils.
*/
public final class RequestLabelUtils {
private RequestLabelUtils() {
}
public static String convertLabel(String label) {
label = label.replaceAll("\"|\\{|\\}", "")
.replaceAll(",", "|");
return label;
}
}

@ -27,12 +27,14 @@
- 在北极星服务端可以通过控制台在命名空间Production下添加服务RateLimitCalleeService。
- 启动服务被调方:
1. IDE直接启动找到主类 `RateLimitCalleeService`,执行 main 方法启动应用。
2. 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar ratelimit-callee-sevice-${verion}.jar`启动应用。
2. 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包
- 执行 `java -jar ratelimit-callee-sevice-${verion}.jar`启动应用
- 执行 `java -jar ratelimit-caller-sevice-${verion}.jar`启动应用
- 启动后,可以在北极星控制台上看到注册上来的服务实例信息。
3. 调用服务
通过浏览器访问http://127.0.0.1:48081/business/invoke可以看到以下输出信息
通过浏览器访问http://127.0.0.1:58080/business/invoke可以看到以下输出信息
````
hello world for ratelimit service 1
hello world for ratelimit service 2

@ -32,13 +32,14 @@ Examples provided by Polaris all support to run at IDE, or compile and run with
- Launch callee server:
1. Launch IDE directly: First find `RateLimitCalleeService`, execute main then launch application
2. compile package then launch: first execute `mvn clean package` compile the package, then execute `java -jar ratelimit-callee-sevice-${verion}.jar` execute the application
2. compile package then launch: first execute `mvn clean package` compile the package
- then execute `java -jar ratelimit-callee-sevice-${verion}.jar` execute the application
- then execute `java -jar ratelimit-caller-sevice-${verion}.jar` execute the application
- After launching, one can watch server instance from Polaris control panel
3. Invoke Service
After visiting http://127.0.0.1:48081/business/invoke, one can see the following information:
After visiting http://127.0.0.1:58080/business/invoke, one can see the following information:
````
hello world for ratelimit service 1
@ -61,7 +62,7 @@ Examples provided by Polaris all support to run at IDE, or compile and run with
![](polaris-ratelimit-ui.png)
5. Verify rate limit result
continue visit http://127.0.0.1:48081/business/invoke, one can see, after 10 invokes, rate limit will start:
continue visit http://127.0.0.1:68080/business/invoke, one can see, after 10 invokes, rate limit will start:
````
hello world for ratelimit service 1

@ -16,6 +16,7 @@
<modules>
<module>ratelimit-callee-service</module>
<module>ratelimit-caller-service</module>
</modules>
<dependencies>

@ -48,6 +48,7 @@ import org.springframework.web.reactive.function.client.WebClientResponseExcepti
*
* @author Haotian Zhang
*/
@Deprecated
@RestController
@RequestMapping("/business")
public class BusinessController {

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>polaris-ratelimit-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>ratelimit-caller-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,171 @@
/*
* 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.ratelimit.example.service.caller;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
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.HttpClientErrorException.TooManyRequests;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
@RestController
@RequestMapping("/business")
public class Controller {
private static final Logger LOG = LoggerFactory.getLogger(Controller.class);
private final AtomicInteger index = new AtomicInteger(0);
private final AtomicLong lastTimestamp = new AtomicLong(0);
@Autowired
private RestTemplate restTemplate;
@Autowired
private WebClient.Builder webClientBuilder;
private String appName = "RateLimitCalleeService";
/**
* Get information.
* @return information
*/
@GetMapping("/info")
public String info() {
return "hello world for ratelimit service " + index.incrementAndGet();
}
@GetMapping("/info/webclient")
public Mono<String> infoWebClient() {
return Mono.just("hello world for ratelimit service " + index.incrementAndGet());
}
@GetMapping("/invoke/webclient")
public String invokeInfoWebClient() throws InterruptedException, ExecutionException {
StringBuffer builder = new StringBuffer();
WebClient webClient = webClientBuilder.baseUrl("http://" + appName).build();
List<Mono<String>> monoList = new ArrayList<>();
for (int i = 0; i < 30; i++) {
Mono<String> response = webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/business/info/webclient")
.queryParam("yyy", "yyy")
.build()
)
.header("xxx", "xxx")
.retrieve()
.bodyToMono(String.class)
.doOnSuccess(s -> builder.append(s + "\n"))
.doOnError(e -> {
if (e instanceof WebClientResponseException) {
if (((WebClientResponseException) e).getRawStatusCode() == 429) {
builder.append("TooManyRequests ").append(index.incrementAndGet() + "\n");
}
}
})
.onErrorReturn("");
monoList.add(response);
}
for (Mono<String> mono : monoList) {
mono.toFuture().get();
}
index.set(0);
return builder.toString();
}
/**
* Get information 30 times per 1 second.
*
* @return result of 30 calls.
* @throws InterruptedException exception
*/
@GetMapping("/invoke")
public String invokeInfo() throws InterruptedException {
StringBuffer builder = new StringBuffer();
CountDownLatch count = new CountDownLatch(30);
for (int i = 0; i < 30; i++) {
new Thread(() -> {
try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("xxx", "xxx");
ResponseEntity<String> entity = restTemplate.exchange(
"http://" + appName + "/business/info?yyy={yyy}",
HttpMethod.GET,
new HttpEntity<>(httpHeaders),
String.class,
"yyy"
);
builder.append(entity.getBody() + "\n");
}
catch (RestClientException e) {
if (e instanceof TooManyRequests) {
builder.append("TooManyRequests ").append(index.incrementAndGet() + "\n");
}
else {
throw e;
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
count.countDown();
}
}).start();
}
count.await();
index.set(0);
return builder.toString();
}
/**
* Get information with unirate.
*
* @return information
*/
@GetMapping("/unirate")
public String unirate() {
long currentTimestamp = System.currentTimeMillis();
long lastTime = lastTimestamp.get();
if (lastTime != 0) {
LOG.info("Current timestamp:" + currentTimestamp + ", diff from last timestamp:" + (currentTimestamp - lastTime));
}
else {
LOG.info("Current timestamp:" + currentTimestamp);
}
lastTimestamp.set(currentTimestamp);
return "hello world for ratelimit service with diff from last request:" + (currentTimestamp - lastTime) + "ms.";
}
}

@ -0,0 +1,52 @@
/*
* 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.ratelimit.example.service.caller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
@SpringBootApplication
public class RateLimitCallerService {
private static final Logger LOG = LoggerFactory.getLogger(RateLimitCallerService.class);
public static void main(String[] args) {
SpringApplication.run(RateLimitCallerService.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@LoadBalanced
@Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}

@ -0,0 +1,20 @@
server:
port: 58080
spring:
application:
name: RateLimitCallerService
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
management:
endpoints:
web:
exposure:
include:
- polaris-ratelimit
logging:
level:
com.tencent.cloud.polaris: debug

@ -50,12 +50,24 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-test-common</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

@ -19,13 +19,18 @@ package com.tencent.cloud.rpc.enhancement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.utils.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
@ -116,4 +121,31 @@ public abstract class AbstractPolarisReporterAdapter {
// DEFAULT RETURN FALSE.
return false;
}
protected RetStatus getRetStatusFromRequest(HttpHeaders headers, RetStatus defaultVal) {
if (headers.containsKey(HeaderConstant.INTERNAL_CALLEE_RET_STATUS)) {
List<String> values = headers.get(HeaderConstant.INTERNAL_CALLEE_RET_STATUS);
if (CollectionUtils.isNotEmpty(values)) {
String retStatusVal = com.tencent.polaris.api.utils.StringUtils.defaultString(values.get(0));
if (Objects.equals(retStatusVal, RetStatus.RetFlowControl.getDesc())) {
return RetStatus.RetFlowControl;
}
if (Objects.equals(retStatusVal, RetStatus.RetReject.getDesc())) {
return RetStatus.RetReject;
}
}
}
return defaultVal;
}
protected String getActiveRuleNameFromRequest(HttpHeaders headers) {
if (headers.containsKey(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME)) {
Collection<String> values = headers.get(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME);
if (CollectionUtils.isNotEmpty(values)) {
String val = com.tencent.polaris.api.utils.StringUtils.defaultString(new ArrayList<>(values).get(0));
return val;
}
}
return "";
}
}

@ -30,7 +30,11 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.ExceptionPolarisR
import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.SuccessPolarisReporter;
import com.tencent.cloud.rpc.enhancement.resttemplate.BlockingLoadBalancerClientAspect;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter;
import com.tencent.cloud.rpc.enhancement.scg.EnhancedPolarisHttpClientCustomizer;
import com.tencent.cloud.rpc.enhancement.scg.EnhancedPolarisHttpHeadersFilter;
import com.tencent.cloud.rpc.enhancement.webclient.EnhancedWebClientReporter;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.client.api.SDKContext;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
@ -42,11 +46,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.gateway.config.HttpClientCustomizer;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Role;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
/**
* Auto Configuration for Polaris {@link feign.Feign} OR {@link RestTemplate} which can automatically bring in the call
@ -89,14 +96,16 @@ public class RpcEnhancementAutoConfiguration {
@Bean
public SuccessPolarisReporter successPolarisReporter(RpcEnhancementReporterProperties properties,
@Autowired(required = false) SDKContext context,
@Autowired(required = false) ConsumerAPI consumerAPI) {
return new SuccessPolarisReporter(properties, consumerAPI);
return new SuccessPolarisReporter(properties, context, consumerAPI);
}
@Bean
public ExceptionPolarisReporter exceptionPolarisReporter(RpcEnhancementReporterProperties properties,
@Autowired(required = false) SDKContext context,
@Autowired(required = false) ConsumerAPI consumerAPI) {
return new ExceptionPolarisReporter(properties, consumerAPI);
return new ExceptionPolarisReporter(properties, context, consumerAPI);
}
}
}
@ -117,8 +126,8 @@ public class RpcEnhancementAutoConfiguration {
@Bean
public EnhancedRestTemplateReporter enhancedRestTemplateReporter(
RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) {
return new EnhancedRestTemplateReporter(properties, consumerAPI);
RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
return new EnhancedRestTemplateReporter(properties, context, consumerAPI);
}
@Bean
@ -137,4 +146,43 @@ public class RpcEnhancementAutoConfiguration {
return new BlockingLoadBalancerClientAspect();
}
}
/**
* Configuration for Polaris {@link org.springframework.web.reactive.function.client.WebClient} which can automatically bring in the call
* results for reporting.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.web.reactive.function.client.WebClient")
protected static class PolarisWebClientAutoConfiguration {
@Bean
public ExchangeFilterFunction exchangeFilterFunction(
RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
return new EnhancedWebClientReporter(properties, context, consumerAPI);
}
}
/**
* Configuration for Polaris {@link org.springframework.web.reactive.function.client.WebClient} which can automatically bring in the call
* results for reporting.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.cloud.gateway.config.GatewayAutoConfiguration")
@Role(RootBeanDefinition.ROLE_INFRASTRUCTURE)
protected static class PolarisGatewayAutoConfiguration {
@Bean
@ConditionalOnClass(name = {"org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter"})
public HttpHeadersFilter enhancedPolarisHttpHeadersFilter() {
return new EnhancedPolarisHttpHeadersFilter();
}
@Bean
@ConditionalOnClass(name = {"org.springframework.cloud.gateway.config.HttpClientCustomizer"})
public HttpClientCustomizer httpClientCustomizer(
RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
return new EnhancedPolarisHttpClientCustomizer(properties, context, consumerAPI);
}
}
}

@ -18,7 +18,9 @@
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
@ -26,28 +28,36 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import feign.Request;
import feign.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
/**
* Polaris reporter when feign call fails.
*
* @author Haotian Zhang
*/
public class ExceptionPolarisReporter implements EnhancedFeignPlugin {
public class ExceptionPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedFeignPlugin {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionPolarisReporter.class);
private final RpcEnhancementReporterProperties reporterProperties;
private final ConsumerAPI consumerAPI;
private final SDKContext context;
public ExceptionPolarisReporter(RpcEnhancementReporterProperties reporterProperties,
SDKContext context,
ConsumerAPI consumerAPI) {
super(reporterProperties);
this.reporterProperties = reporterProperties;
this.context = context;
this.consumerAPI = consumerAPI;
}
@ -78,7 +88,13 @@ public class ExceptionPolarisReporter implements EnhancedFeignPlugin {
}
LOG.debug("Will report result of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", retStatus.name(), request.httpMethod()
.name(), request.url(), response.status(), delay);
ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(request, response, delay, retStatus);
ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(this.context, request, response,
delay, retStatus, serviceCallResult -> {
HttpHeaders headers = new HttpHeaders();
response.headers().forEach((s, strings) -> headers.addAll(s, new ArrayList<>(strings)));
serviceCallResult.setRetStatus(getRetStatusFromRequest(headers, serviceCallResult.getRetStatus()));
serviceCallResult.setRuleName(getActiveRuleNameFromRequest(headers));
});
consumerAPI.updateServiceCallResult(resultRequest);
}
}

@ -21,13 +21,17 @@ import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Consumer;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.RequestLabelUtils;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.client.api.SDKContext;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
@ -49,7 +53,8 @@ public final class ReporterUtils {
private ReporterUtils() {
}
public static ServiceCallResult createServiceCallResult(final Request request, final Response response, long delay, RetStatus retStatus) {
public static ServiceCallResult createServiceCallResult(final SDKContext context, final Request request,
final Response response, long delay, RetStatus retStatus, final Consumer<ServiceCallResult> consumer) {
ServiceCallResult resultRequest = new ServiceCallResult();
resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE);
@ -65,7 +70,7 @@ public final class ReporterUtils {
catch (UnsupportedEncodingException e) {
LOGGER.error("unsupported charset exception " + UTF_8, e);
}
resultRequest.setLabels(convertLabel(label));
resultRequest.setLabels(RequestLabelUtils.convertLabel(label));
}
URI uri = URI.create(request.url());
resultRequest.setMethod(uri.getPath());
@ -82,16 +87,14 @@ public final class ReporterUtils {
if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) {
resultRequest.setCallerService(new ServiceKey(sourceNamespace, sourceService));
}
if (Objects.nonNull(context)) {
resultRequest.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP());
}
resultRequest.setHost(uri.getHost());
// -1 means access directly by url, and use http default port number 80
resultRequest.setPort(uri.getPort() == -1 ? 80 : uri.getPort());
consumer.accept(resultRequest);
return resultRequest;
}
private static String convertLabel(String label) {
label = label.replaceAll("\"|\\{|\\}", "")
.replaceAll(",", "|");
return label;
}
}

@ -17,6 +17,8 @@
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
import java.util.ArrayList;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
@ -25,12 +27,14 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import feign.Request;
import feign.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
/**
@ -44,8 +48,11 @@ public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter imple
private final ConsumerAPI consumerAPI;
public SuccessPolarisReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) {
private final SDKContext context;
public SuccessPolarisReporter(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
super(properties);
this.context = context;
this.consumerAPI = consumerAPI;
}
@ -75,7 +82,13 @@ public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter imple
}
LOG.debug("Will report result of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", retStatus.name(), request.httpMethod()
.name(), request.url(), response.status(), delay);
ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(request, response, delay, retStatus);
ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(this.context, request, response,
delay, retStatus, serviceCallResult -> {
HttpHeaders headers = new HttpHeaders();
response.headers().forEach((s, strings) -> headers.addAll(s, new ArrayList<>(strings)));
serviceCallResult.setRetStatus(getRetStatusFromRequest(headers, serviceCallResult.getRetStatus()));
serviceCallResult.setRuleName(getActiveRuleNameFromRequest(headers));
});
consumerAPI.updateServiceCallResult(resultRequest);
}
}

@ -23,10 +23,12 @@ import java.net.URI;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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.RequestLabelUtils;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
@ -34,6 +36,7 @@ import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.client.api.SDKContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -66,10 +69,12 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter
public static final String HEADER_HAS_ERROR = "X-SCT-Has-Error";
private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class);
private final ConsumerAPI consumerAPI;
private final SDKContext context;
private ResponseErrorHandler delegateHandler;
public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) {
public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
super(properties);
this.context = context;
this.consumerAPI = consumerAPI;
}
@ -149,6 +154,11 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter
if (apply(response.getStatusCode())) {
resultRequest.setRetStatus(RetStatus.RetFail);
}
resultRequest.setRetStatus(getRetStatusFromRequest(response.getHeaders(), resultRequest.getRetStatus()));
resultRequest.setRuleName(getActiveRuleNameFromRequest(response.getHeaders()));
if (Objects.nonNull(context)) {
resultRequest.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP());
}
List<String> labels = response.getHeaders().get(RouterConstant.ROUTER_LABEL_HEADER);
if (CollectionUtils.isNotEmpty(labels)) {
@ -159,7 +169,7 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter
catch (UnsupportedEncodingException e) {
LOGGER.error("unsupported charset exception " + UTF_8, e);
}
resultRequest.setLabels(convertLabel(label));
resultRequest.setLabels(RequestLabelUtils.convertLabel(label));
}
// processing report with consumerAPI .
@ -172,12 +182,6 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter
}
}
private String convertLabel(String label) {
label = label.replaceAll("\"|\\{|\\}", "")
.replaceAll(",", "|");
return label;
}
private void invokeDelegateHandler(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
if (realHasError(response)) {
delegateHandler.handleError(url, method, response);
@ -227,4 +231,5 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter
protected void setDelegateHandler(ResponseErrorHandler delegateHandler) {
this.delegateHandler = delegateHandler;
}
}

@ -0,0 +1,173 @@
/*
* 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.rpc.enhancement.scg;
import java.net.SocketTimeoutException;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import io.netty.handler.codec.http.HttpHeaders;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.netty.http.client.HttpClient;
import reactor.netty.http.client.HttpClientConfig;
import reactor.netty.http.client.HttpClientResponse;
import org.springframework.http.HttpStatus;
public class EnhancedPolarisHttpClient extends HttpClient {
private static final Logger LOG = LoggerFactory.getLogger(EnhancedPolarisHttpClient.class);
private final RpcEnhancementReporterProperties properties;
private final SDKContext context;
private final ConsumerAPI consumerAPI;
private final Reporter adapter;
private final BiConsumer<? super HttpClientResponse, ? super Throwable> handler = new BiConsumer<HttpClientResponse, Throwable>() {
@Override
public void accept(HttpClientResponse httpClientResponse, Throwable throwable) {
if (Objects.isNull(consumerAPI)) {
return;
}
HttpHeaders responseHeaders = httpClientResponse.responseHeaders();
ServiceCallResult result = new ServiceCallResult();
result.setCallerService(new ServiceKey(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE));
result.setNamespace(MetadataContext.LOCAL_NAMESPACE);
Map<String, String> metadata = MetadataContextHolder.get().getLoadbalancerMetadata();
result.setDelay(System.currentTimeMillis() - Long.parseLong(metadata.get("startTime")));
result.setService(metadata.get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID));
result.setHost(metadata.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST));
result.setPort(Integer.parseInt(metadata.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT)));
RetStatus status = RetStatus.RetSuccess;
if (Objects.isNull(throwable)) {
if (EnhancedPolarisHttpClient.this.adapter.apply(HttpStatus.valueOf(httpClientResponse.status()
.code()))) {
status = RetStatus.RetFail;
}
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
responseHeaders.forEach(entry -> headers.add(entry.getKey(), entry.getValue()));
status = adapter.getRetStatusFromRequest(headers, status);
result.setRuleName(adapter.getActiveRuleNameFromRequest(headers));
}
else {
if (throwable instanceof SocketTimeoutException) {
status = RetStatus.RetTimeout;
}
}
result.setMethod(httpClientResponse.uri());
result.setRetCode(httpClientResponse.status().code());
result.setRetStatus(status);
if (Objects.nonNull(context)) {
result.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP());
}
try {
consumerAPI.updateServiceCallResult(result);
}
catch (Throwable ex) {
LOG.error("update service call result fail", ex);
}
}
};
private HttpClient target;
public EnhancedPolarisHttpClient(
HttpClient client,
RpcEnhancementReporterProperties properties,
SDKContext context,
ConsumerAPI consumerAPI) {
this.properties = properties;
this.context = context;
this.consumerAPI = consumerAPI;
this.target = client;
this.adapter = new Reporter(properties);
this.registerReportHandler();
}
@Override
public HttpClientConfig configuration() {
return target.configuration();
}
@Override
protected HttpClient duplicate() {
return new EnhancedPolarisHttpClient(target, properties, context, consumerAPI);
}
private void registerReportHandler() {
target = target.doOnRequest((request, connection) -> {
String serviceId = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID);
String host = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST);
String port = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT);
if (StringUtils.isNotBlank(serviceId)) {
MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, serviceId);
MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST, host);
MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT, port);
MetadataContextHolder.get().setLoadbalancer("startTime", System.currentTimeMillis() + "");
}
request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID);
request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST);
request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT);
});
target = target.doOnResponse((httpClientResponse, connection) -> handler.accept(httpClientResponse, null));
target = target.doOnResponseError(handler);
}
private static class Reporter extends AbstractPolarisReporterAdapter {
/**
* Constructor With {@link RpcEnhancementReporterProperties} .
*
* @param reportProperties instance of {@link RpcEnhancementReporterProperties}.
*/
protected Reporter(RpcEnhancementReporterProperties reportProperties) {
super(reportProperties);
}
@Override
public boolean apply(HttpStatus httpStatus) {
return super.apply(httpStatus);
}
@Override
public RetStatus getRetStatusFromRequest(org.springframework.http.HttpHeaders headers, RetStatus defaultVal) {
return super.getRetStatusFromRequest(headers, defaultVal);
}
@Override
public String getActiveRuleNameFromRequest(org.springframework.http.HttpHeaders headers) {
return super.getActiveRuleNameFromRequest(headers);
}
}
}

@ -0,0 +1,43 @@
/*
* 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.rpc.enhancement.scg;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.client.api.SDKContext;
import reactor.netty.http.client.HttpClient;
import org.springframework.cloud.gateway.config.HttpClientCustomizer;
public class EnhancedPolarisHttpClientCustomizer implements HttpClientCustomizer {
private final RpcEnhancementReporterProperties properties;
private final SDKContext context;
private final ConsumerAPI consumerAPI;
public EnhancedPolarisHttpClientCustomizer(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
this.properties = properties;
this.context = context;
this.consumerAPI = consumerAPI;
}
@Override
public HttpClient customize(HttpClient httpClient) {
return new EnhancedPolarisHttpClient(httpClient, properties, context, consumerAPI);
}
}

@ -0,0 +1,71 @@
/*
* 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.rpc.enhancement.scg;
import java.util.List;
import com.tencent.cloud.common.constant.HeaderConstant;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR;
public class EnhancedPolarisHttpHeadersFilter implements HttpHeadersFilter {
public EnhancedPolarisHttpHeadersFilter() {
}
@Override
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
Response<ServiceInstance> serviceInstanceResponse = exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR);
if (serviceInstanceResponse == null || !serviceInstanceResponse.hasServer()) {
return input;
}
ServiceInstance instance = serviceInstanceResponse.getServer();
write(input, HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, instance.getServiceId(), true);
write(input, HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST, instance.getHost(), true);
write(input, HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT, instance.getPort() + "", true);
return input;
}
@Override
public boolean supports(Type type) {
return Type.REQUEST.equals(type);
}
private void write(HttpHeaders headers, String name, String value, boolean append) {
if (value == null) {
return;
}
if (append) {
headers.add(name, value);
// these headers should be treated as a single comma separated header
List<String> values = headers.get(name);
String delimitedValue = StringUtils.collectionToCommaDelimitedString(values);
headers.set(name, delimitedValue);
}
else {
headers.set(name, value);
}
}
}

@ -0,0 +1,144 @@
/*
* 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.rpc.enhancement.webclient;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.Objects;
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.util.RequestLabelUtils;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.client.api.SDKContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import reactor.util.context.ContextView;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
public class EnhancedWebClientReporter extends AbstractPolarisReporterAdapter implements ExchangeFilterFunction {
protected static final String METRICS_WEBCLIENT_START_TIME = EnhancedWebClientReporter.class.getName()
+ ".START_TIME";
private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedWebClientReporter.class);
private final ConsumerAPI consumerAPI;
private final SDKContext context;
public EnhancedWebClientReporter(RpcEnhancementReporterProperties reportProperties, SDKContext context, ConsumerAPI consumerAPI) {
super(reportProperties);
this.context = context;
this.consumerAPI = consumerAPI;
}
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return next.exchange(request).as((responseMono) -> instrumentResponse(request, responseMono))
.contextWrite(this::putStartTime);
}
Mono<ClientResponse> instrumentResponse(ClientRequest request, Mono<ClientResponse> responseMono) {
return Mono.deferContextual((ctx) -> responseMono.doOnEach((signal) -> {
// report result to polaris
if (!reportProperties.isEnabled()) {
return;
}
ServiceCallResult callResult = new ServiceCallResult();
Long startTime = getStartTime(ctx);
callResult.setDelay(System.currentTimeMillis() - startTime);
callResult.setNamespace(MetadataContext.LOCAL_NAMESPACE);
callResult.setService(request.headers().getFirst(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID));
String sourceNamespace = MetadataContext.LOCAL_NAMESPACE;
String sourceService = MetadataContext.LOCAL_SERVICE;
if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) {
callResult.setCallerService(new ServiceKey(sourceNamespace, sourceService));
}
Collection<String> labels = request.headers().get(RouterConstant.ROUTER_LABEL_HEADER);
if (CollectionUtils.isNotEmpty(labels) && labels.iterator().hasNext()) {
String label = labels.iterator().next();
try {
label = URLDecoder.decode(label, UTF_8);
}
catch (UnsupportedEncodingException e) {
LOGGER.error("unsupported charset exception " + UTF_8, e);
}
callResult.setLabels(RequestLabelUtils.convertLabel(label));
}
URI uri = request.url();
callResult.setMethod(uri.getPath());
callResult.setHost(uri.getHost());
// -1 means access directly by url, and use http default port number 80
callResult.setPort(uri.getPort() == -1 ? 80 : uri.getPort());
if (Objects.nonNull(context)) {
callResult.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP());
}
RetStatus retStatus = RetStatus.RetSuccess;
ClientResponse response = signal.get();
if (Objects.nonNull(response)) {
HttpHeaders headers = response.headers().asHttpHeaders();
callResult.setRuleName(getActiveRuleNameFromRequest(headers));
if (apply(response.statusCode())) {
retStatus = RetStatus.RetFail;
}
retStatus = getRetStatusFromRequest(headers, retStatus);
}
if (signal.isOnError()) {
Throwable throwable = signal.getThrowable();
if (throwable instanceof SocketTimeoutException) {
retStatus = RetStatus.RetTimeout;
}
}
callResult.setRetStatus(retStatus);
consumerAPI.updateServiceCallResult(callResult);
}));
}
private Long getStartTime(ContextView context) {
return context.get(METRICS_WEBCLIENT_START_TIME);
}
private Context putStartTime(Context context) {
return context.put(METRICS_WEBCLIENT_START_TIME, System.currentTimeMillis());
}
}

@ -17,10 +17,13 @@
package com.tencent.cloud.rpc.enhancement;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.pojo.RetStatus;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
/**
@ -104,6 +107,48 @@ public class AbstractPolarisReporterAdapterTest {
Assertions.assertThat(adapter.apply(HttpStatus.FORBIDDEN)).isEqualTo(true);
}
@Test
public void testGetRetStatusFromRequest() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
// Mock Condition
properties.getStatuses().clear();
properties.getSeries().clear();
properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
HttpHeaders headers = new HttpHeaders();
RetStatus ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail);
Assertions.assertThat(ret).isEqualTo(RetStatus.RetFail);
headers.set(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc());
ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail);
Assertions.assertThat(ret).isEqualTo(RetStatus.RetFlowControl);
headers.set(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetReject.getDesc());
ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail);
Assertions.assertThat(ret).isEqualTo(RetStatus.RetReject);
}
@Test
public void testGetActiveRuleNameFromRequest() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
// Mock Condition
properties.getStatuses().clear();
properties.getSeries().clear();
properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
HttpHeaders headers = new HttpHeaders();
String ruleName = adapter.getActiveRuleNameFromRequest(headers);
Assertions.assertThat(ruleName).isEqualTo("");
headers.set(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, "mock_rule");
ruleName = adapter.getActiveRuleNameFromRequest(headers);
Assertions.assertThat(ruleName).isEqualTo("mock_rule");
}
/**
* Simple Polaris CircuitBreak Adapter Implements .
*/

@ -50,7 +50,7 @@ public class RpcEnhancementAutoConfigurationTest {
RpcEnhancementAutoConfiguration.class,
PolarisRestTemplateAutoConfigurationTester.class,
FeignLoadBalancerAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true", "spring.application.name=test");
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true", "spring.application.name=test", "spring.cloud.gateway.enabled=false");
@Test
public void testDefaultInitialization() {

@ -38,7 +38,10 @@ import static org.springframework.http.HttpStatus.Series.SERVER_ERROR;
* @author Haotian Zhang
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = RpcEnhancementReporterPropertiesTest.TestApplication.class, properties = "spring.application.name=test")
@SpringBootTest(classes = RpcEnhancementReporterPropertiesTest.TestApplication.class, properties = {
"spring.application.name=test",
"spring.cloud.gateway.enabled=false"
})
@ActiveProfiles("test")
public class RpcEnhancementReporterPropertiesTest {

@ -51,7 +51,7 @@ import static org.mockito.Mockito.mock;
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = EnhancedFeignClientTest.TestApplication.class,
properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"})
properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp", "spring.cloud.gateway.enabled=false"})
public class EnhancedFeignClientTest {
@Test

@ -18,17 +18,24 @@
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
import java.util.HashMap;
import java.util.function.Consumer;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
import feign.Target;
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.InjectMocks;
@ -37,9 +44,12 @@ import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@ -54,6 +64,7 @@ import static org.mockito.Mockito.verify;
public class ExceptionPolarisReporterTest {
private static MockedStatic<ReporterUtils> mockedReporterUtils;
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private ConsumerAPI consumerAPI;
@Mock
@ -63,16 +74,27 @@ public class ExceptionPolarisReporterTest {
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class);
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(Request.class), any(Response.class), anyLong(), any(RetStatus.class)))
.thenReturn(mock(ServiceCallResult.class));
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class),
any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class)))
.thenReturn(new ServiceCallResult());
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
mockedReporterUtils.close();
}
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void testGetName() {
assertThat(exceptionPolarisReporter.getName()).isEqualTo(ExceptionPolarisReporter.class.getName());
@ -100,6 +122,33 @@ public class ExceptionPolarisReporterTest {
doReturn(true).when(reporterProperties).isEnabled();
exceptionPolarisReporter.run(context);
verify(context, times(1)).getRequest();
try {
mockedReporterUtils.close();
// mock target
Target<?> target = mock(Target.class);
doReturn(SERVICE_PROVIDER).when(target).name();
// mock RequestTemplate.class
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.feignTarget(target);
EnhancedFeignContext feignContext = new EnhancedFeignContext();
request = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), null, null, requestTemplate);
response = Response.builder()
.request(request)
.build();
feignContext.setRequest(request);
feignContext.setResponse(response);
exceptionPolarisReporter.run(feignContext);
}
finally {
mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class);
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class),
any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class)))
.thenReturn(new ServiceCallResult());
}
}
@Test

@ -23,8 +23,12 @@ import java.net.URLEncoder;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.polaris.api.config.Configuration;
import com.tencent.polaris.api.config.global.APIConfig;
import com.tencent.polaris.api.config.global.GlobalConfig;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
@ -99,7 +103,9 @@ public class ReporterUtilsTest {
Response response = mock(Response.class);
doReturn(502).when(response).status();
ServiceCallResult serviceCallResult = ReporterUtils.createServiceCallResult(request, response, 10L, RetStatus.RetSuccess);
ServiceCallResult serviceCallResult = ReporterUtils.createServiceCallResult(mockSDKContext(), request, response, 10L, RetStatus.RetSuccess, result -> {
});
assertThat(serviceCallResult.getNamespace()).isEqualTo(NAMESPACE_TEST);
assertThat(serviceCallResult.getService()).isEqualTo(SERVICE_PROVIDER);
assertThat(serviceCallResult.getHost()).isEqualTo("1.1.1.1");
@ -112,4 +118,17 @@ public class ReporterUtilsTest {
assertThat(serviceCallResult.getRetCode()).isEqualTo(502);
assertThat(serviceCallResult.getDelay()).isEqualTo(10L);
}
public static SDKContext mockSDKContext() {
APIConfig apiConfig = mock(APIConfig.class);
doReturn("127.0.0.1").when(apiConfig).getBindIP();
GlobalConfig globalConfig = mock(GlobalConfig.class);
doReturn(apiConfig).when(globalConfig).getAPI();
Configuration configuration = mock(Configuration.class);
doReturn(globalConfig).when(configuration).getGlobal();
SDKContext context = mock(SDKContext.class);
doReturn(configuration).when(context).getConfig();
return context;
}
}

@ -18,17 +18,24 @@
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
import java.util.HashMap;
import java.util.function.Consumer;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
import feign.Target;
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.InjectMocks;
@ -37,9 +44,12 @@ import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@ -54,6 +64,7 @@ import static org.mockito.Mockito.verify;
public class SuccessPolarisReporterTest {
private static MockedStatic<ReporterUtils> mockedReporterUtils;
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private ConsumerAPI consumerAPI;
@Mock
@ -63,16 +74,27 @@ public class SuccessPolarisReporterTest {
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class);
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(Request.class), any(Response.class), anyLong(), any(RetStatus.class)))
.thenReturn(mock(ServiceCallResult.class));
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class),
any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class)))
.thenReturn(new ServiceCallResult());
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
mockedReporterUtils.close();
}
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void testGetName() {
assertThat(successPolarisReporter.getName()).isEqualTo(SuccessPolarisReporter.class.getName());
@ -101,6 +123,32 @@ public class SuccessPolarisReporterTest {
doReturn(true).when(reporterProperties).isEnabled();
successPolarisReporter.run(context);
verify(context, times(1)).getRequest();
try {
mockedReporterUtils.close();
// mock target
Target<?> target = mock(Target.class);
doReturn(SERVICE_PROVIDER).when(target).name();
// mock RequestTemplate.class
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.feignTarget(target);
EnhancedFeignContext feignContext = new EnhancedFeignContext();
request = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), null, null, requestTemplate);
response = Response.builder()
.request(request)
.build();
feignContext.setRequest(request);
feignContext.setResponse(response);
successPolarisReporter.run(feignContext);
}
finally {
mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class);
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class),
any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class)))
.thenReturn(new ServiceCallResult());
}
}
@Test

@ -0,0 +1,49 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.scg;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import reactor.netty.http.client.HttpClient;
public class EnhancedPolarisHttpClientCustomizerTest {
@Test
public void testCustomize() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
properties.setEnabled(true);
properties.getStatuses().clear();
properties.getSeries().clear();
SDKContext context = Mockito.mock(SDKContext.class);
ConsumerAPI consumerAPI = Mockito.mock(ConsumerAPI.class);
EnhancedPolarisHttpClientCustomizer clientCustomizer = new EnhancedPolarisHttpClientCustomizer(properties, context, consumerAPI);
HttpClient client = HttpClient.create();
HttpClient proxyClient = clientCustomizer.customize(client);
Assertions.assertNotNull(proxyClient);
Assertions.assertEquals(EnhancedPolarisHttpClient.class.getName(), proxyClient.getClass().getName());
}
}

@ -0,0 +1,57 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.scg;
import java.util.Collections;
import com.tencent.cloud.common.constant.HeaderConstant;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR;
public class EnhancedPolarisHttpHeadersFilterTest {
@Test
public void testFilter() {
EnhancedPolarisHttpHeadersFilter filter = new EnhancedPolarisHttpHeadersFilter();
ServiceInstance instance = Mockito.mock(ServiceInstance.class);
Mockito.doReturn("mock_service").when(instance).getServiceId();
Mockito.doReturn("127.0.0.1").when(instance).getHost();
Mockito.doReturn(8080).when(instance).getPort();
DefaultResponse response = new DefaultResponse(instance);
ServerWebExchange exchange = Mockito.mock(ServerWebExchange.class);
Mockito.doReturn(response).when(exchange).getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR);
HttpHeaders input = new HttpHeaders();
HttpHeaders headers = filter.filter(input, exchange);
Assertions.assertEquals(Collections.singletonList("mock_service"), headers.get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID));
Assertions.assertEquals(Collections.singletonList("127.0.0.1"), headers.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST));
Assertions.assertEquals(Collections.singletonList("8080"), headers.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT));
}
}

@ -40,7 +40,8 @@ public class PolarisStatPropertiesTest {
.withPropertyValues("spring.cloud.polaris.stat.path=/xxx")
.withPropertyValues("spring.cloud.polaris.stat.pushgateway.enabled=true")
.withPropertyValues("spring.cloud.polaris.stat.pushgateway.address=127.0.0.1:9091")
.withPropertyValues("spring.cloud.polaris.stat.pushgateway.push-interval=1000");
.withPropertyValues("spring.cloud.polaris.stat.pushgateway.push-interval=1000")
.withPropertyValues("spring.cloud.gateway.enabled=false");
@Test
public void testDefaultInitialization() {

@ -42,7 +42,8 @@ public class StatConfigModifierTest {
.withPropertyValues("spring.cloud.polaris.stat.host=127.0.0.1")
.withPropertyValues("spring.cloud.polaris.stat.port=20000")
.withPropertyValues("spring.cloud.polaris.stat.path=/xxx")
.withPropertyValues("spring.application.name=test");
.withPropertyValues("spring.application.name=test")
.withPropertyValues("spring.cloud.gateway.enabled=false");
private final ApplicationContextRunner pushContextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(TestApplication.class))
@ -51,13 +52,15 @@ public class StatConfigModifierTest {
.withPropertyValues("spring.cloud.polaris.stat.pushgateway.enabled=true")
.withPropertyValues("spring.cloud.polaris.stat.pushgateway.address=127.0.0.1:9091")
.withPropertyValues("spring.cloud.polaris.stat.pushgateway.push-interval=1000")
.withPropertyValues("spring.application.name=test");
.withPropertyValues("spring.application.name=test")
.withPropertyValues("spring.cloud.gateway.enabled=false");
private final ApplicationContextRunner disabledContextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(TestApplication.class))
.withPropertyValues("spring.cloud.polaris.enabled=true")
.withPropertyValues("spring.cloud.polaris.stat.enabled=false")
.withPropertyValues("spring.application.name=test");
.withPropertyValues("spring.application.name=test")
.withPropertyValues("spring.cloud.gateway.enabled=false");
@Test
void testPull() {

@ -0,0 +1,100 @@
/*
* 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.rpc.enhancement.webclient;
import java.net.URI;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import static com.tencent.cloud.rpc.enhancement.webclient.EnhancedWebClientReporter.METRICS_WEBCLIENT_START_TIME;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.mockito.ArgumentMatchers.anyString;
public class EnhancedWebClientReporterTest {
private static final String URI_TEMPLATE_ATTRIBUTE = EnhancedWebClientReporterTest.class.getName() + ".uriTemplate";
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
}
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void testInstrumentResponse() {
ClientResponse response = Mockito.mock(ClientResponse.class);
ClientResponse.Headers headers = Mockito.mock(ClientResponse.Headers.class);
Mockito.doReturn(headers).when(response).headers();
Mockito.doReturn(new HttpHeaders()).when(headers).asHttpHeaders();
Mono<ClientResponse> responseMono = Mono.just(response);
ClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot"))
.attribute(URI_TEMPLATE_ATTRIBUTE, "https://example.org/projects/{project}")
.build();
ConsumerAPI consumerAPI = Mockito.mock(ConsumerAPI.class);
Mockito.doAnswer(invocationOnMock -> {
ServiceCallResult result = invocationOnMock.getArgument(0, ServiceCallResult.class);
Assertions.assertTrue(result.getDelay() > 0);
return null;
}).when(consumerAPI)
.updateServiceCallResult(Mockito.any(ServiceCallResult.class));
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
properties.setEnabled(true);
properties.getStatuses().clear();
properties.getSeries().clear();
EnhancedWebClientReporter reporter = new EnhancedWebClientReporter(properties, null, consumerAPI);
reporter.instrumentResponse(request, responseMono)
.contextWrite(context -> context.put(METRICS_WEBCLIENT_START_TIME, System.currentTimeMillis()))
.subscribe();
}
}
Loading…
Cancel
Save