1. spring retry 支持 rest template

2. PolarisBlockingLoadBalancerClient 扩展放到 rpc enhance
3. 去除 sm3
4. feign 预热支持 name 为 http:// 开头时
5. shade commons-beanutils、jose4j
pull/1635/head
shedfreewu 2 months ago
parent 239ccfc7ba
commit eeb592645b

@ -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;
}
}

@ -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();
}
}

@ -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);
}
}
}

@ -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"));

@ -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);
}
}

@ -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);
}
}
}

@ -19,30 +19,6 @@
<artifactId>spring-cloud-starter-tencent-polaris-config</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.9.4</version>
</dependency>
<dependency>
<groupId>com.tencent</groupId>
<artifactId>tencentsm-crypto</artifactId>
<version>1.9.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>

@ -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;

@ -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<JwtPlugin> {
Map<String, String[]> 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<JwtPlugin> {
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<JwtPlugin> {
.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<JwtPlugin> {
.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<JwtPlugin> {
}
}
logger.warn("Invalid JWT! ", e);
logger.warn("Invalid JWT! tsfGatewayRequest:{}, error:{}", tsfGatewayRequest, e.getMessage());
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "Invalid JWT");
}

@ -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

@ -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);
}
}
}

@ -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);
}

@ -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<ServiceInstance> 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;
}
}

@ -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 {

@ -58,24 +58,24 @@ public class EnhancedRestTemplateWrapInterceptor {
}
public ClientHttpResponse intercept(HttpRequest request, String serviceId,
LoadBalancerRequest<ClientHttpResponse> loadBalancerRequest) throws IOException {
public <T> T intercept(HttpRequest httpRequest, String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> 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;

@ -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<ServiceInstance> loadBalancerClientFactory,
BlockingLoadBalancerClient delegate, EnhancedPluginRunner enhancedPluginRunner) {
super(loadBalancerClientFactory);
this.delegate = delegate;
this.enhancedPluginRunner = enhancedPluginRunner;
}
/**
* common rest template.
*/
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> 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> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> 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);
}
}

@ -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;
}
}

@ -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);
}
}

@ -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<EnhancedPluginContext> 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<EnhancedPluginContext> 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<EnhancedPluginContext> 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<EnhancedPluginContext> 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<ClientHttpResponse> 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;
}
}
}
Loading…
Cancel
Save