From eeb592645b93a00c80551454e582f5bd023d490e Mon Sep 17 00:00:00 2001 From: shedfreewu Date: Fri, 4 Jul 2025 00:28:12 +0800 Subject: [PATCH] =?UTF-8?q?1.=20spring=20retry=20=E6=94=AF=E6=8C=81=20rest?= =?UTF-8?q?=20template=202.=20PolarisBlockingLoadBalancerClient=20?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E6=94=BE=E5=88=B0=20rpc=20enhance=203.=20?= =?UTF-8?q?=E5=8E=BB=E9=99=A4=20sm3=204.=20feign=20=E9=A2=84=E7=83=AD?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20name=20=E4=B8=BA=20http://=20=E5=BC=80?= =?UTF-8?q?=E5=A4=B4=E6=97=B6=205.=20shade=20commons-beanutils=E3=80=81jos?= =?UTF-8?q?e4j?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...dBalancerInterceptorBeanPostProcessor.java | 68 ---- ...olarisCircuitBreakerAutoConfiguration.java | 8 +- .../PolarisLoadBalancerInterceptor.java | 69 ---- ...PolarisCircuitBreakerHttpResponseTest.java | 8 +- .../PolarisLoadBalancerInterceptorTest.java | 126 -------- .../feign/FeignEagerLoadSmartLifecycle.java | 19 +- .../pom.xml | 24 -- .../ContextGatewayPropertiesManager.java | 2 +- .../gateway/core/plugin/JwtGatewayPlugin.java | 26 +- .../tsf/gateway/core/util/IdGenerator.java | 2 +- .../tsf/gateway/core/util/SM3Util.java | 85 ----- .../tsf/gateway/core/util/TsfSignUtil.java | 7 +- ...ngLoadBalancerClientBeanPostProcessor.java | 66 ++++ .../RpcEnhancementAutoConfiguration.java | 7 + .../EnhancedRestTemplateWrapInterceptor.java | 43 ++- .../PolarisBlockingLoadBalancerClient.java | 74 +++++ .../loadbalancer/LoadBalancerUtils.java | 58 ++++ ...adBalancerClientBeanPostProcessorTest.java | 52 +-- ...hancedRestTemplateWrapInterceptorTest.java | 302 ------------------ 19 files changed, 274 insertions(+), 772 deletions(-) delete mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java delete mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptor.java delete mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptorTest.java delete mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/SM3Util.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessor.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/PolarisBlockingLoadBalancerClient.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUtils.java rename spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java => spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java (51%) delete mode 100644 spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java deleted file mode 100644 index 72596b238..000000000 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 Tencent. 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.beanprocessor; - -import com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate.PolarisLoadBalancerInterceptor; -import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; -import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; -import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; -import org.springframework.core.Ordered; -import org.springframework.lang.NonNull; - -/** - * LoadBalancerInterceptorBeanPostProcessor is used to wrap the default LoadBalancerInterceptor implementation and returns a custom PolarisLoadBalancerInterceptor. - * - * @author Shedfree Wu - */ -public class LoadBalancerInterceptorBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, Ordered { - /** - * The order of the bean post processor. if user want to wrap it(CustomLoadBalancerInterceptor -> PolarisLoadBalancerInterceptor), CustomLoadBalancerInterceptorBeanPostProcessor's order should be bigger than ${@link POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER}. - */ - public static final int POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER = 0; - - private BeanFactory factory; - - @Override - public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException { - this.factory = beanFactory; - } - - @Override - public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException { - if (bean instanceof LoadBalancerInterceptor) { - // Support rest template router. - // Replaces the default LoadBalancerInterceptor implementation and returns a custom PolarisLoadBalancerInterceptor - LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class); - LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class); - EnhancedPluginRunner pluginRunner = this.factory.getBean(EnhancedPluginRunner.class); - return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory, pluginRunner); - } - return bean; - } - - @Override - public int getOrder() { - return POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER; - } -} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java index 0f01fa1bf..e7d85492d 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.List; import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory; -import com.tencent.cloud.polaris.circuitbreaker.beanprocessor.LoadBalancerInterceptorBeanPostProcessor; import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier; import com.tencent.cloud.polaris.circuitbreaker.reporter.CircuitBreakerPlugin; import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter; @@ -32,7 +31,6 @@ import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; @@ -92,10 +90,6 @@ public class PolarisCircuitBreakerAutoConfiguration { return new CircuitBreakerConfigModifier(properties, polarisCircuitBreakerProperties); } - @Bean - @ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor") - public LoadBalancerInterceptorBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() { - return new LoadBalancerInterceptorBeanPostProcessor(); - } + } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptor.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptor.java deleted file mode 100644 index fbf077f6f..000000000 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptor.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 Tencent. 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.instrument.resttemplate; - -import java.io.IOException; -import java.net.URI; - -import com.tencent.cloud.rpc.enhancement.instrument.resttemplate.EnhancedRestTemplateWrapInterceptor; -import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; - -import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; -import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; -import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.util.Assert; - -/** - * PolarisLoadBalancerInterceptor is a wrapper of LoadBalancerInterceptor. - * - * @author Shedfree Wu - */ -public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { - - private final LoadBalancerClient loadBalancer; - private final LoadBalancerRequestFactory requestFactory; - - private final EnhancedPluginRunner enhancedPluginRunner; - - public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory, - EnhancedPluginRunner enhancedPluginRunner) { - super(loadBalancer, requestFactory); - this.loadBalancer = loadBalancer; - this.requestFactory = requestFactory; - this.enhancedPluginRunner = enhancedPluginRunner; - } - - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { - final URI originalUri = request.getURI(); - String peerServiceName = originalUri.getHost(); - Assert.state(peerServiceName != null, - "Request URI does not contain a valid hostname: " + originalUri); - - if (enhancedPluginRunner != null) { - EnhancedRestTemplateWrapInterceptor enhancedRestTemplateWrapInterceptor = new EnhancedRestTemplateWrapInterceptor(enhancedPluginRunner, loadBalancer); - return enhancedRestTemplateWrapInterceptor.intercept(request, peerServiceName, this.requestFactory.createRequest(request, body, execution)); - } - else { - return super.intercept(request, body, execution); - } - } -} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisCircuitBreakerHttpResponseTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisCircuitBreakerHttpResponseTest.java index 3e8380787..806c7b626 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisCircuitBreakerHttpResponseTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisCircuitBreakerHttpResponseTest.java @@ -39,7 +39,7 @@ public class PolarisCircuitBreakerHttpResponseTest { void testConstructorWithCodeOnly() throws IOException { PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200); - Assertions.assertEquals(200, response.getRawStatusCode()); + Assertions.assertEquals(200, response.getStatusCode().value()); Assertions.assertNotNull(response.getHeaders()); Assertions.assertTrue(response.getHeaders().isEmpty()); Assertions.assertNull(response.getBody()); @@ -50,7 +50,7 @@ public class PolarisCircuitBreakerHttpResponseTest { String body = "test body"; PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200, body); - Assertions.assertEquals(200, response.getRawStatusCode()); + Assertions.assertEquals(200, response.getStatusCode().value()); Assertions.assertNotNull(response.getHeaders()); Assertions.assertTrue(response.getHeaders().isEmpty()); Assertions.assertNotNull(response.getBody()); @@ -65,7 +65,7 @@ public class PolarisCircuitBreakerHttpResponseTest { PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200, headers, body); - Assertions.assertEquals(200, response.getRawStatusCode()); + Assertions.assertEquals(200, response.getStatusCode().value()); Assertions.assertNotNull(response.getHeaders()); Assertions.assertEquals(2, response.getHeaders().size()); Assertions.assertTrue(response.getHeaders().containsKey("Content-Type")); @@ -81,7 +81,7 @@ public class PolarisCircuitBreakerHttpResponseTest { PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(fallbackInfo); - Assertions.assertEquals(200, response.getRawStatusCode()); + Assertions.assertEquals(200, response.getStatusCode().value()); Assertions.assertEquals(fallbackInfo, response.getFallbackInfo()); Assertions.assertNotNull(response.getHeaders()); Assertions.assertTrue(response.getHeaders().containsKey("Content-Type")); diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptorTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptorTest.java deleted file mode 100644 index 50f7b9ae6..000000000 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptorTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 Tencent. 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.instrument.resttemplate; - -import java.io.IOException; -import java.net.URI; -import java.util.Objects; - -import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; -import org.junit.jupiter.api.Assertions; -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.junit.jupiter.MockitoExtension; - -import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; -import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpResponse; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Test for ${@link PolarisLoadBalancerInterceptor}. - * - * @author Shedfree Wu - */ -@ExtendWith(MockitoExtension.class) -class PolarisLoadBalancerInterceptorTest { - - @Mock - private LoadBalancerClient loadBalancer; - - @Mock - private LoadBalancerRequestFactory requestFactory; - - @Mock - private EnhancedPluginRunner enhancedPluginRunner; - - @Mock - private HttpRequest request; - - @Mock - private ClientHttpRequestExecution execution; - - private PolarisLoadBalancerInterceptor interceptor; - private byte[] body; - - @BeforeEach - void setUp() { - body = "test body".getBytes(); - } - - @Test - void testInterceptWithEnhancedPlugin() throws IOException { - // Arrange - ClientHttpResponse mockResponse = mock(ClientHttpResponse.class); - interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, enhancedPluginRunner); - URI uri = URI.create("http://test-service/path"); - when(request.getURI()).thenReturn(uri); - when(loadBalancer.execute(any(), any())).thenReturn(mockResponse); - - // Act - ClientHttpResponse response = interceptor.intercept(request, body, execution); - - // Assert - Assertions.assertTrue(Objects.equals(mockResponse, response) || response instanceof PolarisCircuitBreakerHttpResponse); - } - - @Test - void testInterceptWithoutEnhancedPlugin() throws IOException { - // Arrange - ClientHttpResponse mockResponse = mock(ClientHttpResponse.class); - interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, null); - URI uri = URI.create("http://test-service/path"); - when(request.getURI()).thenReturn(uri); - when(loadBalancer.execute(any(), any())).thenReturn(mockResponse); - - // Act - ClientHttpResponse response = interceptor.intercept(request, body, execution); - - // Assert - Assertions.assertEquals(mockResponse, response); - } - - @Test - void testInterceptWithInvalidUri() { - // Arrange - interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, enhancedPluginRunner); - when(request.getURI()).thenReturn(URI.create("http:///path")); // Invalid URI without host - - // Act & Assert - Exception exception = Assertions.assertThrows(IllegalStateException.class, () -> { - interceptor.intercept(request, body, execution); - }); - Assertions.assertTrue(exception.getMessage().contains("Request URI does not contain a valid hostname")); - } - - @Test - void testConstructor() { - // Act - interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, enhancedPluginRunner); - - // Assert - Assertions.assertNotNull(interceptor); - } -} diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java index da1bbbaca..327edb59b 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java @@ -19,6 +19,7 @@ package com.tencent.cloud.polaris.eager.instrument.feign; import java.lang.reflect.Field; import java.lang.reflect.Proxy; +import java.net.URI; import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient; import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient; @@ -60,18 +61,24 @@ public class FeignEagerLoadSmartLifecycle implements SmartLifecycle { // if feignClient contains url, it doesn't need to eager load. if (StringUtils.isEmpty(feignClient.url())) { // support variables and default values. - String feignName = hardCodedTarget.name(); - LOG.info("[{}] eager-load start", feignName); + String url = hardCodedTarget.name(); + // refer to FeignClientFactoryBean, convert to URL, then take the host as the service name. + if (!url.startsWith("http://") && !url.startsWith("https://")) { + url = "http://" + url; + } + String serviceName = URI.create(url).getHost(); + + LOG.info("[{}] eager-load start", serviceName); if (polarisDiscoveryClient != null) { - polarisDiscoveryClient.getInstances(feignName); + polarisDiscoveryClient.getInstances(serviceName); } else if (polarisReactiveDiscoveryClient != null) { - polarisReactiveDiscoveryClient.getInstances(feignName).subscribe(); + polarisReactiveDiscoveryClient.getInstances(serviceName).subscribe(); } else { - LOG.warn("[{}] no discovery client found.", feignName); + LOG.warn("[{}] no discovery client found.", serviceName); } - LOG.info("[{}] eager-load end", feignName); + LOG.info("[{}] eager-load end", serviceName); } } } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml index ef439ca41..64bbb5991 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml @@ -19,30 +19,6 @@ spring-cloud-starter-tencent-polaris-config - - commons-codec - commons-codec - - - - commons-beanutils - commons-beanutils - 1.11.0 - - - - org.bitbucket.b_c - jose4j - 0.9.4 - - - - com.tencent - tencentsm-crypto - 1.9.1 - true - - com.tencent.cloud spring-cloud-starter-tencent-polaris-discovery diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java index 0d624f8be..38ecd8af6 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java @@ -41,9 +41,9 @@ import com.tencent.tsf.gateway.core.model.PluginDetail; import com.tencent.tsf.gateway.core.model.PluginInfo; import com.tencent.tsf.gateway.core.model.PluginInstanceInfo; import com.tencent.tsf.gateway.core.util.PluginUtil; -import org.apache.commons.beanutils.BeanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import shade.polaris.org.apache.commons.beanutils.BeanUtils; import org.springframework.util.AntPathMatcher; diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/JwtGatewayPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/JwtGatewayPlugin.java index aaba5aadf..9117aa817 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/JwtGatewayPlugin.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/JwtGatewayPlugin.java @@ -31,17 +31,16 @@ import com.tencent.tsf.gateway.core.exception.TsfGatewayException; import com.tencent.tsf.gateway.core.model.ClaimMapping; import com.tencent.tsf.gateway.core.model.JwtPlugin; import com.tencent.tsf.gateway.core.model.PluginPayload; -import org.jose4j.jwa.AlgorithmConstraints; -import org.jose4j.jwa.AlgorithmConstraints.ConstraintType; -import org.jose4j.jwk.PublicJsonWebKey; -import org.jose4j.jwt.JwtClaims; -import org.jose4j.jwt.MalformedClaimException; -import org.jose4j.jwt.consumer.InvalidJwtException; -import org.jose4j.jwt.consumer.JwtConsumer; -import org.jose4j.jwt.consumer.JwtConsumerBuilder; -import org.jose4j.lang.JoseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import shade.polaris.org.jose4j.jwa.AlgorithmConstraints; +import shade.polaris.org.jose4j.jwk.PublicJsonWebKey; +import shade.polaris.org.jose4j.jwt.JwtClaims; +import shade.polaris.org.jose4j.jwt.MalformedClaimException; +import shade.polaris.org.jose4j.jwt.consumer.InvalidJwtException; +import shade.polaris.org.jose4j.jwt.consumer.JwtConsumer; +import shade.polaris.org.jose4j.jwt.consumer.JwtConsumerBuilder; +import shade.polaris.org.jose4j.lang.JoseException; /** @@ -60,6 +59,7 @@ public class JwtGatewayPlugin implements IGatewayPlugin { Map parameterMap = tsfGatewayRequest.getParameterMap(); String tokenBaggagePosition = pluginInfo.getTokenBaggagePosition(); if (Position.fromString(tokenBaggagePosition) == null) { + logger.error("tokenBaggagePosition is wrong, tokenBaggagePosition: {}, tsfGatewayRequest: {}", tokenBaggagePosition, tsfGatewayRequest); throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "tokenBaggagePosition is wrong"); } String idToken; @@ -69,11 +69,13 @@ public class JwtGatewayPlugin implements IGatewayPlugin { else { //queryParam中取 if (parameterMap.get(pluginInfo.getTokenKeyName()) == null || parameterMap.get(pluginInfo.getTokenKeyName()).length == 0) { + logger.error("idToken is empty, tsfGatewayRequest: {}", tsfGatewayRequest); throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "idToken is empty"); } idToken = parameterMap.get(pluginInfo.getTokenKeyName())[0]; } if (StringUtils.isEmpty(idToken)) { + logger.error("idToken is empty, tsfGatewayRequest: {}", tsfGatewayRequest); throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "idToken is empty"); } @@ -95,7 +97,7 @@ public class JwtGatewayPlugin implements IGatewayPlugin { .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew .setRequireSubject() // the JWT must have a subject claim .setVerificationKey(jwk.getPublicKey()) - .setJwsAlgorithmConstraints(new AlgorithmConstraints(ConstraintType.WHITELIST, jwk.getAlgorithm())) + .setJwsAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST, jwk.getAlgorithm())) // ignore audience .setSkipDefaultAudienceValidation() .build(); // create the JwtConsumer instance @@ -114,7 +116,7 @@ public class JwtGatewayPlugin implements IGatewayPlugin { .getValueInMillis(); String msg = String .format("JWT expired at (%d)", expirationTime); - logger.info(msg); + logger.error(msg); throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, msg); } catch (MalformedClaimException e1) { @@ -122,7 +124,7 @@ public class JwtGatewayPlugin implements IGatewayPlugin { } } - logger.warn("Invalid JWT! ", e); + logger.warn("Invalid JWT! tsfGatewayRequest:{}, error:{}", tsfGatewayRequest, e.getMessage()); throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "Invalid JWT"); } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/IdGenerator.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/IdGenerator.java index 789e9e485..95d33d3c2 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/IdGenerator.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/IdGenerator.java @@ -19,7 +19,7 @@ package com.tencent.tsf.gateway.core.util; import java.util.UUID; -import org.apache.commons.codec.binary.Base64; +import shade.polaris.org.apache.commons.codec.binary.Base64; /** * @author kysonli diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/SM3Util.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/SM3Util.java deleted file mode 100644 index 0162eb9ae..000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/SM3Util.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.util; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Security; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import com.tencent.crypto.provider.SMCSProvider; -import org.apache.commons.codec.binary.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 国密加解密工具类. - * @author xfenggeng - * @date 2021-10-11 9:53 - */ -public final class SM3Util { - - private static final String SM3_ALGORITHM = "SM3"; - private static final String SM3_ALGORITHM_HMAC = "SM3HMac"; - private static final String SMCS_PROVIDER = "SMCSProvider"; - private static final Logger logger = LoggerFactory.getLogger(SM3Util.class); - - static { - try { - Security.addProvider(new SMCSProvider()); - } - catch (Throwable t) { - logger.warn("load SMCSProvider error:{}", t.getMessage()); - } - } - - private SM3Util() { - - } - - /** - * 计算SM3摘要. - * @param content 原文 - * @return 摘要 - */ - public static byte[] hmacSm3(String secretKey, String content) { - try { - // Get Mac instance - Mac sm3HMac = Mac.getInstance(SM3_ALGORITHM_HMAC, SMCS_PROVIDER); - // Initialize Mac with a specified key - SecretKeySpec keySpec = new SecretKeySpec(StringUtils.getBytesUtf8(secretKey), SM3_ALGORITHM); - sm3HMac.init(keySpec); - // Generate MAC for the specified message - byte[] mac = sm3HMac.doFinal(StringUtils.getBytesUtf8(content)); - return mac; - } - catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } - catch (NoSuchProviderException e) { - throw new IllegalArgumentException(e); - } - catch (InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - } - -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/TsfSignUtil.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/TsfSignUtil.java index 87fff0acf..bd2a93e88 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/TsfSignUtil.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/TsfSignUtil.java @@ -18,10 +18,10 @@ package com.tencent.tsf.gateway.core.util; import com.tencent.tsf.gateway.core.constant.TsfAlgType; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.digest.HmacUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import shade.polaris.org.apache.commons.codec.binary.Base64; +import shade.polaris.org.apache.commons.codec.digest.HmacUtils; /** @@ -60,9 +60,6 @@ public final class TsfSignUtil { case HMAC_SHA_512: serverSignBytes = HmacUtils.hmacSha512(secretKey, digestValue); break; - case HMAC_SM3: - serverSignBytes = SM3Util.hmacSm3(secretKey, digestValue); - break; default: throw new UnsupportedOperationException("不支持的鉴权算法: " + algType); } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessor.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessor.java new file mode 100644 index 000000000..f48144b22 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessor.java @@ -0,0 +1,66 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.beanprocessor; + +import com.tencent.cloud.rpc.enhancement.instrument.resttemplate.PolarisBlockingLoadBalancerClient; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; +import org.springframework.core.Ordered; +import org.springframework.lang.NonNull; + +/** + * BlockingLoadBalancerClientBeanPostProcessor is used to wrap the default BlockingLoadBalancerClient implementation and returns a custom PolarisBlockingLoadBalancerClient. + * + * @author Shedfree Wu + */ +public class BlockingLoadBalancerClientBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, Ordered { + /** + * The order of the bean post processor. If user wants to wrap it(CustomBlockingLoadBalancerClient -> PolarisBlockingLoadBalancerClient), CustomBlockingLoadBalancerClientBeanPostProcessor's order should be bigger than ${@link ORDER}. + */ + public static final int ORDER = 0; + + private BeanFactory factory; + + @Override + public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException { + this.factory = beanFactory; + } + + @Override + public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException { + if (bean instanceof BlockingLoadBalancerClient) { + BlockingLoadBalancerClient delegate = (BlockingLoadBalancerClient) bean; + ReactiveLoadBalancer.Factory requestFactory = this.factory.getBean(ReactiveLoadBalancer.Factory.class); + EnhancedPluginRunner pluginRunner = this.factory.getBean(EnhancedPluginRunner.class); + return new PolarisBlockingLoadBalancerClient(requestFactory, delegate, pluginRunner); + } + return bean; + } + + @Override + public int getOrder() { + return ORDER; + } +} 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 96b886a91..edda6e380 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 @@ -23,6 +23,7 @@ import java.util.List; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import com.tencent.cloud.polaris.context.PolarisSDKContextManager; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; +import com.tencent.cloud.rpc.enhancement.beanprocessor.BlockingLoadBalancerClientBeanPostProcessor; import com.tencent.cloud.rpc.enhancement.instrument.feign.EnhancedFeignBeanPostProcessor; import com.tencent.cloud.rpc.enhancement.instrument.feign.PolarisLoadBalancerFeignRequestTransformer; import com.tencent.cloud.rpc.enhancement.instrument.filter.EnhancedReactiveFilter; @@ -114,6 +115,12 @@ public class RpcEnhancementAutoConfiguration { return new ExceptionPolarisReporter(properties, polarisSDKContextManager.getConsumerAPI()); } + @Bean + @ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor") + public BlockingLoadBalancerClientBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() { + return new BlockingLoadBalancerClientBeanPostProcessor(); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) protected static class RpcEnhancementServletFilterConfig { diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java index 2659ce340..cb29597f3 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java @@ -58,24 +58,24 @@ public class EnhancedRestTemplateWrapInterceptor { } - public ClientHttpResponse intercept(HttpRequest request, String serviceId, - LoadBalancerRequest loadBalancerRequest) throws IOException { + public T intercept(HttpRequest httpRequest, String serviceId, ServiceInstance serviceInstance, + LoadBalancerRequest loadBalancerRequest) throws IOException { EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext(); - URI serviceUrl = request.getURI(); - if (request instanceof ServiceRequestWrapper) { - serviceUrl = ((ServiceRequestWrapper) request).getRequest().getURI(); + URI serviceUrl = httpRequest.getURI(); + if (httpRequest instanceof ServiceRequestWrapper) { + serviceUrl = ((ServiceRequestWrapper) httpRequest).getRequest().getURI(); } EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder() - .httpHeaders(request.getHeaders()) - .httpMethod(request.getMethod()) - .url(request.getURI()) + .httpHeaders(httpRequest.getHeaders()) + .httpMethod(httpRequest.getMethod()) + .url(httpRequest.getURI()) .serviceUrl(serviceUrl) .build(); enhancedPluginContext.setRequest(enhancedRequestContext); - enhancedPluginContext.setOriginRequest(request); + enhancedPluginContext.setOriginRequest(httpRequest); enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance()); @@ -85,16 +85,27 @@ public class EnhancedRestTemplateWrapInterceptor { pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext); startMillis = System.currentTimeMillis(); - ClientHttpResponse response = delegate.execute(serviceId, loadBalancerRequest); + T response = null; + // retry rest template, serviceInstance is not null + if (serviceInstance != null) { + response = delegate.execute(serviceId, serviceInstance, loadBalancerRequest); + } + else { + response = delegate.execute(serviceId, loadBalancerRequest); + } // get target instance after execute enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get() - .getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), request.getURI()); + .getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), httpRequest.getURI()); enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); - EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() - .httpStatus(response.getRawStatusCode()) - .httpHeaders(response.getHeaders()) - .build(); + + EnhancedResponseContext.EnhancedContextResponseBuilder enhancedResponseContextBuilder = EnhancedResponseContext.builder(); + if (response instanceof ClientHttpResponse) { + enhancedResponseContextBuilder.httpStatus(((ClientHttpResponse) response).getStatusCode().value()); + enhancedResponseContextBuilder.httpHeaders(((ClientHttpResponse) response).getHeaders()); + } + EnhancedResponseContext enhancedResponseContext = enhancedResponseContextBuilder.build(); + enhancedPluginContext.setResponse(enhancedResponseContext); // Run post enhanced plugins. @@ -114,7 +125,7 @@ public class EnhancedRestTemplateWrapInterceptor { if (existFallback) { Object fallbackResponse = fallbackResponseValue.getObjectValue().orElse(null); if (fallbackResponse instanceof ClientHttpResponse) { - return (ClientHttpResponse) fallbackResponse; + return (T) fallbackResponse; } } throw callAbortedException; diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/PolarisBlockingLoadBalancerClient.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/PolarisBlockingLoadBalancerClient.java new file mode 100644 index 000000000..aa9a89774 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/PolarisBlockingLoadBalancerClient.java @@ -0,0 +1,74 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.instrument.resttemplate; + +import java.io.IOException; + +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; +import org.springframework.cloud.client.loadbalancer.LoadBalancerUtils; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; +import org.springframework.http.HttpRequest; + +/** + * PolarisBlockingLoadBalancerClient is a wrapper of BlockingLoadBalancerClient. + * + * @author Shedfree Wu + */ +public class PolarisBlockingLoadBalancerClient extends BlockingLoadBalancerClient { + + private BlockingLoadBalancerClient delegate; + + private final EnhancedPluginRunner enhancedPluginRunner; + + public PolarisBlockingLoadBalancerClient(ReactiveLoadBalancer.Factory loadBalancerClientFactory, + BlockingLoadBalancerClient delegate, EnhancedPluginRunner enhancedPluginRunner) { + super(loadBalancerClientFactory); + this.delegate = delegate; + this.enhancedPluginRunner = enhancedPluginRunner; + } + + /** + * common rest template. + */ + @Override + public T execute(String serviceId, LoadBalancerRequest request) throws IOException { + HttpRequest httpRequest = LoadBalancerUtils.getHttpRequestIfAvailable(request); + if (httpRequest == null || enhancedPluginRunner == null) { + return delegate.execute(serviceId, request); + } + EnhancedRestTemplateWrapInterceptor enhancedRestTemplateWrapInterceptor = new EnhancedRestTemplateWrapInterceptor(enhancedPluginRunner, delegate); + return enhancedRestTemplateWrapInterceptor.intercept(httpRequest, serviceId, null, request); + } + + /** + * retry rest template. + */ + @Override + public T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException { + HttpRequest httpRequest = LoadBalancerUtils.getHttpRequestIfAvailable(LoadBalancerUtils.getDelegateLoadBalancerRequestIfAvailable(request)); + if (httpRequest == null || serviceInstance == null || enhancedPluginRunner == null) { + return delegate.execute(serviceId, serviceInstance, request); + } + EnhancedRestTemplateWrapInterceptor enhancedRestTemplateWrapInterceptor = new EnhancedRestTemplateWrapInterceptor(enhancedPluginRunner, delegate); + return enhancedRestTemplateWrapInterceptor.intercept(httpRequest, serviceId, serviceInstance, request); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUtils.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUtils.java new file mode 100644 index 000000000..df459747f --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUtils.java @@ -0,0 +1,58 @@ +package org.springframework.cloud.client.loadbalancer; + +import java.lang.reflect.Field; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.http.HttpRequest; + +public final class LoadBalancerUtils { + + private static final Logger logger = LoggerFactory.getLogger(LoadBalancerUtils.class); + + private LoadBalancerUtils() { + } + + /** + * if request is a BlockingLoadBalancerRequest, return its HttpRequest. + */ + public static HttpRequest getHttpRequestIfAvailable(LoadBalancerRequest request) { + if (request instanceof BlockingLoadBalancerRequest) { + BlockingLoadBalancerRequest blockingRequest = (BlockingLoadBalancerRequest) request; + return blockingRequest.getHttpRequest(); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("LoadBalancerRequest is not a BlockingLoadBalancerRequest, request:{}", request); + } + return null; + } + } + + /** + * if request is a LoadBalancerRequestAdapter(RetryLoadBalancerInterceptor), return its delegate. + */ + public static LoadBalancerRequest getDelegateLoadBalancerRequestIfAvailable(LoadBalancerRequest request) { + if (!(request instanceof LoadBalancerRequestAdapter)) { + if (logger.isDebugEnabled()) { + logger.debug("LoadBalancerRequest is not a LoadBalancerRequestAdapter, request:{}", request); + } + return request; + } + + try { + Field delegateField = LoadBalancerRequestAdapter.class.getDeclaredField("delegate"); + delegateField.setAccessible(true); + return (LoadBalancerRequest) delegateField.get(request); + } + catch (Exception e) { + // ignore + if (logger.isDebugEnabled()) { + logger.debug("Failed to get delegate from LoadBalancerRequestAdapter, request:{}", request, e); + } + } + return null; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java similarity index 51% rename from spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java rename to spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java index 6e5f55d50..0d50adcfd 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java @@ -15,9 +15,8 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.polaris.circuitbreaker.beanprocessor; +package com.tencent.cloud.rpc.enhancement.beanprocessor; -import com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate.PolarisLoadBalancerInterceptor; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -27,19 +26,16 @@ import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; -import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** - * Test for ${@link LoadBalancerInterceptorBeanPostProcessor}. + * Test for ${@link BlockingLoadBalancerClientBeanPostProcessor}. * * @author Shedfree Wu */ -class LoadBalancerInterceptorBeanPostProcessorTest { +class BlockingLoadBalancerClientBeanPostProcessorTest { @Mock private BeanFactory beanFactory; @@ -53,12 +49,12 @@ class LoadBalancerInterceptorBeanPostProcessorTest { @Mock private EnhancedPluginRunner pluginRunner; - private LoadBalancerInterceptorBeanPostProcessor processor; + private BlockingLoadBalancerClientBeanPostProcessor processor; @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); - processor = new LoadBalancerInterceptorBeanPostProcessor(); + processor = new BlockingLoadBalancerClientBeanPostProcessor(); processor.setBeanFactory(beanFactory); // Setup mock behavior @@ -67,22 +63,6 @@ class LoadBalancerInterceptorBeanPostProcessorTest { when(beanFactory.getBean(EnhancedPluginRunner.class)).thenReturn(pluginRunner); } - @Test - void testPostProcessBeforeInitializationWithLoadBalancerInterceptor() { - // Arrange - LoadBalancerInterceptor originalInterceptor = mock(LoadBalancerInterceptor.class); - String beanName = "testBean"; - - // Act - Object result = processor.postProcessBeforeInitialization(originalInterceptor, beanName); - - // Assert - Assertions.assertInstanceOf(PolarisLoadBalancerInterceptor.class, result); - verify(beanFactory).getBean(LoadBalancerRequestFactory.class); - verify(beanFactory).getBean(LoadBalancerClient.class); - verify(beanFactory).getBean(EnhancedPluginRunner.class); - } - @Test void testPostProcessBeforeInitializationWithNonLoadBalancerInterceptor() { // Arrange @@ -102,26 +82,6 @@ class LoadBalancerInterceptorBeanPostProcessorTest { int order = processor.getOrder(); // Assert - Assertions.assertEquals(LoadBalancerInterceptorBeanPostProcessor.POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER, order); - } - - @Test - void testSetBeanFactory() { - // Arrange - BeanFactory newBeanFactory = mock(BeanFactory.class); - LoadBalancerInterceptorBeanPostProcessor newProcessor = new LoadBalancerInterceptorBeanPostProcessor(); - - // Act - newProcessor.setBeanFactory(newBeanFactory); - - // Assert - // Verify the bean factory is set by trying to process a bean - LoadBalancerInterceptor interceptor = mock(LoadBalancerInterceptor.class); - when(newBeanFactory.getBean(LoadBalancerRequestFactory.class)).thenReturn(requestFactory); - when(newBeanFactory.getBean(LoadBalancerClient.class)).thenReturn(loadBalancerClient); - when(newBeanFactory.getBean(EnhancedPluginRunner.class)).thenReturn(pluginRunner); - - Object result = newProcessor.postProcessBeforeInitialization(interceptor, "testBean"); - Assertions.assertInstanceOf(PolarisLoadBalancerInterceptor.class, result); + Assertions.assertEquals(BlockingLoadBalancerClientBeanPostProcessor.ORDER, order); } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java deleted file mode 100644 index fd7d99e08..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 Tencent. 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.instrument.resttemplate; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; - -import com.tencent.cloud.common.constant.ContextConstant; -import com.tencent.cloud.common.metadata.MetadataContextHolder; -import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; -import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; -import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; -import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; -import com.tencent.polaris.metadata.core.MetadataType; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; -import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; -import org.springframework.cloud.client.loadbalancer.ServiceRequestWrapper; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpRequest; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.eq; -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 EnhancedRestTemplateWrapInterceptor}. - * - * @author Shedfree Wu - */ -@ExtendWith(MockitoExtension.class) -class EnhancedRestTemplateWrapInterceptorTest { - - @Mock - private EnhancedPluginRunner pluginRunner; - - @Mock - private LoadBalancerClient delegate; - - @Mock - private HttpRequest request; - - @Mock - private ClientHttpResponse response; - - @Mock - private ServiceRequestWrapper serviceRequestWrapper; - - @Mock - private ServiceInstance localServiceInstance; - - private EnhancedRestTemplateWrapInterceptor interceptor; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - interceptor = new EnhancedRestTemplateWrapInterceptor(pluginRunner, delegate); - } - - @Test - void testInterceptWithNormalRequest() throws IOException { - // Arrange - URI uri = URI.create("http://test-service/api"); - HttpHeaders headers = new HttpHeaders(); - headers.add("test-header", "test-value"); - - when(request.getURI()).thenReturn(uri); - when(request.getHeaders()).thenReturn(headers); - when(request.getMethod()).thenReturn(HttpMethod.GET); - when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance); - when(delegate.execute(any(), any())).thenReturn(response); - when(response.getRawStatusCode()).thenReturn(200); - when(response.getHeaders()).thenReturn(new HttpHeaders()); - - // Act - interceptor.intercept(request, "test-service", mock(LoadBalancerRequest.class)); - - // Assert - verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class)); - - // Verify context setup - ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class); - verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture()); - - EnhancedPluginContext capturedContext = contextCaptor.getValue(); - Assertions.assertEquals(uri, capturedContext.getRequest().getUrl()); - Assertions.assertEquals(uri, capturedContext.getRequest().getServiceUrl()); - Assertions.assertEquals(headers, capturedContext.getRequest().getHttpHeaders()); - Assertions.assertEquals(HttpMethod.GET, capturedContext.getRequest().getHttpMethod()); - Assertions.assertEquals(localServiceInstance, capturedContext.getLocalServiceInstance()); - } - - @Test - void testInterceptWithServiceRequestWrapper() throws IOException { - // Arrange - URI originalUri = URI.create("http://original-service/api"); - URI wrappedUri = URI.create("http://wrapped-service/api"); - HttpHeaders headers = new HttpHeaders(); - - when(serviceRequestWrapper.getURI()).thenReturn(wrappedUri); - when(serviceRequestWrapper.getRequest()).thenReturn(request); - when(serviceRequestWrapper.getHeaders()).thenReturn(headers); - when(serviceRequestWrapper.getMethod()).thenReturn(HttpMethod.POST); - when(request.getURI()).thenReturn(originalUri); - when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance); - when(delegate.execute(any(), any())).thenReturn(response); - when(response.getRawStatusCode()).thenReturn(200); - when(response.getHeaders()).thenReturn(new HttpHeaders()); - - // Act - interceptor.intercept(serviceRequestWrapper, "test-service", mock(LoadBalancerRequest.class)); - - // Assert - verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class)); - - ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class); - verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture()); - - EnhancedPluginContext capturedContext = contextCaptor.getValue(); - Assertions.assertEquals(wrappedUri, capturedContext.getRequest().getUrl()); - Assertions.assertEquals(originalUri, capturedContext.getRequest().getServiceUrl()); - Assertions.assertEquals(serviceRequestWrapper, capturedContext.getOriginRequest()); - } - - @Test - void testInterceptWithFallback() throws IOException { - // Arrange - URI originalUri = URI.create("http://original-service/api"); - URI wrappedUri = URI.create("http://wrapped-service/api"); - HttpHeaders headers = new HttpHeaders(); - - CallAbortedException abortedException = new CallAbortedException("test-error", null); - - when(serviceRequestWrapper.getURI()).thenReturn(wrappedUri); - when(serviceRequestWrapper.getRequest()).thenReturn(request); - when(serviceRequestWrapper.getHeaders()).thenReturn(headers); - when(serviceRequestWrapper.getMethod()).thenReturn(HttpMethod.POST); - when(request.getURI()).thenReturn(originalUri); - when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance); - doThrow(abortedException) - .when(pluginRunner) - .run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class)); - - Object fallbackResponse = new MockClientHttpResponse(); - MetadataContextHolder.get().getMetadataContainer(MetadataType.APPLICATION, true). - putMetadataObjectValue(ContextConstant.CircuitBreaker.CIRCUIT_BREAKER_FALLBACK_HTTP_RESPONSE, fallbackResponse); - - // Act - interceptor.intercept(serviceRequestWrapper, "test-service", mock(LoadBalancerRequest.class)); - - // Assert - verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class)); - - ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class); - verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture()); - - EnhancedPluginContext capturedContext = contextCaptor.getValue(); - Assertions.assertEquals(wrappedUri, capturedContext.getRequest().getUrl()); - Assertions.assertEquals(originalUri, capturedContext.getRequest().getServiceUrl()); - Assertions.assertEquals(serviceRequestWrapper, capturedContext.getOriginRequest()); - } - - @Test - void testInterceptWithNoFallback() { - // Arrange - URI originalUri = URI.create("http://original-service/api"); - URI wrappedUri = URI.create("http://wrapped-service/api"); - HttpHeaders headers = new HttpHeaders(); - - CallAbortedException abortedException = new CallAbortedException("test-error", null); - - when(serviceRequestWrapper.getURI()).thenReturn(wrappedUri); - when(serviceRequestWrapper.getRequest()).thenReturn(request); - when(serviceRequestWrapper.getHeaders()).thenReturn(headers); - when(serviceRequestWrapper.getMethod()).thenReturn(HttpMethod.POST); - when(request.getURI()).thenReturn(originalUri); - when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance); - doThrow(abortedException) - .when(pluginRunner) - .run(any(), any()); - // Act - Assertions.assertThrows(CallAbortedException.class, () -> { - interceptor.intercept(serviceRequestWrapper, "test-service", mock(LoadBalancerRequest.class)); - }); - } - - @Test - void testInterceptWithNullLocalServiceInstance() throws IOException { - // Arrange - URI uri = URI.create("http://test-service/api"); - when(request.getURI()).thenReturn(uri); - when(request.getHeaders()).thenReturn(new HttpHeaders()); - when(request.getMethod()).thenReturn(HttpMethod.GET); - when(pluginRunner.getLocalServiceInstance()).thenReturn(null); - when(delegate.execute(any(), any())).thenReturn(response); - when(response.getRawStatusCode()).thenReturn(200); - when(response.getHeaders()).thenReturn(new HttpHeaders()); - - // Act - interceptor.intercept(request, "test-service", mock(LoadBalancerRequest.class)); - - // Assert - verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class)); - - ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class); - verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture()); - - EnhancedPluginContext capturedContext = contextCaptor.getValue(); - assertThat(capturedContext.getLocalServiceInstance()).isNull(); - } - - @Test - void testExceptionHandling() throws IOException { - // Arrange - LoadBalancerRequest loadBalancerRequest = mock(LoadBalancerRequest.class); - IOException expectedException = new IOException("Test exception"); - when(delegate.execute(anyString(), any(LoadBalancerRequest.class))) - .thenThrow(expectedException); - - // Act & Assert - Exception actualException = Assertions.assertThrows(IOException.class, () -> { - interceptor.intercept(request, "test-service", loadBalancerRequest); - }); - - // Verify exception handling - verify(pluginRunner, times(1)) - .run(eq(EnhancedPluginType.Client.EXCEPTION), any(EnhancedPluginContext.class)); - - // Verify finally block is executed - verify(pluginRunner, times(1)) - .run(eq(EnhancedPluginType.Client.FINALLY), any(EnhancedPluginContext.class)); - - // Verify the thrown exception is the same - Assertions.assertEquals(expectedException, actualException); - } - - static class MockClientHttpResponse implements ClientHttpResponse { - @Override - public HttpStatus getStatusCode() throws IOException { - return null; - } - - @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 null; - } - } -}