From d7c53401091007fb237a9b7d957a2b746f3b0785 Mon Sep 17 00:00:00 2001 From: lepdou Date: Tue, 6 Sep 2022 20:04:23 +0800 Subject: [PATCH] optimize report call result for restTemplate (#562) --- CHANGELOG.md | 1 + .../common/metadata/MetadataContext.java | 5 + spring-cloud-tencent-rpc-enhancement/pom.xml | 7 +- .../AbstractPolarisReporterAdapter.java | 14 +- .../RpcEnhancementAutoConfiguration.java | 22 ++- .../reporter/SuccessPolarisReporter.java | 2 +- .../BlockingLoadBalancerClientAspect.java | 42 ++++ .../EnhancedRestTemplateReporter.java | 105 ++++++++-- .../LoadBalancerClientAspectUtils.java | 45 +++++ .../RibbonLoadBalancerClientAspect.java | 42 ++++ .../EnhancedRestTemplateReporterTest.java | 183 +++++++++++++++--- .../SimpleClientHttpResponseTest.java | 105 ---------- 12 files changed, 412 insertions(+), 161 deletions(-) create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/BlockingLoadBalancerClientAspect.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/LoadBalancerClientAspectUtils.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/RibbonLoadBalancerClientAspect.java delete mode 100644 spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/SimpleClientHttpResponseTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a405cebe..bf1d7c654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,3 +25,4 @@ - [feature:remove ConditionalOnConfigReflectEnabled annotation.](https://github.com/Tencent/spring-cloud-tencent/pull/551) - [Optimize:shutdown server when connect to config server failed.](https://github.com/Tencent/spring-cloud-tencent/pull/552) - [fix:fix heartbeat interval different configuration from polaris-java SDK.](https://github.com/Tencent/spring-cloud-tencent/pull/560) +- [Optimize: optimize report call result for restTemplate.](https://github.com/Tencent/spring-cloud-tencent/pull/562) diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContext.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContext.java index 0ef6e551c..aab792f3c 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContext.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContext.java @@ -46,6 +46,11 @@ public class MetadataContext { */ public static final String FRAGMENT_DISPOSABLE = "disposable"; + /** + * load balancer context. + */ + public static final String FRAGMENT_LOAD_BALANCER = "loadbalancer"; + /** * upstream disposable Context. */ diff --git a/spring-cloud-tencent-rpc-enhancement/pom.xml b/spring-cloud-tencent-rpc-enhancement/pom.xml index e311cbff0..72d5113fa 100644 --- a/spring-cloud-tencent-rpc-enhancement/pom.xml +++ b/spring-cloud-tencent-rpc-enhancement/pom.xml @@ -28,6 +28,11 @@ + + org.springframework.boot + spring-boot-starter-aop + + org.springframework.cloud spring-cloud-loadbalancer @@ -71,4 +76,4 @@ - \ No newline at end of file + diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java index 6ba28617d..599b2cc76 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java @@ -53,15 +53,15 @@ public abstract class AbstractPolarisReporterAdapter { private static final List 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 properties; + protected final RpcEnhancementReporterProperties reportProperties; /** * Constructor With {@link RpcEnhancementReporterProperties} . * - * @param properties instance of {@link RpcEnhancementReporterProperties}. + * @param reportProperties instance of {@link RpcEnhancementReporterProperties}. */ - protected AbstractPolarisReporterAdapter(RpcEnhancementReporterProperties properties) { - this.properties = properties; + protected AbstractPolarisReporterAdapter(RpcEnhancementReporterProperties reportProperties) { + this.reportProperties = reportProperties; } /** @@ -88,12 +88,12 @@ public abstract class AbstractPolarisReporterAdapter { } else { // statuses > series - List status = properties.getStatuses(); + List status = reportProperties.getStatuses(); if (status.isEmpty()) { - List series = properties.getSeries(); + List series = reportProperties.getSeries(); // Check INTERNAL_SERVER_ERROR (500) status. - if (properties.isIgnoreInternalServerError() && Objects.equals(httpStatus, INTERNAL_SERVER_ERROR)) { + if (reportProperties.isIgnoreInternalServerError() && Objects.equals(httpStatus, INTERNAL_SERVER_ERROR)) { return false; } if (series.isEmpty()) { diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java index 64ecea941..8e22239cb 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java @@ -27,7 +27,9 @@ 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.resttemplate.BlockingLoadBalancerClientAspect; import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter; +import com.tencent.cloud.rpc.enhancement.resttemplate.RibbonLoadBalancerClientAspect; import com.tencent.polaris.api.core.ConsumerAPI; import org.springframework.beans.factory.SmartInitializingSingleton; @@ -35,6 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +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; @@ -106,9 +109,8 @@ public class RpcEnhancementAutoConfiguration { private List restTemplates = Collections.emptyList(); @Bean - public EnhancedRestTemplateReporter polarisRestTemplateResponseErrorHandler( - RpcEnhancementReporterProperties properties, - ConsumerAPI consumerAPI) { + public EnhancedRestTemplateReporter enhancedRestTemplateReporter( + RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { return new EnhancedRestTemplateReporter(properties, consumerAPI); } @@ -120,5 +122,19 @@ public class RpcEnhancementAutoConfiguration { } }; } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(name = {"org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient"}) + public RibbonLoadBalancerClientAspect ribbonLoadBalancerClientAspect() { + return new RibbonLoadBalancerClientAspect(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(name = {"org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient"}) + public BlockingLoadBalancerClientAspect blockingLoadBalancerClientAspect() { + return new BlockingLoadBalancerClientAspect(); + } } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java index 11614b6fc..eadaeaaf7 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java @@ -61,7 +61,7 @@ public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter imple @Override public void run(EnhancedFeignContext context) { - if (!properties.isEnabled()) { + if (!reportProperties.isEnabled()) { return; } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/BlockingLoadBalancerClientAspect.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/BlockingLoadBalancerClientAspect.java new file mode 100644 index 000000000..bc2cdd05e --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/BlockingLoadBalancerClientAspect.java @@ -0,0 +1,42 @@ +/* + * 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.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +/** + * Intercept for BlockingLoadBalancerClient, put host&port to thread local. + * @author lepdou 2022-09-05 + */ +@Aspect +public class BlockingLoadBalancerClientAspect { + + @Pointcut("execution(public * org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient.reconstructURI(..)) ") + public void pointcut() { + + } + + @Around("pointcut()") + public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable { + LoadBalancerClientAspectUtils.extractLoadBalancerResult(joinPoint); + return joinPoint.proceed(); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java index a617886d5..2a56c39bb 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java @@ -17,12 +17,12 @@ package com.tencent.cloud.rpc.enhancement.resttemplate; -import java.net.HttpURLConnection; +import java.io.IOException; import java.net.URI; -import java.net.URL; +import java.util.Map; import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.util.ReflectionUtils; +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; @@ -33,6 +33,9 @@ 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.client.ClientHttpResponse; import org.springframework.lang.NonNull; @@ -43,13 +46,14 @@ import org.springframework.web.client.ResponseErrorHandler; * * @author wh 2022/6/21 */ -public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter implements ResponseErrorHandler { +public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter implements ResponseErrorHandler, ApplicationContextAware { private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class); - private static final String FIELD_NAME = "connection"; + static final String HEADER_HAS_ERROR = "X-SCT-Has-Error"; private final ConsumerAPI consumerAPI; + private ResponseErrorHandler delegateHandler; public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { super(properties); @@ -57,30 +61,69 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter } @Override - public boolean hasError(@NonNull ClientHttpResponse response) { + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + String[] handlerBeanNames = applicationContext.getBeanNamesForType(ResponseErrorHandler.class); + if (handlerBeanNames.length == 1) { + 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) { + 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) { - if (!properties.isEnabled()) { - return; + 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) { ServiceCallResult resultRequest = createServiceCallResult(url); try { + Map loadBalancerContext = MetadataContextHolder.get() + .getFragmentContext(MetadataContext.FRAGMENT_LOAD_BALANCER); - HttpURLConnection connection = (HttpURLConnection) ReflectionUtils.getFieldValue(response, FIELD_NAME); - if (connection != null) { - URL realURL = connection.getURL(); - resultRequest.setHost(realURL.getHost()); - resultRequest.setPort(realURL.getPort()); + String targetHost = loadBalancerContext.get("host"); + String targetPort = loadBalancerContext.get("port"); + + 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)); + // checking response http status code if (apply(response.getStatusCode())) { resultRequest.setRetStatus(RetStatus.RetFail); @@ -96,6 +139,34 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter } } + 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) { + if (!response.getHeaders().containsKey(HEADER_HAS_ERROR)) { + return; + } + response.getHeaders().remove(HEADER_HAS_ERROR); + } + private ServiceCallResult createServiceCallResult(URI uri) { ServiceCallResult resultRequest = new ServiceCallResult(); String serviceName = uri.getHost(); @@ -110,4 +181,8 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter } return resultRequest; } + + public void setDelegateHandler(ResponseErrorHandler delegateHandler) { + this.delegateHandler = delegateHandler; + } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/LoadBalancerClientAspectUtils.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/LoadBalancerClientAspectUtils.java new file mode 100644 index 000000000..0eb2dfd7f --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/LoadBalancerClientAspectUtils.java @@ -0,0 +1,45 @@ +/* + * 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.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import org.aspectj.lang.ProceedingJoinPoint; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; + +/** + * Extract load balancer result from {@link LoadBalancerClient} and put to MetadataContext. + * @author lepdou 2022-09-06 + */ +public final class LoadBalancerClientAspectUtils { + + private LoadBalancerClientAspectUtils() { + } + + public static void extractLoadBalancerResult(ProceedingJoinPoint joinPoint) { + Object server = joinPoint.getArgs()[0]; + if (server instanceof ServiceInstance) { + ServiceInstance instance = (ServiceInstance) server; + MetadataContextHolder.get().putContext(MetadataContext.FRAGMENT_LOAD_BALANCER, "host", instance.getHost()); + MetadataContextHolder.get() + .putContext(MetadataContext.FRAGMENT_LOAD_BALANCER, "port", String.valueOf(instance.getPort())); + } + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/RibbonLoadBalancerClientAspect.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/RibbonLoadBalancerClientAspect.java new file mode 100644 index 000000000..12b469735 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/RibbonLoadBalancerClientAspect.java @@ -0,0 +1,42 @@ +/* + * 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.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +/** + * Intercept for RibbonLoadBalancerClient, put host&port to thread local. + * @author lepdou 2022-09-05 + */ +@Aspect +public class RibbonLoadBalancerClientAspect { + + @Pointcut("execution(public * org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.reconstructURI(..))") + public void pointcut() { + + } + + @Around("pointcut()") + public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable { + LoadBalancerClientAspectUtils.extractLoadBalancerResult(joinPoint); + return joinPoint.proceed(); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java index 848e22980..173cb727d 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java @@ -17,55 +17,180 @@ package com.tencent.cloud.rpc.enhancement.resttemplate; - -import java.net.HttpURLConnection; +import java.io.IOException; +import java.io.InputStream; import java.net.URI; -import java.net.URL; +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.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.AbstractClientHttpResponse; +import org.springframework.web.client.ResponseErrorHandler; +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 wh 2022/6/22 + * Test for {@link EnhancedRestTemplateReporter} + * @author lepdou 2022-09-06 */ -@RunWith(SpringRunner.class) -@SpringBootTest(classes = EnhancedRestTemplateReporterTest.TestApplication.class, - properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"}) +@RunWith(MockitoJUnitRunner.class) public class EnhancedRestTemplateReporterTest { + private static MockedStatic mockedMetadataContextHolder; + private static MockedStatic mockedApplicationContextAwareUtils; + @Mock + private ConsumerAPI consumerAPI; + @Mock + private RpcEnhancementReporterProperties reporterProperties; + @Mock + private ResponseErrorHandler delegate; + @InjectMocks + private EnhancedRestTemplateReporter enhancedRestTemplateReporter; + + @BeforeClass + public static void beforeClass() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("caller"); + MetadataContext metadataContext = Mockito.mock(MetadataContext.class); + + // mock transitive metadata + Map loadBalancerContext = new HashMap<>(); + loadBalancerContext.put("host", "1.1.1.1"); + loadBalancerContext.put("port", "8080"); + when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_LOAD_BALANCER)).thenReturn(loadBalancerContext); + + mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class); + mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext); + } + + @AfterClass + public static void afterClass() { + mockedApplicationContextAwareUtils.close(); + mockedMetadataContextHolder.close(); + } + + @Before + public void before() { + enhancedRestTemplateReporter.setDelegateHandler(delegate); + } + @Test - public void handleError() throws Exception { - ConsumerAPI consumerAPI = mock(ConsumerAPI.class); - EnhancedRestTemplateReporter enhancedRestTemplateReporter = - new EnhancedRestTemplateReporter(mock(RpcEnhancementReporterProperties.class), consumerAPI); + public void testHasError() throws IOException { + when(delegate.hasError(any())).thenReturn(true); + + MockedClientHttpResponse response = new MockedClientHttpResponse(); + Assert.assertTrue(enhancedRestTemplateReporter.hasError(response)); + + String realHasError = response.getHeaders().getFirst(EnhancedRestTemplateReporter.HEADER_HAS_ERROR); + Assert.assertEquals("true", realHasError); + } + + @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); - when(uri.getPath()).thenReturn("/test"); - when(uri.getHost()).thenReturn("host"); - HttpURLConnection httpURLConnection = mock(HttpURLConnection.class); - URL url = mock(URL.class); - when(httpURLConnection.getURL()).thenReturn(url); - when(url.getHost()).thenReturn("127.0.0.1"); - when(url.getPort()).thenReturn(8080); - when(httpURLConnection.getResponseCode()).thenReturn(200); - SimpleClientHttpResponseTest clientHttpResponse = new SimpleClientHttpResponseTest(httpURLConnection); - enhancedRestTemplateReporter.handleError(uri, HttpMethod.GET, clientHttpResponse); - when(consumerAPI.unWatchService(null)).thenReturn(true); + enhancedRestTemplateReporter.handleError(uri, HttpMethod.GET, response); + + verify(consumerAPI).updateServiceCallResult(any()); + verify(delegate).handleError(uri, HttpMethod.GET, response); } - @SpringBootApplication - protected static class TestApplication { + @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).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); + } + + class MockedClientHttpResponse extends AbstractClientHttpResponse { + + private HttpHeaders headers; + + MockedClientHttpResponse() { + this.headers = new HttpHeaders(); + } + + @Override + public int getRawStatusCode() throws IOException { + return 0; + } + + @Override + public String getStatusText() throws IOException { + 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; + } } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/SimpleClientHttpResponseTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/SimpleClientHttpResponseTest.java deleted file mode 100644 index 0899da7a1..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/SimpleClientHttpResponseTest.java +++ /dev/null @@ -1,105 +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.HttpURLConnection; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.client.AbstractClientHttpResponse; -import org.springframework.lang.Nullable; -import org.springframework.util.StreamUtils; -import org.springframework.util.StringUtils; - - -/** - * Mock Test for {@link AbstractClientHttpResponse}. - * - * @author wh 2022/6/22 - */ -public class SimpleClientHttpResponseTest extends AbstractClientHttpResponse { - - private final HttpURLConnection connection; - - @Nullable - private HttpHeaders headers; - - @Nullable - private InputStream responseStream; - - - SimpleClientHttpResponseTest(HttpURLConnection connection) { - this.connection = connection; - } - - - @Override - public int getRawStatusCode() throws IOException { - return this.connection.getResponseCode(); - } - - @Override - public String getStatusText() throws IOException { - String result = this.connection.getResponseMessage(); - return (result != null) ? result : ""; - } - - @Override - public HttpHeaders getHeaders() { - if (this.headers == null) { - this.headers = new HttpHeaders(); - // Header field 0 is the status line for most HttpURLConnections, but not on GAE - String name = this.connection.getHeaderFieldKey(0); - if (StringUtils.hasLength(name)) { - this.headers.add(name, this.connection.getHeaderField(0)); - } - int i = 1; - while (true) { - name = this.connection.getHeaderFieldKey(i); - if (!StringUtils.hasLength(name)) { - break; - } - this.headers.add(name, this.connection.getHeaderField(i)); - i++; - } - } - return this.headers; - } - - @Override - public InputStream getBody() throws IOException { - InputStream errorStream = this.connection.getErrorStream(); - this.responseStream = (errorStream != null ? errorStream : this.connection.getInputStream()); - return this.responseStream; - } - - @Override - public void close() { - try { - if (this.responseStream == null) { - getBody(); - } - StreamUtils.drain(this.responseStream); - this.responseStream.close(); - } - catch (Exception ex) { - // ignore - } - } -}