refactor: refactor circuitbreaker (#964)

pull/991/head
Shanyou Yu (Sean Yu) 1 year ago committed by GitHub
parent 8869e901b9
commit abed6d57af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,3 +7,4 @@
- [fix: fix nacos CircuitBreaker disable bug.](https://github.com/Tencent/spring-cloud-tencent/pull/949)
- [feature: add config for customized local port.](https://github.com/Tencent/spring-cloud-tencent/pull/957)
- [refactor:refactor reactor code in router module.](https://github.com/Tencent/spring-cloud-tencent/pull/959)
- [refactor: refactor circuitbreaker .](https://github.com/Tencent/spring-cloud-tencent/pull/964)

@ -22,6 +22,8 @@ import java.util.List;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier;
import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.reporter.SuccessCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerRestTemplateBeanPostProcessor;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
@ -61,6 +63,20 @@ public class PolarisCircuitBreakerAutoConfiguration {
return CircuitBreakAPIFactory.createCircuitBreakAPIByContext(polarisContext);
}
@Bean
@ConditionalOnMissingBean(SuccessCircuitBreakerReporter.class)
public SuccessCircuitBreakerReporter successCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
SDKContext polarisContext, CircuitBreakAPI circuitBreakAPI) {
return new SuccessCircuitBreakerReporter(properties, polarisContext, circuitBreakAPI);
}
@Bean
@ConditionalOnMissingBean(ExceptionCircuitBreakerReporter.class)
public ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
SDKContext polarisContext, CircuitBreakAPI circuitBreakAPI) {
return new ExceptionCircuitBreakerReporter(properties, polarisContext, circuitBreakAPI);
}
@Bean
@ConditionalOnMissingBean(CircuitBreakerFactory.class)
public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {

@ -22,6 +22,8 @@ import java.util.List;
import com.tencent.cloud.polaris.circuitbreaker.ReactivePolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier;
import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.reporter.SuccessCircuitBreakerReporter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
@ -59,6 +61,20 @@ public class ReactivePolarisCircuitBreakerAutoConfiguration {
return CircuitBreakAPIFactory.createCircuitBreakAPIByContext(polarisContext);
}
@Bean
@ConditionalOnMissingBean(SuccessCircuitBreakerReporter.class)
public SuccessCircuitBreakerReporter successCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
SDKContext polarisContext, CircuitBreakAPI circuitBreakAPI) {
return new SuccessCircuitBreakerReporter(properties, polarisContext, circuitBreakAPI);
}
@Bean
@ConditionalOnMissingBean(ExceptionCircuitBreakerReporter.class)
public ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
SDKContext polarisContext, CircuitBreakAPI circuitBreakAPI) {
return new ExceptionCircuitBreakerReporter(properties, polarisContext, circuitBreakAPI);
}
@Bean
@ConditionalOnMissingBean(ReactiveCircuitBreakerFactory.class)
public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {

@ -0,0 +1,97 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.reporter;
import java.util.Optional;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.polaris.api.plugin.circuitbreaker.ResourceStat;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.client.api.SDKContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.Ordered;
public class ExceptionCircuitBreakerReporter extends AbstractPolarisReporterAdapter implements EnhancedPlugin {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionCircuitBreakerReporter.class);
private final CircuitBreakAPI circuitBreakAPI;
public ExceptionCircuitBreakerReporter(RpcEnhancementReporterProperties reportProperties,
SDKContext context,
CircuitBreakAPI circuitBreakAPI) {
super(reportProperties, context);
this.circuitBreakAPI = circuitBreakAPI;
}
@Override
public String getName() {
return ExceptionCircuitBreakerReporter.class.getName();
}
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.EXCEPTION;
}
@Override
public void run(EnhancedPluginContext context) throws Throwable {
if (!super.reportProperties.isEnabled()) {
return;
}
EnhancedRequestContext request = context.getRequest();
ServiceInstance serviceInstance = Optional.ofNullable(context.getServiceInstance()).orElse(new DefaultServiceInstance());
ResourceStat resourceStat = createInstanceResourceStat(
serviceInstance.getServiceId(),
serviceInstance.getHost(),
serviceInstance.getPort(),
request.getUrl(),
null,
context.getDelay(),
context.getThrowable()
);
LOG.debug("Will report CircuitBreaker ResourceStat of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.",
resourceStat.getRetStatus().name(), request.getHttpMethod().name(), request.getUrl().getPath(), context.getThrowable().getMessage(), context.getDelay());
circuitBreakAPI.report(resourceStat);
}
@Override
public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
LOG.error("ExceptionCircuitBreakerReporter runs failed. context=[{}].",
context, throwable);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 2;
}
}

@ -0,0 +1,99 @@
/*
* 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.reporter;
import java.util.Optional;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter;
import com.tencent.polaris.api.plugin.circuitbreaker.ResourceStat;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.client.api.SDKContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.Ordered;
public class SuccessCircuitBreakerReporter extends AbstractPolarisReporterAdapter implements EnhancedPlugin {
private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class);
private final CircuitBreakAPI circuitBreakAPI;
public SuccessCircuitBreakerReporter(RpcEnhancementReporterProperties reportProperties,
SDKContext context,
CircuitBreakAPI circuitBreakAPI) {
super(reportProperties, context);
this.circuitBreakAPI = circuitBreakAPI;
}
@Override
public String getName() {
return SuccessCircuitBreakerReporter.class.getName();
}
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.POST;
}
@Override
public void run(EnhancedPluginContext context) throws Throwable {
if (!super.reportProperties.isEnabled()) {
return;
}
EnhancedRequestContext request = context.getRequest();
EnhancedResponseContext response = context.getResponse();
ServiceInstance serviceInstance = Optional.ofNullable(context.getServiceInstance()).orElse(new DefaultServiceInstance());
ResourceStat resourceStat = createInstanceResourceStat(
serviceInstance.getServiceId(),
serviceInstance.getHost(),
serviceInstance.getPort(),
request.getUrl(),
response.getHttpStatus(),
context.getDelay(),
null
);
LOG.debug("Will report CircuitBreaker ResourceStat of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.",
resourceStat.getRetStatus().name(), request.getHttpMethod().name(), request.getUrl().getPath(), response.getHttpStatus(), context.getDelay());
circuitBreakAPI.report(resourceStat);
}
@Override
public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
LOG.error("SuccessCircuitBreakerReporter runs failed. context=[{}].",
context, throwable);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 2;
}
}

@ -28,8 +28,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.AbstractClientHttpResponse;
import static com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter.POLARIS_CIRCUIT_BREAKER_FALLBACK_HEADER;
/**
* PolarisCircuitBreakerHttpResponse.
*
@ -57,7 +55,6 @@ public class PolarisCircuitBreakerHttpResponse extends AbstractClientHttpRespons
PolarisCircuitBreakerHttpResponse(CircuitBreakerStatus.FallbackInfo fallbackInfo) {
this.fallbackInfo = fallbackInfo;
headers.add(POLARIS_CIRCUIT_BREAKER_FALLBACK_HEADER, "true");
if (fallbackInfo.getHeaders() != null) {
fallbackInfo.getHeaders().forEach(headers::add);
}

@ -20,7 +20,6 @@ package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
import java.io.IOException;
import java.lang.reflect.Method;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
@ -35,8 +34,6 @@ import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import static com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter.HEADER_HAS_ERROR;
/**
* PolarisCircuitBreakerRestTemplateInterceptor.
*
@ -70,15 +67,8 @@ public class PolarisCircuitBreakerRestTemplateInterceptor implements ClientHttpR
() -> {
try {
ClientHttpResponse response = execution.execute(request, body);
// pre handle response error
// EnhancedRestTemplateReporter always return true,
// so we need to check header set by EnhancedRestTemplateReporter
ResponseErrorHandler errorHandler = restTemplate.getErrorHandler();
boolean hasError = errorHandler.hasError(response);
if (errorHandler instanceof EnhancedRestTemplateReporter) {
hasError = Boolean.parseBoolean(response.getHeaders().getFirst(HEADER_HAS_ERROR));
}
if (hasError) {
if (errorHandler.hasError(response)) {
errorHandler.handleError(request.getURI(), request.getMethod(), response);
}
return response;

@ -33,13 +33,9 @@ import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeig
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerFallback;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerHttpResponse;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter;
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;
@ -71,7 +67,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import static com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter.HEADER_HAS_ERROR;
import static com.tencent.polaris.test.common.TestUtils.SERVER_ADDRESS_ENV;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@ -144,13 +139,11 @@ public class PolarisCircuitBreakerIntegrationTest {
mockServer.verify();
mockServer.reset();
HttpHeaders headers = new HttpHeaders();
headers.add(HEADER_HAS_ERROR, "true");
// no delegateHandler in EnhancedRestTemplateReporter, so this will except err
mockServer
.expect(ExpectedCount.once(), requestTo(new URI("http://localhost:18001/example/service/b/info")))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.BAD_GATEWAY).headers(headers).body("BAD_GATEWAY"));
assertThat(defaultRestTemplate.getForObject("http://localhost:18001/example/service/b/info", String.class)).isEqualTo("BAD_GATEWAY");
assertThat(defaultRestTemplate.getForObject("http://localhost:18001/example/service/b/info", String.class)).isEqualTo("fallback");
mockServer.verify();
mockServer.reset();
assertThat(restTemplateFallbackFromCode.getForObject("/example/service/b/info", String.class)).isEqualTo("\"this is a fallback class\"");
@ -176,11 +169,8 @@ public class PolarisCircuitBreakerIntegrationTest {
@Bean
@PolarisCircuitBreaker(fallback = "fallback")
public RestTemplate defaultRestTemplate(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
RestTemplate defaultRestTemplate = new RestTemplate();
EnhancedRestTemplateReporter enhancedRestTemplateReporter = new EnhancedRestTemplateReporter(properties, context, consumerAPI);
defaultRestTemplate.setErrorHandler(enhancedRestTemplateReporter);
return defaultRestTemplate;
public RestTemplate defaultRestTemplate() {
return new RestTemplate();
}
@Bean

@ -0,0 +1,145 @@
/*
* 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.reporter;
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.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
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.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* ExceptionCircuitBreakerReporterTest.
*
* @author sean yu
*/
@ExtendWith(MockitoExtension.class)
public class ExceptionCircuitBreakerReporterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private RpcEnhancementReporterProperties reporterProperties;
@Mock
private SDKContext sdkContext;
@InjectMocks
private ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter;
@Mock
private CircuitBreakAPI circuitBreakAPI;
@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 testGetName() {
assertThat(exceptionCircuitBreakerReporter.getName()).isEqualTo(ExceptionCircuitBreakerReporter.class.getName());
}
@Test
public void testType() {
assertThat(exceptionCircuitBreakerReporter.getType()).isEqualTo(EnhancedPluginType.EXCEPTION);
}
@Test
public void testRun() throws Throwable {
EnhancedPluginContext context = mock(EnhancedPluginContext.class);
// test not report
exceptionCircuitBreakerReporter.run(context);
verify(context, times(0)).getRequest();
doReturn(true).when(reporterProperties).isEnabled();
EnhancedPluginContext pluginContext = new EnhancedPluginContext();
EnhancedRequestContext request = EnhancedRequestContext.builder()
.httpMethod(HttpMethod.GET)
.url(URI.create("http://0.0.0.0/"))
.httpHeaders(new HttpHeaders())
.build();
EnhancedResponseContext response = EnhancedResponseContext.builder()
.httpStatus(200)
.build();
DefaultServiceInstance serviceInstance = new DefaultServiceInstance();
serviceInstance.setServiceId(SERVICE_PROVIDER);
pluginContext.setRequest(request);
pluginContext.setResponse(response);
pluginContext.setServiceInstance(serviceInstance);
pluginContext.setThrowable(new RuntimeException());
exceptionCircuitBreakerReporter.run(pluginContext);
exceptionCircuitBreakerReporter.getOrder();
exceptionCircuitBreakerReporter.getName();
exceptionCircuitBreakerReporter.getType();
}
@Test
public void testHandlerThrowable() {
// mock request
EnhancedRequestContext request = mock(EnhancedRequestContext.class);
// mock response
EnhancedResponseContext response = mock(EnhancedResponseContext.class);
EnhancedPluginContext context = new EnhancedPluginContext();
context.setRequest(request);
context.setResponse(response);
exceptionCircuitBreakerReporter.handlerThrowable(context, new RuntimeException("Mock exception."));
}
}

@ -0,0 +1,143 @@
/*
* 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.reporter;
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.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.http.HttpMethod;
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.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* SuccessCircuitBreakerReporterTest.
*
* @author sean yu
*/
@ExtendWith(MockitoExtension.class)
public class SuccessCircuitBreakerReporterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private SDKContext sdkContext;
@Mock
private RpcEnhancementReporterProperties reporterProperties;
@InjectMocks
private SuccessCircuitBreakerReporter successCircuitBreakerReporter;
@Mock
private CircuitBreakAPI circuitBreakAPI;
@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 testGetName() {
assertThat(successCircuitBreakerReporter.getName()).isEqualTo(SuccessCircuitBreakerReporter.class.getName());
}
@Test
public void testType() {
assertThat(successCircuitBreakerReporter.getType()).isEqualTo(EnhancedPluginType.POST);
}
@Test
public void testRun() throws Throwable {
EnhancedPluginContext context = mock(EnhancedPluginContext.class);
// test not report
successCircuitBreakerReporter.run(context);
verify(context, times(0)).getRequest();
doReturn(true).when(reporterProperties).isEnabled();
EnhancedPluginContext pluginContext = new EnhancedPluginContext();
EnhancedRequestContext request = EnhancedRequestContext.builder()
.httpMethod(HttpMethod.GET)
.url(URI.create("http://0.0.0.0/"))
.build();
EnhancedResponseContext response = EnhancedResponseContext.builder()
.httpStatus(200)
.build();
DefaultServiceInstance serviceInstance = new DefaultServiceInstance();
serviceInstance.setServiceId(SERVICE_PROVIDER);
pluginContext.setRequest(request);
pluginContext.setResponse(response);
pluginContext.setServiceInstance(serviceInstance);
successCircuitBreakerReporter.run(pluginContext);
successCircuitBreakerReporter.getOrder();
successCircuitBreakerReporter.getName();
successCircuitBreakerReporter.getType();
}
@Test
public void testHandlerThrowable() {
// mock request
EnhancedRequestContext request = mock(EnhancedRequestContext.class);
// mock response
EnhancedResponseContext response = mock(EnhancedResponseContext.class);
EnhancedPluginContext context = new EnhancedPluginContext();
context.setRequest(request);
context.setResponse(response);
successCircuitBreakerReporter.handlerThrowable(context, new RuntimeException("Mock exception."));
}
}

@ -18,14 +18,9 @@
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;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
@ -34,7 +29,6 @@ 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;
@ -71,14 +65,6 @@ public class PolarisLoadBalancerClientConfiguration {
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), routerAPI);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.web.reactive.function.client.ClientRequest")
public LoadBalancerClientRequestTransformer polarisLoadBalancerClientRequestTransformer(SDKContext sdkContext) {
ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPIByContext(sdkContext);
return new PolarisLoadBalancerClientRequestTransformer(consumerAPI);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnReactiveDiscoveryEnabled
@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER)

@ -73,12 +73,13 @@
<revision>1.11.0-2022.0.1-SNAPSHOT</revision>
<!-- Dependencies -->
<polaris.version>1.11.6</polaris.version>
<polaris.version>1.12.0-SNAPSHOT</polaris.version>
<guava.version>31.1-jre</guava.version>
<mocktio.version>4.9.0</mocktio.version>
<byte-buddy.version>1.12.19</byte-buddy.version>
<protobuf-java.version>3.21.7</protobuf-java.version>
<bcprov-jdk15on.version>1.69</bcprov-jdk15on.version>
<okio.version>3.0.0</okio.version>
<system-stubs-jupiter.version>2.0.2</system-stubs-jupiter.version>
<!-- Maven Plugin Versions -->
@ -214,6 +215,12 @@
<version>${byte-buddy.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>${okio.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>

@ -54,6 +54,6 @@ logging:
level:
root: info
com.tencent.polaris.discovery.client.flow.RegisterFlow: off
com.tencent.polaris.plugins.registry.memory.CacheObject: off
com.tencent.cloud.polaris.circuitbreaker: debug
com.tencent.polaris.plugins.registry: off
com.tencent.cloud: debug

@ -12,7 +12,9 @@ spring:
enabled: true
rejectRequestTipsFilePath: reject-tips.html
maxQueuingTime: 500
stat:
enabled: true
port: 28083
management:
endpoints:
web:

@ -17,6 +17,10 @@
package com.tencent.cloud.rpc.enhancement;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -24,9 +28,19 @@ import java.util.List;
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.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.plugin.circuitbreaker.ResourceStat;
import com.tencent.polaris.api.plugin.circuitbreaker.entity.InstanceResource;
import com.tencent.polaris.api.plugin.circuitbreaker.entity.Resource;
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;
@ -34,6 +48,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static org.springframework.http.HttpStatus.BAD_GATEWAY;
import static org.springframework.http.HttpStatus.BANDWIDTH_LIMIT_EXCEEDED;
import static org.springframework.http.HttpStatus.GATEWAY_TIMEOUT;
@ -53,22 +68,85 @@ import static org.springframework.http.HttpStatus.VARIANT_ALSO_NEGOTIATES;
* @author <a href="mailto:iskp.me@gmail.com">Elve.Xu</a> 2022-07-11
*/
public abstract class AbstractPolarisReporterAdapter {
private static final Logger LOG = LoggerFactory.getLogger(AbstractPolarisReporterAdapter.class);
private static final List<HttpStatus> HTTP_STATUSES = toList(NOT_IMPLEMENTED, BAD_GATEWAY,
SERVICE_UNAVAILABLE, GATEWAY_TIMEOUT, HTTP_VERSION_NOT_SUPPORTED, VARIANT_ALSO_NEGOTIATES,
INSUFFICIENT_STORAGE, LOOP_DETECTED, BANDWIDTH_LIMIT_EXCEEDED, NOT_EXTENDED, NETWORK_AUTHENTICATION_REQUIRED);
protected final RpcEnhancementReporterProperties reportProperties;
protected final SDKContext context;
/**
* Constructor With {@link RpcEnhancementReporterProperties} .
*
* @param reportProperties instance of {@link RpcEnhancementReporterProperties}.
*/
protected AbstractPolarisReporterAdapter(RpcEnhancementReporterProperties reportProperties) {
protected AbstractPolarisReporterAdapter(RpcEnhancementReporterProperties reportProperties, SDKContext context) {
this.reportProperties = reportProperties;
this.context = context;
}
/**
* createServiceCallResult.
* @param calleeServiceName will pick up url host when null
* @param calleeHost will pick up url host when null
* @param calleePort will pick up url port when null
* @param uri request url
* @param requestHeaders request header
* @param responseHeaders response header
* @param statusCode response status
* @param delay delay
* @param exception exception
* @return ServiceCallResult
*/
public ServiceCallResult createServiceCallResult(
@Nullable String calleeServiceName, @Nullable String calleeHost, @Nullable Integer calleePort,
URI uri, HttpHeaders requestHeaders, @Nullable HttpHeaders responseHeaders,
@Nullable Integer statusCode, long delay, @Nullable Throwable exception) {
ServiceCallResult resultRequest = new ServiceCallResult();
resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE);
resultRequest.setService(StringUtils.isBlank(calleeServiceName) ? uri.getHost() : calleeServiceName);
resultRequest.setMethod(uri.getPath());
resultRequest.setRetCode(statusCode == null ? -1 : statusCode);
resultRequest.setDelay(delay);
resultRequest.setCallerService(new ServiceKey(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE));
resultRequest.setCallerIp(this.context.getConfig().getGlobal().getAPI().getBindIP());
resultRequest.setHost(StringUtils.isBlank(calleeHost) ? uri.getHost() : calleeHost);
resultRequest.setPort(calleePort == null ? getPort(uri) : calleePort);
resultRequest.setLabels(getLabels(requestHeaders));
resultRequest.setRetStatus(getRetStatusFromRequest(responseHeaders, getDefaultRetStatus(statusCode, exception)));
resultRequest.setRuleName(getActiveRuleNameFromRequest(responseHeaders));
return resultRequest;
}
/**
* createInstanceResourceStat.
* @param calleeServiceName will pick up url host when null
* @param calleeHost will pick up url host when null
* @param calleePort will pick up url port when null
* @param uri request url
* @param statusCode response status
* @param delay delay
* @param exception exception
* @return ResourceStat
*/
public ResourceStat createInstanceResourceStat(
@Nullable String calleeServiceName, @Nullable String calleeHost, @Nullable Integer calleePort,
URI uri, @Nullable Integer statusCode, long delay, @Nullable Throwable exception) {
ServiceKey calleeServiceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, StringUtils.isBlank(calleeServiceName) ? uri.getHost() : calleeServiceName);
ServiceKey callerServiceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE);
Resource resource = new InstanceResource(
calleeServiceKey,
StringUtils.isBlank(calleeHost) ? uri.getHost() : calleeHost,
calleePort == null ? getPort(uri) : calleePort,
callerServiceKey
);
return new ResourceStat(resource, statusCode == null ? -1 : statusCode, delay, getDefaultRetStatus(statusCode, exception));
}
/**
* Convert items to List.
*
@ -123,7 +201,7 @@ public abstract class AbstractPolarisReporterAdapter {
}
protected RetStatus getRetStatusFromRequest(HttpHeaders headers, RetStatus defaultVal) {
if (headers.containsKey(HeaderConstant.INTERNAL_CALLEE_RET_STATUS)) {
if (headers != null && 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));
@ -139,13 +217,50 @@ public abstract class AbstractPolarisReporterAdapter {
}
protected String getActiveRuleNameFromRequest(HttpHeaders headers) {
if (headers.containsKey(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME)) {
if (headers != null && 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 com.tencent.polaris.api.utils.StringUtils.defaultString(new ArrayList<>(values).get(0));
}
}
return "";
}
private RetStatus getDefaultRetStatus(Integer statusCode, Throwable exception) {
RetStatus retStatus = RetStatus.RetSuccess;
if (exception != null) {
retStatus = RetStatus.RetFail;
if (exception instanceof SocketTimeoutException) {
retStatus = RetStatus.RetTimeout;
}
}
else if (statusCode == null || apply(HttpStatus.resolve(statusCode))) {
retStatus = RetStatus.RetFail;
}
return retStatus;
}
private int getPort(URI uri) {
// -1 means access directly by url, and use http default port number 80
return uri.getPort() == -1 ? 80 : uri.getPort();
}
private String getLabels(HttpHeaders headers) {
if (headers != null) {
Collection<String> labels = 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) {
LOG.error("unsupported charset exception " + UTF_8, e);
}
return RequestLabelUtils.convertLabel(label);
}
}
return null;
}
}

@ -22,17 +22,17 @@ import java.util.List;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.feign.DefaultEnhancedFeignPluginRunner;
import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignBeanPostProcessor;
import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignPluginRunner;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.ExceptionPolarisReporter;
import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.SuccessPolarisReporter;
import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.ExceptionPolarisReporter;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter;
import com.tencent.cloud.rpc.enhancement.resttemplate.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.resttemplate.EnhancedRestTemplateInterceptor;
import com.tencent.cloud.rpc.enhancement.scg.EnhancedGatewayGlobalFilter;
import com.tencent.cloud.rpc.enhancement.webclient.EnhancedWebClientReporter;
import com.tencent.cloud.rpc.enhancement.webclient.PolarisLoadBalancerClientRequestTransformer;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.client.api.SDKContext;
@ -46,14 +46,12 @@ 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;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Auto Configuration for Polaris {@link feign.Feign} OR {@link RestTemplate} which can automatically bring in the call
@ -68,6 +66,26 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
@AutoConfigureAfter(PolarisContextAutoConfiguration.class)
public class RpcEnhancementAutoConfiguration {
@Bean
public EnhancedPluginRunner enhancedFeignPluginRunner(
@Autowired(required = false) List<EnhancedPlugin> enhancedPlugins) {
return new DefaultEnhancedPluginRunner(enhancedPlugins);
}
@Bean
public SuccessPolarisReporter successPolarisReporter(RpcEnhancementReporterProperties properties,
SDKContext context,
ConsumerAPI consumerAPI) {
return new SuccessPolarisReporter(properties, context, consumerAPI);
}
@Bean
public ExceptionPolarisReporter exceptionPolarisReporter(RpcEnhancementReporterProperties properties,
SDKContext context,
ConsumerAPI consumerAPI) {
return new ExceptionPolarisReporter(properties, context, consumerAPI);
}
/**
* Configuration for Polaris {@link feign.Feign} which can automatically bring in the call
* results for reporting.
@ -81,33 +99,10 @@ public class RpcEnhancementAutoConfiguration {
protected static class PolarisFeignClientAutoConfiguration {
@Bean
public EnhancedFeignPluginRunner enhancedFeignPluginRunner(
@Autowired(required = false) List<EnhancedFeignPlugin> enhancedFeignPlugins) {
return new DefaultEnhancedFeignPluginRunner(enhancedFeignPlugins);
}
@Bean
public EnhancedFeignBeanPostProcessor polarisFeignBeanPostProcessor(@Lazy EnhancedFeignPluginRunner pluginRunner) {
public EnhancedFeignBeanPostProcessor polarisFeignBeanPostProcessor(@Lazy EnhancedPluginRunner pluginRunner) {
return new EnhancedFeignBeanPostProcessor(pluginRunner);
}
@Configuration
static class PolarisReporterConfig {
@Bean
public SuccessPolarisReporter successPolarisReporter(RpcEnhancementReporterProperties properties,
@Autowired(required = false) SDKContext context,
@Autowired(required = false) ConsumerAPI 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, context, consumerAPI);
}
}
}
/**
@ -125,16 +120,15 @@ public class RpcEnhancementAutoConfiguration {
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public EnhancedRestTemplateReporter enhancedRestTemplateReporter(
RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
return new EnhancedRestTemplateReporter(properties, context, consumerAPI);
public EnhancedRestTemplateInterceptor enhancedPolarisRestTemplateReporter(@Lazy EnhancedPluginRunner pluginRunner) {
return new EnhancedRestTemplateInterceptor(pluginRunner);
}
@Bean
public SmartInitializingSingleton setErrorHandlerForRestTemplate(EnhancedRestTemplateReporter reporter) {
public SmartInitializingSingleton setPolarisReporterForRestTemplate(EnhancedRestTemplateInterceptor reporter) {
return () -> {
for (RestTemplate restTemplate : restTemplates) {
restTemplate.setErrorHandler(reporter);
restTemplate.getInterceptors().add(reporter);
}
};
}
@ -145,6 +139,7 @@ public class RpcEnhancementAutoConfiguration {
public BlockingLoadBalancerClientAspect blockingLoadBalancerClientAspect() {
return new BlockingLoadBalancerClientAspect();
}
}
@ -156,11 +151,28 @@ public class RpcEnhancementAutoConfiguration {
@ConditionalOnClass(name = "org.springframework.web.reactive.function.client.WebClient")
protected static class PolarisWebClientAutoConfiguration {
@Autowired(required = false)
private List<WebClient.Builder> webClientBuilder = Collections.emptyList();
@Bean
public ExchangeFilterFunction exchangeFilterFunction(
RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
return new EnhancedWebClientReporter(properties, context, consumerAPI);
public EnhancedWebClientReporter exchangeFilterFunction(@Lazy EnhancedPluginRunner pluginRunner) {
return new EnhancedWebClientReporter(pluginRunner);
}
@Bean
public SmartInitializingSingleton addEnhancedWebClientReporterForWebClient(EnhancedWebClientReporter reporter) {
return () -> webClientBuilder.forEach(webClient -> {
webClient.filter(reporter);
});
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerClientRequestTransformer")
public PolarisLoadBalancerClientRequestTransformer polarisLoadBalancerClientRequestTransformer() {
return new PolarisLoadBalancerClientRequestTransformer();
}
}
/**
@ -173,17 +185,11 @@ public class RpcEnhancementAutoConfiguration {
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);
@ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.GlobalFilter")
public EnhancedGatewayGlobalFilter enhancedPolarisGatewayReporter(@Lazy EnhancedPluginRunner pluginRunner) {
return new EnhancedGatewayGlobalFilter(pluginRunner);
}
}
}

@ -17,6 +17,7 @@
package com.tencent.cloud.rpc.enhancement.feign;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import feign.Client;
import org.springframework.beans.BeansException;
@ -35,11 +36,11 @@ import org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLo
*/
public class EnhancedFeignBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private final EnhancedFeignPluginRunner pluginRunner;
private final EnhancedPluginRunner pluginRunner;
private BeanFactory factory;
public EnhancedFeignBeanPostProcessor(EnhancedFeignPluginRunner pluginRunner) {
public EnhancedFeignBeanPostProcessor(EnhancedPluginRunner pluginRunner) {
this.pluginRunner = pluginRunner;
}

@ -18,17 +18,26 @@
package com.tencent.cloud.rpc.enhancement.feign;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import feign.Client;
import feign.Request;
import feign.Request.Options;
import feign.Response;
import static com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType.EXCEPTION;
import static com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType.FINALLY;
import static com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType.POST;
import static com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType.PRE;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.EXCEPTION;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.FINALLY;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.POST;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.PRE;
import static feign.Util.checkNotNull;
/**
@ -40,40 +49,64 @@ public class EnhancedFeignClient implements Client {
private final Client delegate;
private final EnhancedFeignPluginRunner pluginRunner;
private final EnhancedPluginRunner pluginRunner;
public EnhancedFeignClient(Client target, EnhancedFeignPluginRunner pluginRunner) {
public EnhancedFeignClient(Client target, EnhancedPluginRunner pluginRunner) {
this.delegate = checkNotNull(target, "target");
this.pluginRunner = pluginRunner;
}
@Override
public Response execute(Request request, Options options) throws IOException {
EnhancedFeignContext enhancedFeignContext = new EnhancedFeignContext();
enhancedFeignContext.setRequest(request);
enhancedFeignContext.setOptions(options);
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
HttpHeaders requestHeaders = new HttpHeaders();
request.headers().forEach((s, strings) -> requestHeaders.addAll(s, new ArrayList<>(strings)));
URI url = URI.create(request.url());
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
.httpHeaders(requestHeaders)
.httpMethod(HttpMethod.resolve(request.httpMethod().name()))
.url(url)
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
// Run pre enhanced feign plugins.
pluginRunner.run(PRE, enhancedFeignContext);
// Run pre enhanced plugins.
pluginRunner.run(PRE, enhancedPluginContext);
long startMillis = System.currentTimeMillis();
try {
long startMillis = System.currentTimeMillis();
Response response = delegate.execute(request, options);
enhancedFeignContext.setDelay(System.currentTimeMillis() - startMillis);
enhancedFeignContext.setResponse(response);
enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis);
HttpHeaders responseHeaders = new HttpHeaders();
response.headers().forEach((s, strings) -> responseHeaders.addAll(s, new ArrayList<>(strings)));
EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder()
.httpStatus(response.status())
.httpHeaders(responseHeaders)
.build();
enhancedPluginContext.setResponse(enhancedResponseContext);
DefaultServiceInstance serviceInstance = new DefaultServiceInstance();
serviceInstance.setServiceId(request.requestTemplate().feignTarget().name());
serviceInstance.setHost(url.getHost());
serviceInstance.setPort(url.getPort());
enhancedPluginContext.setServiceInstance(serviceInstance);
// Run post enhanced feign plugins.
pluginRunner.run(POST, enhancedFeignContext);
// Run post enhanced plugins.
pluginRunner.run(POST, enhancedPluginContext);
return response;
}
catch (IOException origin) {
enhancedFeignContext.setException(origin);
enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis);
enhancedPluginContext.setThrowable(origin);
// Run exception enhanced feign plugins.
pluginRunner.run(EXCEPTION, enhancedFeignContext);
pluginRunner.run(EXCEPTION, enhancedPluginContext);
throw origin;
}
finally {
// Run finally enhanced feign plugins.
pluginRunner.run(FINALLY, enhancedFeignContext);
// Run finally enhanced plugins.
pluginRunner.run(FINALLY, enhancedPluginContext);
}
}
}

@ -1,79 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin;
import feign.Request;
import feign.Response;
/**
* Context used by EnhancedFeignPlugin.
*
* @author Haotian Zhang
*/
public class EnhancedFeignContext {
private Request request;
private Request.Options options;
private Response response;
private Exception exception;
private long delay;
public Request getRequest() {
return request;
}
public void setRequest(Request request) {
this.request = request;
}
public Request.Options getOptions() {
return options;
}
public void setOptions(Request.Options options) {
this.options = options;
}
public Response getResponse() {
return response;
}
public void setResponse(Response response) {
this.response = response;
}
public Exception getException() {
return exception;
}
public void setException(Exception exception) {
this.exception = exception;
}
public long getDelay() {
return delay;
}
public void setDelay(long delay) {
this.delay = delay;
}
}

@ -1,113 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.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;
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 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;
}
@Override
public String getName() {
return ExceptionPolarisReporter.class.getName();
}
@Override
public EnhancedFeignPluginType getType() {
return EnhancedFeignPluginType.EXCEPTION;
}
@Override
public void run(EnhancedFeignContext context) {
if (!reporterProperties.isEnabled()) {
return;
}
if (consumerAPI != null) {
Request request = context.getRequest();
Response response = context.getResponse();
Exception exception = context.getException();
RetStatus retStatus = RetStatus.RetFail;
long delay = context.getDelay();
if (exception instanceof SocketTimeoutException) {
retStatus = RetStatus.RetTimeout;
}
LOG.debug("Will report result of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", retStatus.name(), request.httpMethod()
.name(), request.url(), response.status(), delay);
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);
}
}
@Override
public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) {
Request request = context.getRequest();
Response response = context.getResponse();
LOG.error("ExceptionPolarisReporter runs failed. Request=[{}]. Response=[{}].", request, response, throwable);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

@ -1,100 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
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;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
/**
* Util for polaris reporter.
*
* @author Haotian Zhang
*/
public final class ReporterUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(ReporterUtils.class);
private ReporterUtils() {
}
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);
RequestTemplate requestTemplate = request.requestTemplate();
String serviceName = requestTemplate.feignTarget().name();
resultRequest.setService(serviceName);
Collection<String> labels = requestTemplate.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);
}
resultRequest.setLabels(RequestLabelUtils.convertLabel(label));
}
URI uri = URI.create(request.url());
resultRequest.setMethod(uri.getPath());
resultRequest.setRetCode(response.status());
resultRequest.setRetStatus(retStatus);
resultRequest.setDelay(delay);
String scheme = uri.getScheme();
if (StringUtils.isBlank(scheme)) {
scheme = "http";
}
resultRequest.setProtocol(scheme);
String sourceNamespace = MetadataContext.LOCAL_NAMESPACE;
String sourceService = MetadataContext.LOCAL_SERVICE;
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;
}
}

@ -1,107 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.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;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
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;
/**
* Polaris reporter when feign call is successful.
*
* @author Haotian Zhang
*/
public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedFeignPlugin {
private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class);
private final ConsumerAPI consumerAPI;
private final SDKContext context;
public SuccessPolarisReporter(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
super(properties);
this.context = context;
this.consumerAPI = consumerAPI;
}
@Override
public String getName() {
return SuccessPolarisReporter.class.getName();
}
@Override
public EnhancedFeignPluginType getType() {
return EnhancedFeignPluginType.POST;
}
@Override
public void run(EnhancedFeignContext context) {
if (!reportProperties.isEnabled()) {
return;
}
if (consumerAPI != null) {
Request request = context.getRequest();
Response response = context.getResponse();
RetStatus retStatus = RetStatus.RetSuccess;
long delay = context.getDelay();
if (apply(HttpStatus.resolve(response.status()))) {
retStatus = RetStatus.RetFail;
}
LOG.debug("Will report result of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", retStatus.name(), request.httpMethod()
.name(), request.url(), response.status(), delay);
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);
}
}
@Override
public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) {
Request request = context.getRequest();
Response response = context.getResponse();
LOG.error("SuccessPolarisReporter runs failed. Request=[{}]. Response=[{}].", request, response, throwable);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

@ -16,16 +16,13 @@
*
*/
package com.tencent.cloud.rpc.enhancement.feign;
package com.tencent.cloud.rpc.enhancement.plugin;
import java.util.Comparator;
import java.util.List;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import org.springframework.util.CollectionUtils;
@ -34,14 +31,14 @@ import org.springframework.util.CollectionUtils;
*
* @author Derek Yi 2022-08-16
*/
public class DefaultEnhancedFeignPluginRunner implements EnhancedFeignPluginRunner {
public class DefaultEnhancedPluginRunner implements EnhancedPluginRunner {
private final Multimap<String, EnhancedFeignPlugin> pluginMap = ArrayListMultimap.create();
private final Multimap<String, EnhancedPlugin> pluginMap = ArrayListMultimap.create();
public DefaultEnhancedFeignPluginRunner(List<EnhancedFeignPlugin> enhancedFeignPlugins) {
if (!CollectionUtils.isEmpty(enhancedFeignPlugins)) {
enhancedFeignPlugins.stream()
.sorted(Comparator.comparing(EnhancedFeignPlugin::getOrder))
public DefaultEnhancedPluginRunner(List<EnhancedPlugin> enhancedPlugins) {
if (!CollectionUtils.isEmpty(enhancedPlugins)) {
enhancedPlugins.stream()
.sorted(Comparator.comparing(EnhancedPlugin::getOrder))
.forEach(plugin -> pluginMap.put(plugin.getType().name(), plugin));
}
}
@ -53,8 +50,8 @@ public class DefaultEnhancedFeignPluginRunner implements EnhancedFeignPluginRunn
* @param context context in enhanced feign client.
*/
@Override
public void run(EnhancedFeignPluginType pluginType, EnhancedFeignContext context) {
for (EnhancedFeignPlugin plugin : pluginMap.get(pluginType.name())) {
public void run(EnhancedPluginType pluginType, EnhancedPluginContext context) {
for (EnhancedPlugin plugin : pluginMap.get(pluginType.name())) {
try {
plugin.run(context);
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin;
package com.tencent.cloud.rpc.enhancement.plugin;
import org.springframework.core.Ordered;
@ -24,7 +24,7 @@ import org.springframework.core.Ordered;
*
* @author Haotian Zhang
*/
public interface EnhancedFeignPlugin extends Ordered {
public interface EnhancedPlugin extends Ordered {
/**
* Get name of plugin.
@ -38,9 +38,9 @@ public interface EnhancedFeignPlugin extends Ordered {
/**
* Get type of plugin.
*
* @return {@link EnhancedFeignPluginType}
* @return {@link EnhancedPluginType}
*/
EnhancedFeignPluginType getType();
EnhancedPluginType getType();
/**
* Run the plugin.
@ -48,15 +48,15 @@ public interface EnhancedFeignPlugin extends Ordered {
* @param context context in enhanced feign client.
* @throws Throwable throwable thrown from run method.
*/
void run(EnhancedFeignContext context) throws Throwable;
void run(EnhancedPluginContext context) throws Throwable;
/**
* Handler throwable from {@link EnhancedFeignPlugin#run(EnhancedFeignContext)}.
* Handler throwable from {@link EnhancedPlugin#run(EnhancedPluginContext)}.
*
* @param context context in enhanced feign client.
* @param throwable throwable thrown from run method.
*/
default void handlerThrowable(EnhancedFeignContext context, Throwable throwable) {
default void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
}
}

@ -0,0 +1,90 @@
/*
* 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.plugin;
import org.springframework.cloud.client.ServiceInstance;
/**
* Context used by EnhancedPlugin.
*
* @author Haotian Zhang
*/
public class EnhancedPluginContext {
private EnhancedRequestContext request;
private EnhancedResponseContext response;
private Throwable throwable;
private long delay;
private ServiceInstance serviceInstance;
public EnhancedRequestContext getRequest() {
return request;
}
public void setRequest(EnhancedRequestContext request) {
this.request = request;
}
public EnhancedResponseContext getResponse() {
return response;
}
public void setResponse(EnhancedResponseContext response) {
this.response = response;
}
public Throwable getThrowable() {
return throwable;
}
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
}
public long getDelay() {
return delay;
}
public void setDelay(long delay) {
this.delay = delay;
}
public ServiceInstance getServiceInstance() {
return serviceInstance;
}
public void setServiceInstance(ServiceInstance serviceInstance) {
this.serviceInstance = serviceInstance;
}
@Override
public String toString() {
return "EnhancedPluginContext{" +
"request=" + request +
", response=" + response +
", throwable=" + throwable +
", delay=" + delay +
", serviceInstance=" + serviceInstance +
'}';
}
}

@ -16,17 +16,14 @@
*
*/
package com.tencent.cloud.rpc.enhancement.feign;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
package com.tencent.cloud.rpc.enhancement.plugin;
/**
* Plugin runner.
*
* @author Derek Yi 2022-08-16
*/
public interface EnhancedFeignPluginRunner {
public interface EnhancedPluginRunner {
/**
* run the plugin.
@ -34,5 +31,5 @@ public interface EnhancedFeignPluginRunner {
* @param pluginType type of plugin
* @param context context in enhanced feign client.
*/
void run(EnhancedFeignPluginType pluginType, EnhancedFeignContext context);
void run(EnhancedPluginType pluginType, EnhancedPluginContext context);
}

@ -15,14 +15,14 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin;
package com.tencent.cloud.rpc.enhancement.plugin;
/**
* Type of EnhancedFeignPlugin.
* Type of EnhancedPlugin.
*
* @author Haotian Zhang
*/
public enum EnhancedFeignPluginType {
public enum EnhancedPluginType {
/**
* Pre feign plugin.

@ -0,0 +1,107 @@
/*
* 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.plugin;
import java.net.URI;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
/**
* EnhancedRequestContext.
*
* @author sean yu
*/
public class EnhancedRequestContext {
private HttpMethod httpMethod;
private HttpHeaders httpHeaders;
private URI url;
public HttpMethod getHttpMethod() {
return httpMethod;
}
public void setHttpMethod(HttpMethod httpMethod) {
this.httpMethod = httpMethod;
}
public HttpHeaders getHttpHeaders() {
return httpHeaders;
}
public void setHttpHeaders(HttpHeaders httpHeaders) {
this.httpHeaders = httpHeaders;
}
public URI getUrl() {
return url;
}
public void setUrl(URI url) {
this.url = url;
}
public static EnhancedContextRequestBuilder builder() {
return new EnhancedContextRequestBuilder();
}
@Override
public String toString() {
return "EnhancedRequestContext{" +
"httpMethod=" + httpMethod +
", httpHeaders=" + httpHeaders +
", url=" + url +
'}';
}
public static final class EnhancedContextRequestBuilder {
private HttpMethod httpMethod;
private HttpHeaders httpHeaders;
private URI url;
private EnhancedContextRequestBuilder() {
}
public EnhancedContextRequestBuilder httpMethod(HttpMethod httpMethod) {
this.httpMethod = httpMethod;
return this;
}
public EnhancedContextRequestBuilder httpHeaders(HttpHeaders httpHeaders) {
this.httpHeaders = httpHeaders;
return this;
}
public EnhancedContextRequestBuilder url(URI url) {
this.url = url;
return this;
}
public EnhancedRequestContext build() {
EnhancedRequestContext enhancedRequestContext = new EnhancedRequestContext();
enhancedRequestContext.httpMethod = this.httpMethod;
enhancedRequestContext.url = this.url;
enhancedRequestContext.httpHeaders = this.httpHeaders;
return enhancedRequestContext;
}
}
}

@ -0,0 +1,86 @@
/*
* 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.plugin;
import org.springframework.http.HttpHeaders;
/**
* EnhancedResponseContext.
*
* @author sean yu
*/
public class EnhancedResponseContext {
private Integer httpStatus;
private HttpHeaders httpHeaders;
public Integer getHttpStatus() {
return httpStatus;
}
public void setHttpStatus(Integer httpStatus) {
this.httpStatus = httpStatus;
}
public HttpHeaders getHttpHeaders() {
return httpHeaders;
}
public void setHttpHeaders(HttpHeaders httpHeaders) {
this.httpHeaders = httpHeaders;
}
public static EnhancedContextResponseBuilder builder() {
return new EnhancedContextResponseBuilder();
}
@Override
public String toString() {
return "EnhancedResponseContext{" +
"httpStatus=" + httpStatus +
", httpHeaders=" + httpHeaders +
'}';
}
public static final class EnhancedContextResponseBuilder {
private Integer httpStatus;
private HttpHeaders httpHeaders;
private EnhancedContextResponseBuilder() {
}
public EnhancedContextResponseBuilder httpStatus(Integer httpStatus) {
this.httpStatus = httpStatus;
return this;
}
public EnhancedContextResponseBuilder httpHeaders(HttpHeaders httpHeaders) {
this.httpHeaders = httpHeaders;
return this;
}
public EnhancedResponseContext build() {
EnhancedResponseContext enhancedResponseContext = new EnhancedResponseContext();
enhancedResponseContext.setHttpStatus(httpStatus);
enhancedResponseContext.setHttpHeaders(httpHeaders);
return enhancedResponseContext;
}
}
}

@ -0,0 +1,106 @@
/*
* 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.plugin.reporter;
import java.util.Optional;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.Ordered;
/**
* Polaris reporter when feign call fails.
*
* @author Haotian Zhang
*/
public class ExceptionPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedPlugin {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionPolarisReporter.class);
private final ConsumerAPI consumerAPI;
public ExceptionPolarisReporter(RpcEnhancementReporterProperties reporterProperties,
SDKContext context,
ConsumerAPI consumerAPI) {
super(reporterProperties, context);
this.consumerAPI = consumerAPI;
}
@Override
public String getName() {
return ExceptionPolarisReporter.class.getName();
}
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.EXCEPTION;
}
@Override
public void run(EnhancedPluginContext context) {
if (!super.reportProperties.isEnabled()) {
return;
}
EnhancedRequestContext request = context.getRequest();
ServiceInstance serviceInstance = Optional.ofNullable(context.getServiceInstance()).orElse(new DefaultServiceInstance());
ServiceCallResult resultRequest = createServiceCallResult(
serviceInstance.getServiceId(),
serviceInstance.getHost(),
serviceInstance.getPort(),
request.getUrl(),
request.getHttpHeaders(),
null,
null,
context.getDelay(),
context.getThrowable()
);
LOG.debug("Will report ServiceCallResult of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.",
resultRequest.getRetStatus().name(), request.getHttpMethod().name(), request.getUrl().getPath(), context.getThrowable().getMessage(), context.getDelay());
consumerAPI.updateServiceCallResult(resultRequest);
}
@Override
public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
LOG.error("ExceptionPolarisReporter runs failed. context=[{}].",
context, throwable);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

@ -0,0 +1,106 @@
/*
* 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.plugin.reporter;
import java.util.Optional;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.Ordered;
/**
* Polaris reporter when feign call is successful.
*
* @author Haotian Zhang
*/
public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedPlugin {
private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class);
private final ConsumerAPI consumerAPI;
public SuccessPolarisReporter(RpcEnhancementReporterProperties properties,
SDKContext context,
ConsumerAPI consumerAPI) {
super(properties, context);
this.consumerAPI = consumerAPI;
}
@Override
public String getName() {
return SuccessPolarisReporter.class.getName();
}
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.POST;
}
@Override
public void run(EnhancedPluginContext context) {
if (!super.reportProperties.isEnabled()) {
return;
}
EnhancedRequestContext request = context.getRequest();
EnhancedResponseContext response = context.getResponse();
ServiceInstance serviceInstance = Optional.ofNullable(context.getServiceInstance()).orElse(new DefaultServiceInstance());
ServiceCallResult resultRequest = createServiceCallResult(
serviceInstance.getServiceId(),
serviceInstance.getHost(),
serviceInstance.getPort(),
request.getUrl(),
request.getHttpHeaders(),
response.getHttpHeaders(),
response.getHttpStatus(),
context.getDelay(),
null
);
LOG.debug("Will report ServiceCallResult of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.",
resultRequest.getRetStatus().name(), request.getHttpMethod().name(), request.getUrl().getPath(), response.getHttpStatus(), context.getDelay());
consumerAPI.updateServiceCallResult(resultRequest);
}
@Override
public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
LOG.error("SuccessPolarisReporter runs failed. context=[{}].",
context, throwable);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

@ -0,0 +1,105 @@
/*
* 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.resttemplate;
import java.io.IOException;
import java.util.Map;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.EXCEPTION;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.FINALLY;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.POST;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.PRE;
/**
* EnhancedRestTemplateInterceptor.
*
* @author sean yu
*/
public class EnhancedRestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final EnhancedPluginRunner pluginRunner;
public EnhancedRestTemplateInterceptor(EnhancedPluginRunner pluginRunner) {
this.pluginRunner = pluginRunner;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
.httpHeaders(request.getHeaders())
.httpMethod(request.getMethod())
.url(request.getURI())
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
// Run pre enhanced plugins.
pluginRunner.run(PRE, enhancedPluginContext);
long startMillis = System.currentTimeMillis();
try {
ClientHttpResponse response = execution.execute(request, body);
enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis);
EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder()
.httpStatus(response.getRawStatusCode())
.httpHeaders(response.getHeaders())
.build();
enhancedPluginContext.setResponse(enhancedResponseContext);
Map<String, String> loadBalancerContext = MetadataContextHolder.get().getLoadbalancerMetadata();
DefaultServiceInstance serviceInstance = new DefaultServiceInstance();
serviceInstance.setServiceId(request.getURI().getHost());
serviceInstance.setHost(loadBalancerContext.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST));
if (loadBalancerContext.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT) != null) {
serviceInstance.setPort(Integer.parseInt(loadBalancerContext.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT)));
}
enhancedPluginContext.setServiceInstance(serviceInstance);
// Run post enhanced plugins.
pluginRunner.run(POST, enhancedPluginContext);
return response;
}
catch (IOException e) {
enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis);
enhancedPluginContext.setThrowable(e);
// Run exception enhanced plugins.
pluginRunner.run(EXCEPTION, enhancedPluginContext);
throw e;
}
finally {
// Run finally enhanced plugins.
pluginRunner.run(FINALLY, enhancedPluginContext);
}
}
}

@ -1,237 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.resttemplate;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
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;
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 org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.ResponseErrorHandler;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
/**
* Extend ResponseErrorHandler to get request information.
*
* @author wh 2022/6/21
*/
public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter implements ResponseErrorHandler, ApplicationContextAware {
/**
* Polaris-CircuitBreaker-Fallback header flag.
*/
public static final String POLARIS_CIRCUIT_BREAKER_FALLBACK_HEADER = "X-SCT-Polaris-CircuitBreaker-Fallback";
/**
* response has error header flag, since EnhancedRestTemplateReporter#hasError always return true.
*/
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, SDKContext context, ConsumerAPI consumerAPI) {
super(properties);
this.context = context;
this.consumerAPI = consumerAPI;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
String[] handlerBeanNames = applicationContext.getBeanNamesForType(ResponseErrorHandler.class);
if (handlerBeanNames.length == 1) {
if (this.delegateHandler == null) {
this.delegateHandler = new DefaultResponseErrorHandler();
}
return;
}
// inject user custom ResponseErrorHandler
for (String beanName : handlerBeanNames) {
// ignore self
if (StringUtils.equalsIgnoreCase("enhancedRestTemplateReporter", beanName)) {
continue;
}
this.delegateHandler = (ResponseErrorHandler) applicationContext.getBean(beanName);
}
}
@Override
public boolean hasError(@NonNull ClientHttpResponse response) throws IOException {
if (delegateHandler != null) {
// Preserve the delegated handler result
boolean hasError = delegateHandler.hasError(response);
response.getHeaders().add(HEADER_HAS_ERROR, String.valueOf(hasError));
}
return true;
}
@Override
public void handleError(@NonNull ClientHttpResponse response) throws IOException {
if (realHasError(response)) {
delegateHandler.handleError(response);
}
clear(response);
}
@Override
public void handleError(@NonNull URI url, @NonNull HttpMethod method, @NonNull ClientHttpResponse response) throws IOException {
// report result to polaris
if (reportProperties.isEnabled()) {
reportResult(url, response);
}
// invoke delegate handler
invokeDelegateHandler(url, method, response);
}
private void reportResult(URI url, ClientHttpResponse response) {
if (Boolean.parseBoolean(response.getHeaders().getFirst(POLARIS_CIRCUIT_BREAKER_FALLBACK_HEADER))) {
return;
}
try {
ServiceCallResult resultRequest = createServiceCallResult(url, response);
Map<String, String> loadBalancerContext = MetadataContextHolder.get().getLoadbalancerMetadata();
String targetHost = loadBalancerContext.get("host");
String targetPort = loadBalancerContext.get("port");
String startMillis = loadBalancerContext.get("startMillis");
long delay = System.currentTimeMillis() - Long.parseLong(startMillis);
if (StringUtils.isBlank(targetHost) || StringUtils.isBlank(targetPort)) {
LOGGER.warn("Can not get target host or port from metadata context. host = {}, port = {}", targetHost, targetPort);
return;
}
resultRequest.setHost(targetHost);
resultRequest.setPort(Integer.parseInt(targetPort));
resultRequest.setDelay(delay);
// checking response http status code
HttpStatusCode httpStatusCode = response.getStatusCode();
HttpStatus httpStatus = HttpStatus.valueOf(httpStatusCode.value());
if (apply(httpStatus)) {
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)) {
String label = labels.get(0);
try {
label = URLDecoder.decode(label, UTF_8);
}
catch (UnsupportedEncodingException e) {
LOGGER.error("unsupported charset exception " + UTF_8, e);
}
resultRequest.setLabels(RequestLabelUtils.convertLabel(label));
}
// processing report with consumerAPI .
LOGGER.debug("Will report result of {}. Request=[{}]. Response=[{}]. Delay=[{}]ms.", resultRequest.getRetStatus()
.name(), url, httpStatusCode.value(), delay);
consumerAPI.updateServiceCallResult(resultRequest);
}
catch (Exception e) {
LOGGER.error("RestTemplate response reporter execute failed of {} url {}", response, url, e);
}
}
private void invokeDelegateHandler(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
if (realHasError(response)) {
delegateHandler.handleError(url, method, response);
}
clear(response);
}
private Boolean realHasError(ClientHttpResponse response) {
if (delegateHandler == null) {
return false;
}
String hasErrorHeader = response.getHeaders().getFirst(HEADER_HAS_ERROR);
if (StringUtils.isBlank(hasErrorHeader)) {
return false;
}
return Boolean.parseBoolean(hasErrorHeader);
}
private void clear(ClientHttpResponse response) {
response.getHeaders().remove(HEADER_HAS_ERROR);
response.getHeaders().remove(POLARIS_CIRCUIT_BREAKER_FALLBACK_HEADER);
}
private ServiceCallResult createServiceCallResult(URI uri, ClientHttpResponse response) throws IOException {
ServiceCallResult resultRequest = new ServiceCallResult();
String serviceName = uri.getHost();
resultRequest.setService(serviceName);
resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE);
resultRequest.setMethod(uri.getPath());
resultRequest.setRetCode(response.getStatusCode().value());
resultRequest.setRetStatus(RetStatus.RetSuccess);
String sourceNamespace = MetadataContext.LOCAL_NAMESPACE;
String sourceService = MetadataContext.LOCAL_SERVICE;
if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) {
resultRequest.setCallerService(new ServiceKey(sourceNamespace, sourceService));
}
return resultRequest;
}
protected ResponseErrorHandler getDelegateHandler() {
return this.delegateHandler;
}
protected void setDelegateHandler(ResponseErrorHandler delegateHandler) {
this.delegateHandler = delegateHandler;
}
}

@ -17,6 +17,7 @@
package com.tencent.cloud.rpc.enhancement.resttemplate;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
@ -36,9 +37,8 @@ public final class LoadBalancerClientAspectUtils {
Object server = joinPoint.getArgs()[0];
if (server instanceof ServiceInstance) {
ServiceInstance instance = (ServiceInstance) server;
MetadataContextHolder.get().setLoadbalancer("host", instance.getHost());
MetadataContextHolder.get().setLoadbalancer("port", String.valueOf(instance.getPort()));
MetadataContextHolder.get().setLoadbalancer("startMillis", String.valueOf(System.currentTimeMillis()));
MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST, instance.getHost());
MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT, String.valueOf(instance.getPort()));
}
}
}

@ -1,29 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.resttemplate;
import org.springframework.web.client.ResponseErrorHandler;
/**
* Polaris Response Error Handler Definition Of {@link ResponseErrorHandler}.
*
* @author wh 2022/6/21
*/
public interface PolarisResponseErrorHandler extends ResponseErrorHandler {
}

@ -0,0 +1,104 @@
/*
* 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.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.EXCEPTION;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.FINALLY;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.POST;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.PRE;
import static org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR;
/**
* EnhancedGatewayGlobalFilter.
*
* @author sean yu
*/
public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered {
private final EnhancedPluginRunner pluginRunner;
public EnhancedGatewayGlobalFilter(EnhancedPluginRunner pluginRunner) {
this.pluginRunner = pluginRunner;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
.httpHeaders(exchange.getRequest().getHeaders())
.httpMethod(exchange.getRequest().getMethod())
.url(exchange.getRequest().getURI())
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
// Run pre enhanced plugins.
pluginRunner.run(PRE, enhancedPluginContext);
long startTime = System.currentTimeMillis();
return chain.filter(exchange)
.doOnSubscribe(v -> {
Response<ServiceInstance> serviceInstanceResponse = exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR);
if (serviceInstanceResponse != null && serviceInstanceResponse.hasServer()) {
ServiceInstance instance = serviceInstanceResponse.getServer();
enhancedPluginContext.setServiceInstance(instance);
}
})
.doOnSuccess(v -> {
enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime);
EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder()
.httpStatus(exchange.getResponse().getRawStatusCode())
.httpHeaders(exchange.getResponse().getHeaders())
.build();
enhancedPluginContext.setResponse(enhancedResponseContext);
// Run post enhanced plugins.
pluginRunner.run(POST, enhancedPluginContext);
})
.doOnError(t -> {
enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime);
enhancedPluginContext.setThrowable(t);
// Run exception enhanced plugins.
pluginRunner.run(EXCEPTION, enhancedPluginContext);
})
.doFinally(v -> {
// Run finally enhanced plugins.
pluginRunner.run(FINALLY, enhancedPluginContext);
});
}
@Override
public int getOrder() {
return LOAD_BALANCER_CLIENT_FILTER_ORDER + 1;
}
}

@ -1,173 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.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);
}
}
}

@ -1,43 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.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);
}
}

@ -1,71 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.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);
}
}
}

@ -17,129 +17,81 @@
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 java.util.Map;
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 com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import reactor.util.context.ContextView;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.cloud.client.DefaultServiceInstance;
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;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.EXCEPTION;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.FINALLY;
import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.POST;
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;
/**
* EnhancedWebClientReporter.
*
* @author sean yu
*/
public class EnhancedWebClientReporter implements ExchangeFilterFunction {
private final EnhancedPluginRunner pluginRunner;
public EnhancedWebClientReporter(RpcEnhancementReporterProperties reportProperties, SDKContext context, ConsumerAPI consumerAPI) {
super(reportProperties);
this.context = context;
this.consumerAPI = consumerAPI;
public EnhancedWebClientReporter(EnhancedPluginRunner pluginRunner) {
this.pluginRunner = pluginRunner;
}
@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(HttpStatus.valueOf(response.statusCode().value()))) {
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());
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
.httpHeaders(request.headers())
.httpMethod(request.method())
.url(request.url())
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
long startTime = System.currentTimeMillis();
return next.exchange(request)
.doOnSubscribe(subscription -> {
Map<String, String> loadBalancerContext = MetadataContextHolder.get().getLoadbalancerMetadata();
DefaultServiceInstance serviceInstance = new DefaultServiceInstance();
serviceInstance.setServiceId(loadBalancerContext.get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID));
serviceInstance.setHost(request.url().getHost());
serviceInstance.setPort(request.url().getPort());
enhancedPluginContext.setServiceInstance(serviceInstance);
})
.doOnSuccess(response -> {
enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime);
EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder()
.httpStatus(response.rawStatusCode())
.httpHeaders(response.headers().asHttpHeaders())
.build();
enhancedPluginContext.setResponse(enhancedResponseContext);
// Run post enhanced plugins.
pluginRunner.run(POST, enhancedPluginContext);
})
.doOnError(t -> {
enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime);
enhancedPluginContext.setThrowable(t);
// Run exception enhanced plugins.
pluginRunner.run(EXCEPTION, enhancedPluginContext);
})
.doFinally(v -> {
// Run finally enhanced plugins.
pluginRunner.run(FINALLY, enhancedPluginContext);
});
}
}

@ -15,30 +15,28 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.loadbalancer.reactive;
package com.tencent.cloud.rpc.enhancement.webclient;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
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;
/**
* PolarisLoadBalancerClientRequestTransformer.
*
* @author sean yu
*/
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());
MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, instance.getServiceId());
}
return request;
}
}

@ -17,32 +17,196 @@
package com.tencent.cloud.rpc.enhancement;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
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.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
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.plugin.circuitbreaker.ResourceStat;
import com.tencent.polaris.api.pojo.RetStatus;
import org.assertj.core.api.Assertions;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
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.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
/**
* Test For {@link AbstractPolarisReporterAdapter}.
*
* @author <a href="mailto:iskp.me@gmail.com">Elve.Xu</a> 2022/7/11
*/
@ExtendWith(MockitoExtension.class)
public class AbstractPolarisReporterAdapterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private final RpcEnhancementReporterProperties reporterProperties = new RpcEnhancementReporterProperties();
@Mock
private SDKContext sdkContext;
@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 testServiceCallResult() throws URISyntaxException {
APIConfig apiConfig = mock(APIConfig.class);
doReturn("0.0.0.0").when(apiConfig).getBindIP();
GlobalConfig globalConfig = mock(GlobalConfig.class);
doReturn(apiConfig).when(globalConfig).getAPI();
Configuration configuration = mock(Configuration.class);
doReturn(globalConfig).when(configuration).getGlobal();
doReturn(configuration).when(sdkContext).getConfig();
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(reporterProperties, sdkContext);
ServiceCallResult serviceCallResult;
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add(RouterConstant.ROUTER_LABEL_HEADER, "{\"k1\":\"v1\"}");
serviceCallResult = adapter.createServiceCallResult(
"test",
null,
null,
new URI("http://0.0.0.0/"),
requestHeaders,
new HttpHeaders(),
200,
0,
null
);
assertThat(serviceCallResult.getRetStatus()).isEqualTo(RetStatus.RetSuccess);
serviceCallResult = adapter.createServiceCallResult(
"test",
null,
null,
new URI("http://0.0.0.0/"),
requestHeaders,
new HttpHeaders(),
502,
0,
null
);
assertThat(serviceCallResult.getRetStatus()).isEqualTo(RetStatus.RetFail);
serviceCallResult = adapter.createServiceCallResult(
"test",
null,
null,
new URI("http://0.0.0.0/"),
requestHeaders,
null,
null,
0,
new SocketTimeoutException()
);
assertThat(serviceCallResult.getRetStatus()).isEqualTo(RetStatus.RetTimeout);
serviceCallResult = adapter.createServiceCallResult(
"test",
"0.0.0.0",
8080,
new URI("/"),
requestHeaders,
new HttpHeaders(),
200,
0,
null
);
assertThat(serviceCallResult.getRetStatus()).isEqualTo(RetStatus.RetSuccess);
assertThat(serviceCallResult.getHost()).isEqualTo("0.0.0.0");
assertThat(serviceCallResult.getPort()).isEqualTo(8080);
}
@Test
public void testResourceStat() throws URISyntaxException {
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(reporterProperties, sdkContext);
ResourceStat resourceStat;
resourceStat = adapter.createInstanceResourceStat("test",
null,
null,
new URI("http://0.0.0.0/"),
200,
0,
null
);
assertThat(resourceStat.getRetStatus()).isEqualTo(RetStatus.RetSuccess);
resourceStat = adapter.createInstanceResourceStat("test",
null,
null,
new URI("http://0.0.0.0/"),
null,
0,
new SocketTimeoutException()
);
assertThat(resourceStat.getRetStatus()).isEqualTo(RetStatus.RetTimeout);
resourceStat = adapter.createInstanceResourceStat("test",
null,
null,
new URI("http://0.0.0.0/"),
200,
0,
null
);
assertThat(resourceStat.getRetStatus()).isEqualTo(RetStatus.RetSuccess);
}
@Test
public void testApplyWithDefaultConfig() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
// Mock Condition
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext);
// Assert
Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
}
@Test
@ -52,12 +216,12 @@ public class AbstractPolarisReporterAdapterTest {
properties.getStatuses().clear();
properties.setIgnoreInternalServerError(false);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext);
// Assert
Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(true);
Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(true);
assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
}
@Test
@ -67,12 +231,12 @@ public class AbstractPolarisReporterAdapterTest {
properties.getStatuses().clear();
properties.setIgnoreInternalServerError(true);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext);
// Assert
Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
}
@Test
@ -82,12 +246,12 @@ public class AbstractPolarisReporterAdapterTest {
properties.getStatuses().clear();
properties.getSeries().clear();
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext);
// Assert
Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
}
@Test
@ -98,15 +262,16 @@ public class AbstractPolarisReporterAdapterTest {
properties.getSeries().clear();
properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext);
// Assert
Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.FORBIDDEN)).isEqualTo(true);
assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(false);
assertThat(adapter.apply(HttpStatus.FORBIDDEN)).isEqualTo(true);
}
@Test
public void testGetRetStatusFromRequest() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
@ -115,19 +280,19 @@ public class AbstractPolarisReporterAdapterTest {
properties.getSeries().clear();
properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext);
HttpHeaders headers = new HttpHeaders();
RetStatus ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail);
Assertions.assertThat(ret).isEqualTo(RetStatus.RetFail);
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);
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);
assertThat(ret).isEqualTo(RetStatus.RetReject);
}
@Test
@ -138,15 +303,15 @@ public class AbstractPolarisReporterAdapterTest {
properties.getSeries().clear();
properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext);
HttpHeaders headers = new HttpHeaders();
String ruleName = adapter.getActiveRuleNameFromRequest(headers);
Assertions.assertThat(ruleName).isEqualTo("");
assertThat(ruleName).isEqualTo("");
headers.set(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, "mock_rule");
ruleName = adapter.getActiveRuleNameFromRequest(headers);
Assertions.assertThat(ruleName).isEqualTo("mock_rule");
assertThat(ruleName).isEqualTo("mock_rule");
}
/**
@ -154,8 +319,8 @@ public class AbstractPolarisReporterAdapterTest {
*/
public static class SimplePolarisReporterAdapter extends AbstractPolarisReporterAdapter {
public SimplePolarisReporterAdapter(RpcEnhancementReporterProperties properties) {
super(properties);
protected SimplePolarisReporterAdapter(RpcEnhancementReporterProperties reportProperties, SDKContext context) {
super(reportProperties, context);
}
}
}

@ -19,10 +19,10 @@ package com.tencent.cloud.rpc.enhancement.config;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignBeanPostProcessor;
import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignPluginRunner;
import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.ExceptionPolarisReporter;
import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.SuccessPolarisReporter;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.ExceptionPolarisReporter;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor;
import com.tencent.polaris.api.core.ConsumerAPI;
import org.junit.jupiter.api.Test;
@ -56,14 +56,12 @@ public class RpcEnhancementAutoConfigurationTest {
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(ConsumerAPI.class);
assertThat(context).hasSingleBean(EnhancedFeignPluginRunner.class);
assertThat(context).hasSingleBean(EnhancedPluginRunner.class);
assertThat(context).hasSingleBean(EnhancedFeignBeanPostProcessor.class);
assertThat(context).hasSingleBean(SuccessPolarisReporter.class);
assertThat(context).hasSingleBean(ExceptionPolarisReporter.class);
assertThat(context).hasSingleBean(EnhancedRestTemplateReporter.class);
assertThat(context).hasSingleBean(EnhancedRestTemplateInterceptor.class);
assertThat(context).hasSingleBean(RestTemplate.class);
RestTemplate restTemplate = context.getBean(RestTemplate.class);
assertThat(restTemplate.getErrorHandler() instanceof EnhancedRestTemplateReporter).isTrue();
});
}

@ -40,7 +40,8 @@ import static org.springframework.http.HttpStatus.Series.SERVER_ERROR;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = RpcEnhancementReporterPropertiesTest.TestApplication.class, properties = {
"spring.application.name=test",
"spring.cloud.gateway.enabled=false"
"spring.cloud.gateway.enabled=false",
"spring.cloud.tencent.rpc-enhancement.reporter=true"
})
@ActiveProfiles("test")
public class RpcEnhancementReporterPropertiesTest {
@ -58,6 +59,7 @@ public class RpcEnhancementReporterPropertiesTest {
assertThat(rpcEnhancementReporterProperties.getStatuses()).isNotEmpty();
assertThat(rpcEnhancementReporterProperties.getStatuses().get(0)).isEqualTo(MULTIPLE_CHOICES);
assertThat(rpcEnhancementReporterProperties.getStatuses().get(1)).isEqualTo(MOVED_PERMANENTLY);
assertThat(rpcEnhancementReporterProperties.isEnabled()).isEqualTo(true);
}
@SpringBootApplication

@ -22,9 +22,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import feign.Client;
import feign.Request;
import feign.RequestTemplate;
@ -72,9 +73,9 @@ public class EnhancedFeignClientTest {
fail("Exception encountered.", e);
}
List<EnhancedFeignPlugin> enhancedFeignPlugins = getMockEnhancedFeignPlugins();
List<EnhancedPlugin> enhancedPlugins = getMockEnhancedFeignPlugins();
try {
new EnhancedFeignClient(mock(Client.class), new DefaultEnhancedFeignPluginRunner(enhancedFeignPlugins));
new EnhancedFeignClient(mock(Client.class), new DefaultEnhancedPluginRunner(enhancedPlugins));
}
catch (Throwable e) {
fail("Exception encountered.", e);
@ -103,7 +104,7 @@ public class EnhancedFeignClientTest {
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.feignTarget(target);
EnhancedFeignClient polarisFeignClient = new EnhancedFeignClient(delegate, new DefaultEnhancedFeignPluginRunner(getMockEnhancedFeignPlugins()));
EnhancedFeignClient polarisFeignClient = new EnhancedFeignClient(delegate, new DefaultEnhancedPluginRunner(getMockEnhancedFeignPlugins()));
// 200
Response response = polarisFeignClient.execute(Request.create(Request.HttpMethod.GET, "http://localhost:8080/test",
@ -127,22 +128,22 @@ public class EnhancedFeignClientTest {
}
}
private List<EnhancedFeignPlugin> getMockEnhancedFeignPlugins() {
List<EnhancedFeignPlugin> enhancedFeignPlugins = new ArrayList<>();
private List<EnhancedPlugin> getMockEnhancedFeignPlugins() {
List<EnhancedPlugin> enhancedPlugins = new ArrayList<>();
enhancedFeignPlugins.add(new EnhancedFeignPlugin() {
enhancedPlugins.add(new EnhancedPlugin() {
@Override
public EnhancedFeignPluginType getType() {
return EnhancedFeignPluginType.PRE;
public EnhancedPluginType getType() {
return EnhancedPluginType.PRE;
}
@Override
public void run(EnhancedFeignContext context) {
public void run(EnhancedPluginContext context) {
}
@Override
public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) {
public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
}
@ -152,19 +153,19 @@ public class EnhancedFeignClientTest {
}
});
enhancedFeignPlugins.add(new EnhancedFeignPlugin() {
enhancedPlugins.add(new EnhancedPlugin() {
@Override
public EnhancedFeignPluginType getType() {
return EnhancedFeignPluginType.POST;
public EnhancedPluginType getType() {
return EnhancedPluginType.POST;
}
@Override
public void run(EnhancedFeignContext context) {
public void run(EnhancedPluginContext context) {
}
@Override
public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) {
public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
}
@ -174,19 +175,19 @@ public class EnhancedFeignClientTest {
}
});
enhancedFeignPlugins.add(new EnhancedFeignPlugin() {
enhancedPlugins.add(new EnhancedPlugin() {
@Override
public EnhancedFeignPluginType getType() {
return EnhancedFeignPluginType.EXCEPTION;
public EnhancedPluginType getType() {
return EnhancedPluginType.EXCEPTION;
}
@Override
public void run(EnhancedFeignContext context) {
public void run(EnhancedPluginContext context) {
}
@Override
public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) {
public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
}
@ -196,19 +197,19 @@ public class EnhancedFeignClientTest {
}
});
enhancedFeignPlugins.add(new EnhancedFeignPlugin() {
enhancedPlugins.add(new EnhancedPlugin() {
@Override
public EnhancedFeignPluginType getType() {
return EnhancedFeignPluginType.FINALLY;
public EnhancedPluginType getType() {
return EnhancedPluginType.FINALLY;
}
@Override
public void run(EnhancedFeignContext context) {
public void run(EnhancedPluginContext context) {
}
@Override
public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) {
public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
}
@ -218,7 +219,7 @@ public class EnhancedFeignClientTest {
}
});
return enhancedFeignPlugins;
return enhancedPlugins;
}

@ -1,46 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin;
import feign.Request;
import feign.Response;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Test for {@link EnhancedFeignContext}.
*
* @author Haotian Zhang
*/
public class EnhancedFeignContextTest {
@Test
public void testGetAndSet() {
EnhancedFeignContext enhancedFeignContext = new EnhancedFeignContext();
enhancedFeignContext.setRequest(mock(Request.class));
enhancedFeignContext.setOptions(mock(Request.Options.class));
enhancedFeignContext.setResponse(mock(Response.class));
enhancedFeignContext.setException(mock(Exception.class));
assertThat(enhancedFeignContext.getRequest()).isNotNull();
assertThat(enhancedFeignContext.getOptions()).isNotNull();
assertThat(enhancedFeignContext.getResponse()).isNotNull();
assertThat(enhancedFeignContext.getException()).isNotNull();
}
}

@ -1,135 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
import java.io.UnsupportedEncodingException;
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;
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.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
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.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
/**
* Test for {@link ReporterUtils}.
*
* @author Haotian Zhang
*/
@ExtendWith(MockitoExtension.class)
public class ReporterUtilsTest {
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 testCreateServiceCallResult() {
// mock target
Target<?> target = mock(Target.class);
doReturn(SERVICE_PROVIDER).when(target).name();
// mock RequestTemplate.class
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.feignTarget(target);
try {
requestTemplate.header(RouterConstant.ROUTER_LABEL_HEADER, URLEncoder.encode("{\"k1\":\"v1\",\"k2\":\"v2\"}", UTF_8));
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException("unsupported charset exception " + UTF_8);
}
// mock request
Request request = mock(Request.class);
doReturn(requestTemplate).when(request).requestTemplate();
doReturn("http://1.1.1.1:2345/path").when(request).url();
// mock request
Response response = mock(Response.class);
doReturn(502).when(response).status();
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");
assertThat(serviceCallResult.getPort()).isEqualTo(2345);
assertThat(serviceCallResult.getRetStatus()).isEqualTo(RetStatus.RetSuccess);
assertThat(serviceCallResult.getMethod()).isEqualTo("/path");
assertThat(serviceCallResult.getCallerService().getNamespace()).isEqualTo(NAMESPACE_TEST);
assertThat(serviceCallResult.getCallerService().getService()).isEqualTo(SERVICE_PROVIDER);
assertThat(serviceCallResult.getLabels()).isEqualTo("k1:v1|k2:v2");
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;
}
}

@ -0,0 +1,134 @@
/*
* 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.plugin;
import java.net.URI;
import java.util.Arrays;
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.plugin.reporter.ExceptionPolarisReporter;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
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.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
/**
* Test for {@link EnhancedPluginContext}.
*
* @author Haotian Zhang
*/
@ExtendWith(MockitoExtension.class)
public class EnhancedPluginContextTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private RpcEnhancementReporterProperties reporterProperties;
@Mock
private SDKContext sdkContext;
@Mock
private ConsumerAPI consumerAPI;
@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 testGetAndSet() throws Throwable {
EnhancedRequestContext requestContext = new EnhancedRequestContext();
requestContext.setHttpHeaders(new HttpHeaders());
requestContext.setUrl(new URI("/"));
requestContext.setHttpMethod(HttpMethod.GET);
EnhancedRequestContext requestContext1 = EnhancedRequestContext.builder()
.httpHeaders(requestContext.getHttpHeaders())
.url(requestContext.getUrl())
.httpMethod(requestContext.getHttpMethod())
.build();
assertThat(requestContext1.getUrl()).isEqualTo(requestContext.getUrl());
EnhancedResponseContext responseContext = new EnhancedResponseContext();
responseContext.setHttpStatus(200);
responseContext.setHttpHeaders(new HttpHeaders());
EnhancedResponseContext responseContext1 = EnhancedResponseContext.builder()
.httpStatus(responseContext.getHttpStatus())
.httpHeaders(responseContext.getHttpHeaders())
.build();
assertThat(responseContext1.getHttpStatus()).isEqualTo(responseContext.getHttpStatus());
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
enhancedPluginContext.setRequest(requestContext);
enhancedPluginContext.setResponse(responseContext);
enhancedPluginContext.setServiceInstance(new DefaultServiceInstance());
enhancedPluginContext.setThrowable(mock(Exception.class));
enhancedPluginContext.setDelay(0);
assertThat(enhancedPluginContext.getRequest()).isNotNull();
assertThat(enhancedPluginContext.getResponse()).isNotNull();
assertThat(enhancedPluginContext.getServiceInstance()).isNotNull();
assertThat(enhancedPluginContext.getThrowable()).isNotNull();
assertThat(enhancedPluginContext.getDelay()).isNotNull();
EnhancedPlugin enhancedPlugin = new SuccessPolarisReporter(reporterProperties, sdkContext, consumerAPI);
EnhancedPlugin enhancedPlugin1 = new ExceptionPolarisReporter(reporterProperties, sdkContext, consumerAPI);
EnhancedPluginRunner enhancedPluginRunner = new DefaultEnhancedPluginRunner(Arrays.asList(enhancedPlugin, enhancedPlugin1));
enhancedPluginRunner.run(EnhancedPluginType.POST, enhancedPluginContext);
EnhancedPlugin enhancedPlugin2 = mock(EnhancedPlugin.class);
doThrow(new RuntimeException()).when(enhancedPlugin2).run(any());
doReturn(EnhancedPluginType.POST).when(enhancedPlugin2).getType();
enhancedPluginRunner = new DefaultEnhancedPluginRunner(Arrays.asList(enhancedPlugin2));
enhancedPluginRunner.run(EnhancedPluginType.POST, enhancedPluginContext);
}
}

@ -15,24 +15,19 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
package com.tencent.cloud.rpc.enhancement.plugin;
import java.util.HashMap;
import java.util.function.Consumer;
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.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.ExceptionPolarisReporter;
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.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;
@ -44,11 +39,13 @@ import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
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;
@ -64,29 +61,25 @@ import static org.mockito.Mockito.verify;
public class ExceptionPolarisReporterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ReporterUtils> mockedReporterUtils;
@Mock
private ConsumerAPI consumerAPI;
@Mock
private RpcEnhancementReporterProperties reporterProperties;
@Mock
private SDKContext sdkContext;
@InjectMocks
private ExceptionPolarisReporter exceptionPolarisReporter;
@Mock
private ConsumerAPI consumerAPI;
@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(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
@ -102,62 +95,60 @@ public class ExceptionPolarisReporterTest {
@Test
public void testType() {
assertThat(exceptionPolarisReporter.getType()).isEqualTo(EnhancedFeignPluginType.EXCEPTION);
assertThat(exceptionPolarisReporter.getType()).isEqualTo(EnhancedPluginType.EXCEPTION);
}
@Test
public void testRun() {
// mock request
Request request = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), null, null, null);
// mock response
Response response = mock(Response.class);
EnhancedFeignContext context = mock(EnhancedFeignContext.class);
doReturn(request).when(context).getRequest();
doReturn(response).when(context).getResponse();
EnhancedPluginContext context = mock(EnhancedPluginContext.class);
// test not report
exceptionPolarisReporter.run(context);
verify(context, times(0)).getRequest();
// test do report
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());
}
APIConfig apiConfig = mock(APIConfig.class);
doReturn("0.0.0.0").when(apiConfig).getBindIP();
GlobalConfig globalConfig = mock(GlobalConfig.class);
doReturn(apiConfig).when(globalConfig).getAPI();
Configuration configuration = mock(Configuration.class);
doReturn(globalConfig).when(configuration).getGlobal();
doReturn(configuration).when(sdkContext).getConfig();
EnhancedPluginContext pluginContext = new EnhancedPluginContext();
EnhancedRequestContext request = EnhancedRequestContext.builder()
.httpMethod(HttpMethod.GET)
.url(URI.create("http://0.0.0.0/"))
.httpHeaders(new HttpHeaders())
.build();
EnhancedResponseContext response = EnhancedResponseContext.builder()
.httpStatus(200)
.build();
DefaultServiceInstance serviceInstance = new DefaultServiceInstance();
serviceInstance.setServiceId(SERVICE_PROVIDER);
pluginContext.setRequest(request);
pluginContext.setResponse(response);
pluginContext.setServiceInstance(serviceInstance);
pluginContext.setThrowable(new RuntimeException());
exceptionPolarisReporter.run(pluginContext);
exceptionPolarisReporter.getOrder();
exceptionPolarisReporter.getName();
exceptionPolarisReporter.getType();
}
@Test
public void testHandlerThrowable() {
// mock request
Request request = mock(Request.class);
EnhancedRequestContext request = mock(EnhancedRequestContext.class);
// mock response
Response response = mock(Response.class);
EnhancedResponseContext response = mock(EnhancedResponseContext.class);
EnhancedFeignContext context = new EnhancedFeignContext();
EnhancedPluginContext context = new EnhancedPluginContext();
context.setRequest(request);
context.setResponse(response);
exceptionPolarisReporter.handlerThrowable(context, new RuntimeException("Mock exception."));

@ -15,24 +15,19 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
package com.tencent.cloud.rpc.enhancement.plugin;
import java.util.HashMap;
import java.util.function.Consumer;
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.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter;
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.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;
@ -44,11 +39,12 @@ import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.http.HttpMethod;
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;
@ -62,31 +58,26 @@ import static org.mockito.Mockito.verify;
*/
@ExtendWith(MockitoExtension.class)
public class SuccessPolarisReporterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ReporterUtils> mockedReporterUtils;
@Mock
private ConsumerAPI consumerAPI;
private SDKContext sdkContext;
@Mock
private RpcEnhancementReporterProperties reporterProperties;
@InjectMocks
private SuccessPolarisReporter successPolarisReporter;
@Mock
private ConsumerAPI consumerAPI;
@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(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
@ -102,63 +93,58 @@ public class SuccessPolarisReporterTest {
@Test
public void testType() {
assertThat(successPolarisReporter.getType()).isEqualTo(EnhancedFeignPluginType.POST);
assertThat(successPolarisReporter.getType()).isEqualTo(EnhancedPluginType.POST);
}
@Test
public void testRun() {
// mock request
Request request = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), null, null, null);
// mock response
Response response = mock(Response.class);
doReturn(502).when(response).status();
EnhancedFeignContext context = mock(EnhancedFeignContext.class);
doReturn(request).when(context).getRequest();
doReturn(response).when(context).getResponse();
EnhancedPluginContext context = mock(EnhancedPluginContext.class);
// test not report
successPolarisReporter.run(context);
verify(context, times(0)).getRequest();
// test do report
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());
}
APIConfig apiConfig = mock(APIConfig.class);
doReturn("0.0.0.0").when(apiConfig).getBindIP();
GlobalConfig globalConfig = mock(GlobalConfig.class);
doReturn(apiConfig).when(globalConfig).getAPI();
Configuration configuration = mock(Configuration.class);
doReturn(globalConfig).when(configuration).getGlobal();
doReturn(configuration).when(sdkContext).getConfig();
EnhancedPluginContext pluginContext = new EnhancedPluginContext();
EnhancedRequestContext request = EnhancedRequestContext.builder()
.httpMethod(HttpMethod.GET)
.url(URI.create("http://0.0.0.0/"))
.build();
EnhancedResponseContext response = EnhancedResponseContext.builder()
.httpStatus(200)
.build();
DefaultServiceInstance serviceInstance = new DefaultServiceInstance();
serviceInstance.setServiceId(SERVICE_PROVIDER);
pluginContext.setRequest(request);
pluginContext.setResponse(response);
pluginContext.setServiceInstance(serviceInstance);
successPolarisReporter.run(pluginContext);
successPolarisReporter.getOrder();
successPolarisReporter.getName();
successPolarisReporter.getType();
}
@Test
public void testHandlerThrowable() {
// mock request
Request request = mock(Request.class);
EnhancedRequestContext request = mock(EnhancedRequestContext.class);
// mock response
Response response = mock(Response.class);
EnhancedResponseContext response = mock(EnhancedResponseContext.class);
EnhancedFeignContext context = new EnhancedFeignContext();
EnhancedPluginContext context = new EnhancedPluginContext();
context.setRequest(request);
context.setResponse(response);
successPolarisReporter.handlerThrowable(context, new RuntimeException("Mock exception."));

@ -0,0 +1,92 @@
/*
* 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.resttemplate;
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.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.context.ApplicationContext;
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.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@ExtendWith(MockitoExtension.class)
public class BlockingLoadBalancerClientAspectTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private ProceedingJoinPoint proceedingJoinPoint;
private BlockingLoadBalancerClientAspect aspect = new BlockingLoadBalancerClientAspect();
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
ApplicationContext applicationContext = mock(ApplicationContext.class);
MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class);
StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class);
doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class);
doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class);
mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext).thenReturn(applicationContext);
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
}
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void test() throws Throwable {
ServiceInstance serviceInstance = mock(ServiceInstance.class);
doReturn("0.0.0.0").when(serviceInstance).getHost();
doReturn(80).when(serviceInstance).getPort();
doReturn(new Object[]{ serviceInstance }).when(proceedingJoinPoint).getArgs();
aspect.invoke(proceedingJoinPoint);
aspect.pointcut();
assertThat(MetadataContextHolder.get().getLoadbalancerMetadata().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST)).isEqualTo("0.0.0.0");
assertThat(MetadataContextHolder.get().getLoadbalancerMetadata().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT)).isEqualTo("80");
}
}

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

@ -1,230 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.resttemplate;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import org.checkerframework.checker.nullness.qual.NonNull;
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;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.AbstractClientHttpResponse;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.ResponseErrorHandler;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Test for {@link EnhancedRestTemplateReporter}.
* @author lepdou 2022-09-06
*/
@ExtendWith(MockitoExtension.class)
public class EnhancedRestTemplateReporterTest {
private static MockedStatic<MetadataContextHolder> mockedMetadataContextHolder;
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private ConsumerAPI consumerAPI;
@Mock
private RpcEnhancementReporterProperties reporterProperties;
@Mock
private ResponseErrorHandler delegate;
@InjectMocks
private EnhancedRestTemplateReporter enhancedRestTemplateReporter;
@InjectMocks
private EnhancedRestTemplateReporter enhancedRestTemplateReporter2;
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("caller");
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
// mock transitive metadata
Map<String, String> loadBalancerContext = new HashMap<>();
loadBalancerContext.put("host", "1.1.1.1");
loadBalancerContext.put("port", "8080");
loadBalancerContext.put("startMillis", String.valueOf(System.currentTimeMillis()));
when(metadataContext.getLoadbalancerMetadata()).thenReturn(loadBalancerContext);
mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class);
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
mockedMetadataContextHolder.close();
}
@BeforeEach
void setUp() {
enhancedRestTemplateReporter.setDelegateHandler(delegate);
}
@Test
public void testSetApplicationContext() {
ApplicationContext applicationContext = mock(ApplicationContext.class);
// test no ResponseErrorHandler
when(applicationContext.getBeanNamesForType(any(Class.class)))
.thenReturn(new String[] {"enhancedRestTemplateReporter"});
enhancedRestTemplateReporter2.setApplicationContext(applicationContext);
assertThat(enhancedRestTemplateReporter2.getDelegateHandler()).isInstanceOf(DefaultResponseErrorHandler.class);
// test one other ResponseErrorHandler
when(applicationContext.getBeanNamesForType(any(Class.class)))
.thenReturn(new String[] {"enhancedRestTemplateReporter", "mockedResponseErrorHandler"});
when(applicationContext.getBean(anyString())).thenReturn(mock(MockedResponseErrorHandler.class));
enhancedRestTemplateReporter2.setApplicationContext(applicationContext);
assertThat(enhancedRestTemplateReporter2.getDelegateHandler()).isInstanceOf(MockedResponseErrorHandler.class);
}
@Test
public void testHasError() throws IOException {
when(delegate.hasError(any())).thenReturn(true);
MockedClientHttpResponse response = new MockedClientHttpResponse();
assertThat(enhancedRestTemplateReporter.hasError(response)).isTrue();
String realHasError = response.getHeaders().getFirst(EnhancedRestTemplateReporter.HEADER_HAS_ERROR);
assertThat(realHasError).isEqualTo("true");
}
@Test
public void testHandleHasError() throws IOException {
when(reporterProperties.isEnabled()).thenReturn(true);
when(delegate.hasError(any())).thenReturn(true);
MockedClientHttpResponse response = new MockedClientHttpResponse();
enhancedRestTemplateReporter.hasError(response);
URI uri = mock(URI.class);
enhancedRestTemplateReporter.handleError(uri, HttpMethod.GET, response);
verify(consumerAPI, times(1)).updateServiceCallResult(any());
verify(delegate).handleError(uri, HttpMethod.GET, response);
}
@Test
public void testHandleHasNotError() throws IOException {
when(reporterProperties.isEnabled()).thenReturn(true);
when(delegate.hasError(any())).thenReturn(false);
MockedClientHttpResponse response = new MockedClientHttpResponse();
enhancedRestTemplateReporter.hasError(response);
URI uri = mock(URI.class);
enhancedRestTemplateReporter.handleError(uri, HttpMethod.GET, response);
verify(consumerAPI, times(1)).updateServiceCallResult(any());
verify(delegate, times(0)).handleError(uri, HttpMethod.GET, response);
}
@Test
public void testReportSwitchOff() throws IOException {
when(reporterProperties.isEnabled()).thenReturn(false);
when(delegate.hasError(any())).thenReturn(true);
MockedClientHttpResponse response = new MockedClientHttpResponse();
enhancedRestTemplateReporter.hasError(response);
URI uri = mock(URI.class);
enhancedRestTemplateReporter.handleError(uri, HttpMethod.GET, response);
verify(consumerAPI, times(0)).updateServiceCallResult(any());
verify(delegate).handleError(uri, HttpMethod.GET, response);
}
static class MockedClientHttpResponse extends AbstractClientHttpResponse {
private final HttpHeaders headers;
MockedClientHttpResponse() {
this.headers = new HttpHeaders();
}
@Override
public int getRawStatusCode() {
return 0;
}
@Override
public String getStatusText() {
return null;
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return null;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
}
private static class MockedResponseErrorHandler extends DefaultResponseErrorHandler {
@Override
public void handleError(@NonNull ClientHttpResponse response) {
}
}
}

@ -0,0 +1,136 @@
/*
* 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.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
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.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR;
@ExtendWith(MockitoExtension.class)
public class EnhancedGatewayGlobalFilterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private RpcEnhancementReporterProperties reporterProperties;
@Mock
private SDKContext sdkContext;
@Mock
ServerWebExchange exchange;
@Mock
GatewayFilterChain chain;
@Mock
ServerHttpResponse response;
@Mock
ServerHttpRequest request;
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
ApplicationContext applicationContext = mock(ApplicationContext.class);
MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class);
StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class);
doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class);
doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class);
mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext).thenReturn(applicationContext);
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
}
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void testRun() throws URISyntaxException {
doReturn(new URI("http://0.0.0.0/")).when(request).getURI();
doReturn(new HttpHeaders()).when(request).getHeaders();
doReturn(HttpMethod.GET).when(request).getMethod();
doReturn(new HttpHeaders()).when(response).getHeaders();
doReturn(Mono.empty()).when(chain).filter(exchange);
ServiceInstance serviceInstance = mock(ServiceInstance.class);
Response<ServiceInstance> serviceInstanceResponse = new Response<ServiceInstance>() {
@Override
public boolean hasServer() {
return true;
}
@Override
public ServiceInstance getServer() {
return serviceInstance;
}
};
doReturn(serviceInstanceResponse).when(exchange).getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR);
doReturn(request).when(exchange).getRequest();
doReturn(response).when(exchange).getResponse();
EnhancedGatewayGlobalFilter reporter = new EnhancedGatewayGlobalFilter(new DefaultEnhancedPluginRunner(new ArrayList<>()));
reporter.getOrder();
reporter.filter(exchange, chain).block();
doReturn(Mono.error(new RuntimeException())).when(chain).filter(exchange);
assertThatThrownBy(() -> reporter.filter(exchange, chain).block()).isInstanceOf(RuntimeException.class);
}
}

@ -1,49 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.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());
}
}

@ -1,57 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.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));
}
}

@ -18,42 +18,69 @@
package com.tencent.cloud.rpc.enhancement.webclient;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
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 org.springframework.web.reactive.function.client.ExchangeFunction;
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.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@ExtendWith(MockitoExtension.class)
public class EnhancedWebClientReporterTest {
private static final String URI_TEMPLATE_ATTRIBUTE = EnhancedWebClientReporterTest.class.getName() + ".uriTemplate";
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private RpcEnhancementReporterProperties reporterProperties;
@Mock
private SDKContext sdkContext;
@Mock
private ClientRequest clientRequest;
@Mock
private ExchangeFunction exchangeFunction;
@Mock
private ClientResponse clientResponse;
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
ApplicationContext applicationContext = mock(ApplicationContext.class);
MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class);
StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class);
doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class);
doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class);
mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext).thenReturn(applicationContext);
}
@AfterAll
@ -66,35 +93,28 @@ public class EnhancedWebClientReporterTest {
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();
public void testRun() throws URISyntaxException {
doReturn(new URI("http://0.0.0.0/")).when(clientRequest).url();
doReturn(new HttpHeaders()).when(clientRequest).headers();
doReturn(HttpMethod.GET).when(clientRequest).method();
ClientResponse.Headers headers = mock(ClientResponse.Headers.class);
doReturn(headers).when(clientResponse).headers();
doReturn(Mono.just(clientResponse)).when(exchangeFunction).exchange(any());
EnhancedWebClientReporter reporter = new EnhancedWebClientReporter(new DefaultEnhancedPluginRunner(new ArrayList<>()));
ClientResponse clientResponse1 = reporter.filter(clientRequest, exchangeFunction).block();
assertThat(clientResponse1).isEqualTo(clientResponse);
ClientResponse clientResponse2 = reporter.filter(clientRequest, exchangeFunction).block();
assertThat(clientResponse2).isEqualTo(clientResponse);
doReturn(Mono.error(new RuntimeException())).when(exchangeFunction).exchange(any());
assertThatThrownBy(() -> reporter.filter(clientRequest, exchangeFunction).block()).isInstanceOf(RuntimeException.class);
}
}

@ -0,0 +1,90 @@
/*
* 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 com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.context.ApplicationContext;
import org.springframework.web.reactive.function.client.ClientRequest;
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.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@ExtendWith(MockitoExtension.class)
public class PolarisLoadBalancerClientRequestTransformerTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private PolarisLoadBalancerClientRequestTransformer transformer = new PolarisLoadBalancerClientRequestTransformer();
@Mock
private ClientRequest clientRequest;
@Mock
private ServiceInstance serviceInstance;
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
ApplicationContext applicationContext = mock(ApplicationContext.class);
MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class);
StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class);
doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class);
doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class);
mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext).thenReturn(applicationContext);
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
}
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void test() throws Throwable {
doReturn("test").when(serviceInstance).getServiceId();
transformer.transformRequest(clientRequest, serviceInstance);
assertThat(MetadataContextHolder.get().getLoadbalancerMetadata().get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID)).isEqualTo("test");
}
}
Loading…
Cancel
Save