feat:support smooth upgrade from tsf.

pull/1542/head
Haotian Zhang 4 months ago
parent 65ae5ec2bc
commit efb6f4a057

@ -15,7 +15,6 @@
<dependencies>
<!-- Spring Cloud Tencent dependencies start -->
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>

@ -0,0 +1,95 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.common;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for ${@link PolarisResultToErrorCode}.
*
* @author Shedfree Wu
*/
@ExtendWith(MockitoExtension.class)
class PolarisResultToErrorCodeTest {
private final PolarisResultToErrorCode converter = new PolarisResultToErrorCode();
@Test
void testOnSuccess() {
assertThat(converter.onSuccess("any value")).isEqualTo(200);
}
@Test
void testOnErrorWithWebClientResponseException() {
// Given
WebClientResponseException exception = WebClientResponseException.create(
404, "Not Found", null, null, null);
// When
int errorCode = converter.onError(exception);
// Then
assertThat(errorCode).isEqualTo(404);
}
@Test
void testOnErrorWithCircuitBreakerStatusCodeException() {
// When
int errorCode = converter.onError(new RuntimeException("test"));
// Then
assertThat(errorCode).isEqualTo(-1);
}
@Test
void testOnErrorWithUnknownException() {
// Given
RuntimeException exception = new RuntimeException("Unknown error");
// When
int errorCode = converter.onError(exception);
// Then
assertThat(errorCode).isEqualTo(-1);
}
@Test
void testCheckClassExist() throws Exception {
// Given
Method checkClassExist = PolarisResultToErrorCode.class.getDeclaredMethod("checkClassExist", String.class);
checkClassExist.setAccessible(true);
PolarisResultToErrorCode converter = new PolarisResultToErrorCode();
// test exist class
boolean result1 = (boolean) checkClassExist.invoke(converter, "java.lang.String");
assertThat(result1).isTrue();
// test not exist class
boolean result2 = (boolean) checkClassExist.invoke(converter, "com.nonexistent.Class");
assertThat(result2).isFalse();
}
}

@ -0,0 +1,216 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.polaris.circuitbreaker.exception.FallbackWrapperException;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import feign.InvocationHandlerFactory;
import feign.Target;
import feign.codec.Decoder;
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.openfeign.FallbackFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for ${@link PolarisFeignCircuitBreakerInvocationHandler}.
*
* @author Shedfree Wu
*/
@ExtendWith(MockitoExtension.class)
class PolarisFeignCircuitBreakerInvocationHandlerTest {
@Mock
private Target<?> target;
@Mock
private InvocationHandlerFactory.MethodHandler methodHandler;
@Mock
private FallbackFactory<TestInterface> fallbackFactory;
@Mock
private Decoder decoder;
private Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private PolarisFeignCircuitBreakerInvocationHandler handler;
private Method testMethod;
@BeforeEach
void setUp() throws Exception {
dispatch = new HashMap<>();
testMethod = TestInterface.class.getDeclaredMethod("testMethod");
dispatch.put(testMethod, methodHandler);
handler = new PolarisFeignCircuitBreakerInvocationHandler(
target,
dispatch,
fallbackFactory,
decoder
);
}
@Test
void testConstructorWithNullTarget() {
assertThatThrownBy(() ->
new PolarisFeignCircuitBreakerInvocationHandler(
null, dispatch, fallbackFactory, decoder
)
).isInstanceOf(NullPointerException.class);
}
@Test
void testConstructorWithNullDispatch() {
assertThatThrownBy(() ->
new PolarisFeignCircuitBreakerInvocationHandler(
target, null, fallbackFactory, decoder
)).isInstanceOf(NullPointerException.class);
}
@Test
void testToFallbackMethod() throws Exception {
Method method = TestInterface.class.getMethod("testMethod");
Map<Method, InvocationHandlerFactory.MethodHandler> testDispatch = new HashMap<>();
testDispatch.put(method, methodHandler);
Map<Method, Method> result = PolarisFeignCircuitBreakerInvocationHandler.toFallbackMethod(testDispatch);
assertThat(result).isNotNull();
assertThat(result.containsKey(method)).isTrue();
assertThat(result.get(method)).isEqualTo(method);
}
@Test
void testEqualsMethod() throws Throwable {
Method equalsMethod = Object.class.getMethod("equals", Object.class);
Object mockProxy = mock(Object.class);
// Test equals with null
assertThat((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {null})).isFalse();
// Test equals with non-proxy object
assertThat((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {new Object()})).isFalse();
}
@Test
void testToStringMethod() throws Throwable {
Method toStringMethod = Object.class.getMethod("toString");
Object mockProxy = mock(Object.class);
when(target.toString()).thenReturn("TestTarget");
assertThat(handler.invoke(mockProxy, toStringMethod, null)).isEqualTo("TestTarget");
}
@Test
void testCustomFallbackFactoryWithFallbackError() throws Throwable {
// Arrange
handler = new PolarisFeignCircuitBreakerInvocationHandler(target, dispatch, fallbackFactory, decoder);
Exception originalException = new RuntimeException("Original error");
when(methodHandler.invoke(any())).thenThrow(originalException);
TestImpl testImpl = new TestImpl();
when(fallbackFactory.create(any())).thenReturn(testImpl);
// Act
assertThatThrownBy(() -> handler.invoke(null, testMethod, new Object[] {})).isInstanceOf(FallbackWrapperException.class);
}
@Test
void testCustomFallbackFactoryWithFallbackError2() throws Throwable {
// Arrange
handler = new PolarisFeignCircuitBreakerInvocationHandler(target, dispatch, fallbackFactory, decoder);
Exception originalException = new RuntimeException("Original error");
when(methodHandler.invoke(any())).thenThrow(originalException);
TestImpl2 testImpl = new TestImpl2();
when(fallbackFactory.create(any())).thenReturn(testImpl);
// Act
assertThatThrownBy(() -> handler.invoke(null, testMethod, new Object[] {})).
isInstanceOf(RuntimeException.class).hasMessage("test");
}
@Test
void testDefaultFallbackCreation() throws Throwable {
// Arrange
handler = new PolarisFeignCircuitBreakerInvocationHandler(target, dispatch, null, decoder);
CircuitBreakerStatus.FallbackInfo fallbackInfo = new CircuitBreakerStatus.FallbackInfo(200, new HashMap<>(), "mock body");
CallAbortedException originalException = new CallAbortedException("test rule", fallbackInfo);
Object expected = new Object();
when(methodHandler.invoke(any())).thenThrow(originalException);
when(decoder.decode(any(), any())).thenReturn(expected);
// Act
Object result = handler.invoke(null, testMethod, new Object[] {});
// Verify
assertThat(result).isEqualTo(expected);
}
@Test
void testEquals() {
PolarisFeignCircuitBreakerInvocationHandler testHandler = new PolarisFeignCircuitBreakerInvocationHandler(
target,
dispatch,
fallbackFactory,
decoder
);
assertThat(testHandler).isEqualTo(handler);
assertThat(testHandler.hashCode()).isEqualTo(handler.hashCode());
}
interface TestInterface {
String testMethod() throws InvocationTargetException;
}
static class TestImpl implements TestInterface {
@Override
public String testMethod() throws InvocationTargetException {
throw new InvocationTargetException(new RuntimeException("test"));
}
}
static class TestImpl2 implements TestInterface {
@Override
public String testMethod() throws InvocationTargetException {
throw new RuntimeException("test");
}
}
}

@ -0,0 +1,133 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import feign.Feign;
import feign.RequestLine;
import feign.Target;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.openfeign.FallbackFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Tests for {@link PolarisFeignCircuitBreaker}.
*/
public class PolarisFeignCircuitBreakerTest {
private PolarisFeignCircuitBreaker.Builder builder;
@BeforeEach
public void setUp() {
builder = PolarisFeignCircuitBreaker.builder();
}
@Test
public void testBuilderNotNull() {
assertThat(builder).isNotNull();
}
@Test
public void testTargetWithFallback() {
// Mock the target
Class<MyService> targetType = MyService.class;
String name = "myService";
Target<MyService> target = mock(Target.class);
// mock return value
when(target.type()).thenReturn(targetType);
when(target.name()).thenReturn(name);
// Mock the fallback
MyService fallback = mock(MyService.class);
when(fallback.sayHello()).thenReturn("Fallback Hello");
// Call the target method
MyService result = builder.target(target, fallback);
// Verify that the result is not null and the fallback factory is used
assertThat(result).isNotNull();
assertThat(result.sayHello()).isEqualTo("Fallback Hello");
}
@Test
public void testTargetWithFallbackFactory() {
// Mock the target and fallback factory
Class<MyService> targetType = MyService.class;
String name = "myService";
Target<MyService> target = mock(Target.class);
// mock return value
when(target.type()).thenReturn(targetType);
when(target.name()).thenReturn(name);
FallbackFactory<MyService> fallbackFactory = mock(FallbackFactory.class);
// Mock the fallback from the factory
MyService fallback = mock(MyService.class);
when(fallback.sayHello()).thenReturn("Fallback Hello");
when(fallbackFactory.create(any())).thenReturn(fallback);
// Call the target method
MyService result = builder.target(target, fallbackFactory);
// Verify that the result is not null and the fallback factory is used
assertThat(result).isNotNull();
assertThat(result.sayHello()).isEqualTo("Fallback Hello");
}
@Test
public void testTargetWithoutFallback() {
// Mock the target
Class<MyService> targetType = MyService.class;
String name = "myService";
Target<MyService> target = mock(Target.class);
// mock return value
when(target.type()).thenReturn(targetType);
when(target.name()).thenReturn(name);
// Call the target method
MyService result = builder.target(target);
// Verify that the result is not null
assertThat(result).isNotNull();
// Additional verifications can be added here based on the implementation
}
@Test
public void testBuildWithNullableFallbackFactory() {
// Call the build method with a null fallback factory
Feign feign = builder.build(null);
// Verify that the Feign instance is not null
assertThat(feign).isNotNull();
// Additional verifications can be added here based on the implementation
}
public interface MyService {
@RequestLine("GET /hello")
String sayHello();
}
}

@ -0,0 +1,209 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.config.adapter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ConfigurableApplicationContext;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
/**
* Tests for {@link PolarisRefreshEntireContextRefresher}.
*
* @author Shedfree Wu
*/
public class PolarisRefreshEntireContextRefresherTest {
@Mock
private PolarisConfigProperties polarisConfigProperties;
@Mock
private SpringValueRegistry springValueRegistry;
@Mock
private ConfigFileService configFileService;
@Mock
private ContextRefresher contextRefresher;
@Mock
private ConfigurableApplicationContext applicationContext;
@Mock
private PolarisConfigCustomExtensionLayer mockExtensionLayer;
private PolarisRefreshEntireContextRefresher refresher;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
refresher = new PolarisRefreshEntireContextRefresher(
polarisConfigProperties,
springValueRegistry,
configFileService,
contextRefresher
);
refresher.setApplicationContext(applicationContext);
}
@Test
void testRefreshSpringValue() {
// test refreshSpringValue method, it should do nothing
refresher.refreshSpringValue("test.key");
// Verify
verifyNoInteractions(contextRefresher);
verifyNoInteractions(springValueRegistry);
}
@Test
void testRefreshConfigurationPropertiesWithRefreshScope() {
// Arrange
Set<String> changeKeys = new HashSet<>();
changeKeys.add("test.key1");
changeKeys.add("test.key2");
// mock test.key1 in refresh scope
when(springValueRegistry.isRefreshScopeKey("test.key1")).thenReturn(true);
// Act
refresher.refreshConfigurationProperties(changeKeys);
// Verify
verify(contextRefresher, times(1)).refresh();
verifyNoInteractions(applicationContext);
}
@Test
void testRefreshConfigurationPropertiesWithoutRefreshScope() {
// Arrange
Set<String> changeKeys = new HashSet<>();
changeKeys.add("test.key1");
changeKeys.add("test.key2");
// mock a key not in refresh scope
when(springValueRegistry.isRefreshScopeKey(anyString())).thenReturn(false);
// Act
refresher.refreshConfigurationProperties(changeKeys);
// Verify
verify(contextRefresher, never()).refresh();
verify(applicationContext, times(1))
.publishEvent(any(EnvironmentChangeEvent.class));
}
@Test
void testSetApplicationContext() {
// Arrange
ConfigurableApplicationContext newContext = mock(ConfigurableApplicationContext.class);
// Act
refresher.setApplicationContext(newContext);
// Verify
Set<String> changeKeys = new HashSet<>();
changeKeys.add("test.key");
when(springValueRegistry.isRefreshScopeKey(anyString())).thenReturn(false);
refresher.refreshConfigurationProperties(changeKeys);
verify(newContext, times(1)).publishEvent(any(EnvironmentChangeEvent.class));
}
@Test
void testRefreshConfigurationPropertiesWithEmptyChangeKeys() {
// Arrange
Set<String> changeKeys = new HashSet<>();
// Act
refresher.refreshConfigurationProperties(changeKeys);
// Verify
verify(contextRefresher, never()).refresh();
verify(applicationContext, times(1))
.publishEvent(any(EnvironmentChangeEvent.class));
}
@Test
void testRefreshConfigurationPropertiesWithMultipleRefreshScopeKeys() {
// Arrange
Set<String> changeKeys = new HashSet<>();
changeKeys.add("test.key1");
changeKeys.add("test.key2");
changeKeys.add("test.key3");
// mock multiple keys in refresh scope
when(springValueRegistry.isRefreshScopeKey(anyString())).thenReturn(true);
// Act
refresher.refreshConfigurationProperties(changeKeys);
// Verify
verify(contextRefresher, times(1)).refresh();
verifyNoInteractions(applicationContext);
}
@Test
void testPolarisConfigCustomExtensionLayer() throws Exception {
refresher.setRegistered(true);
Field field = PolarisConfigPropertyAutoRefresher.class
.getDeclaredField("polarisConfigCustomExtensionLayer");
field.setAccessible(true);
field.set(refresher, mockExtensionLayer);
Method method = PolarisConfigPropertyAutoRefresher.class
.getDeclaredMethod("customInitRegisterPolarisConfig", PolarisConfigPropertyAutoRefresher.class);
method.setAccessible(true);
method.invoke(refresher, refresher);
method = PolarisConfigPropertyAutoRefresher.class.getDeclaredMethod(
"customRegisterPolarisConfigPublishChangeListener",
PolarisPropertySource.class, PolarisPropertySource.class);
method.setAccessible(true);
method.invoke(refresher, null, null);
// Verify
verify(mockExtensionLayer, times(1)).initRegisterConfig(refresher);
}
}

@ -25,6 +25,7 @@ import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.GzipUtil;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.cloud.polaris.contract.config.PolarisContractProperties;
@ -100,7 +101,7 @@ public class PolarisContractReporter implements ApplicationListener<ApplicationR
ReportServiceContractRequest request = new ReportServiceContractRequest();
String name = polarisContractProperties.getName();
if (StringUtils.isBlank(name)) {
name = polarisDiscoveryProperties.getService();
name = MetadataContext.LOCAL_SERVICE;
}
request.setName(name);
request.setNamespace(polarisDiscoveryProperties.getNamespace());

@ -47,7 +47,7 @@ public final class FilterConstant {
/**
* Swagger resource url prefix.
*/
public static final String SWAGGER_RESOURCE_PREFIX = "/swagger-resource/";
public static final String SWAGGER_RESOURCE_PREFIX = "/swagger-resource";
/**
* Swagger webjars V2 url prefix.

@ -22,6 +22,12 @@
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-security-crypto</artifactId>
<groupId>org.springframework.security</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>

@ -104,6 +104,7 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, path);
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
LOG.info("block by ratelimit rule, uri:{}", exchange.getRequest().getURI());
ServerHttpResponse response = exchange.getResponse();
DataBuffer dataBuffer;
if (Objects.nonNull(quotaResponse.getActiveRule())

@ -98,6 +98,7 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
try {
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, request.getRequestURI());
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
LOG.info("block by ratelimit rule, uri:{}", request.getRequestURI());
if (Objects.nonNull(quotaResponse.getActiveRule())
&& StringUtils.isNotBlank(quotaResponse.getActiveRule().getCustomResponse().getBody())) {
response.setStatus(polarisRateLimitProperties.getRejectHttpCode());

@ -43,8 +43,8 @@ import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver;
import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver;
import com.tencent.cloud.polaris.router.zuul.PolarisRibbonRoutingFilter;
import com.tencent.cloud.polaris.router.zuul.RouterLabelZuulFilter;
import com.tencent.cloud.rpc.enhancement.instrument.zuul.EnhancedZuulPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedZuulPluginRunner;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;

@ -38,7 +38,7 @@ import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedZuulPluginRunner;
import com.tencent.cloud.rpc.enhancement.instrument.zuul.EnhancedZuulPluginRunner;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ -37,7 +37,7 @@ import com.tencent.cloud.polaris.loadbalancer.PolarisLoadBalancer;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedZuulPluginRunner;
import com.tencent.cloud.rpc.enhancement.instrument.zuul.EnhancedZuulPluginRunner;
import okhttp3.OkHttpClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;

@ -158,24 +158,25 @@ public final class MetadataContextHolder {
Map<String, String> dynamicApplicationMetadata, MetadataProvider callerMetadataProvider) {
com.tencent.polaris.metadata.core.manager.MetadataContextHolder.refresh(metadataManager -> {
// caller transitive metadata to local custom transitive metadata
MetadataContainer metadataContainerUpstream = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false);
MetadataContainer calleeCustomMetadataContainer = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false);
if (!CollectionUtils.isEmpty(dynamicTransitiveMetadata)) {
for (Map.Entry<String, String> entry : dynamicTransitiveMetadata.entrySet()) {
metadataContainerUpstream.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.PASS_THROUGH);
calleeCustomMetadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.PASS_THROUGH);
}
}
// caller disposable metadata to caller custom disposable metadata
MetadataContainer metadataContainerDownstream = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false);
MetadataContainer callerCustomMetadataContainer = metadataManager.getMetadataContainer(MetadataType.CUSTOM, true);
if (!CollectionUtils.isEmpty(dynamicDisposableMetadata)) {
for (Map.Entry<String, String> entry : dynamicDisposableMetadata.entrySet()) {
metadataContainerDownstream.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.NONE);
calleeCustomMetadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.NONE);
callerCustomMetadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.DISPOSABLE);
}
}
// caller application metadata to caller application disposable metadata
MetadataContainer applicationMetadataContainerDownstream = metadataManager.getMetadataContainer(MetadataType.APPLICATION, true);
MetadataContainer callerApplicationMetadataContainer = metadataManager.getMetadataContainer(MetadataType.APPLICATION, true);
if (!CollectionUtils.isEmpty(dynamicApplicationMetadata)) {
for (Map.Entry<String, String> entry : dynamicApplicationMetadata.entrySet()) {
applicationMetadataContainerDownstream.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.DISPOSABLE);
callerApplicationMetadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.DISPOSABLE);
}
}

@ -73,13 +73,13 @@
<revision>2.0.2.0-Hoxton.SR12-SNAPSHOT</revision>
<!-- Dependencies -->
<polaris.version>2.0.0.0</polaris.version>
<polaris.version>2.0.0.1</polaris.version>
<guava.version>32.1.3-jre</guava.version>
<logback.version>1.2.13</logback.version>
<springdoc.version>1.7.0</springdoc.version>
<io.swagger.version>1.5.24</io.swagger.version>
<mocktio.version>4.5.1</mocktio.version>
<byte-buddy.version>1.12.10</byte-buddy.version>
<byte-buddy.version>1.12.19</byte-buddy.version>
<jackson.version>2.15.3</jackson.version>
<protobuf-java.version>3.21.7</protobuf-java.version>
<joda-time.version>2.9.9</joda-time.version>
@ -238,6 +238,12 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-security-protection-plugin</artifactId>
<version>${revision}</version>
</dependency>
<!-- third part framework dependencies -->
<dependency>
<groupId>com.google.guava</groupId>

@ -22,6 +22,7 @@
<module>spring-cloud-starter-tencent-threadlocal-plugin</module>
<module>spring-cloud-starter-tencent-trace-plugin</module>
<module>spring-cloud-starter-tencent-fault-tolerance</module>
<module>spring-cloud-tencent-security-protection-plugin</module>
</modules>
</project>

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-security-protection-plugin</artifactId>
<name>Spring Cloud Tencent Lossless Plugin</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,52 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.protection;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
/**
* ExitUtils.
*
* @author Shedfree Wu
*/
public final class ExitUtils {
private ExitUtils() {
}
public static void exit(ApplicationContext context) {
exit(context, 3000);
}
public static void exit(ApplicationContext context, int delay) {
if (context instanceof ConfigurableApplicationContext) {
ConfigurableApplicationContext configurableContext = (ConfigurableApplicationContext) context;
configurableContext.close();
}
try {
Thread.sleep(delay);
}
catch (InterruptedException e) {
// do nothing
}
System.exit(0);
}
}

@ -0,0 +1,87 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.protection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.function.RouterFunction;
/**
* SecurityProtectionAutoConfiguration.
*
* @author Shedfree Wu
*/
@Configuration
public class SecurityProtectionAutoConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityProtectionAutoConfiguration.class);
@Configuration
@ConditionalOnProperty(name = "spring.cloud.tencent.security.protection.servlet.enabled", matchIfMissing = true)
@ConditionalOnClass(name = {"org.springframework.web.servlet.function.RouterFunction"})
static class ServletProtectionConfiguration implements InitializingBean {
@Autowired(required = false)
List<RouterFunction> routerFunctions;
@Autowired
ApplicationContext applicationContext;
@Override
public void afterPropertiesSet() {
if (routerFunctions != null && !routerFunctions.isEmpty()) {
LOGGER.error("Detected the presence of webmvc RouterFunction-related beans, which may trigger the CVE-2024-38819 vulnerability. The program will soon exit.");
LOGGER.error("routerFunctions:{}: ", routerFunctions);
ExitUtils.exit(applicationContext);
}
}
}
@Configuration
@ConditionalOnProperty(name = "spring.cloud.tencent.security.protection.reactive.enabled", matchIfMissing = true)
@ConditionalOnClass(name = {"org.springframework.web.reactive.function.server.RouterFunction"})
static class ReactiveProtectionConfiguration implements InitializingBean {
@Autowired(required = false)
List<org.springframework.web.reactive.function.server.RouterFunction> routerFunctions;
@Autowired
ApplicationContext applicationContext;
@Override
public void afterPropertiesSet() {
if (routerFunctions != null && !routerFunctions.isEmpty()) {
LOGGER.error("Detected the presence of webflux RouterFunction-related beans, which may trigger the CVE-2024-38819 vulnerability. The program will soon exit.");
LOGGER.error("routerFunctions:{}: ", routerFunctions);
ExitUtils.exit(applicationContext);
}
}
}
}

@ -0,0 +1,3 @@
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.plugin.protection.SecurityProtectionAutoConfiguration

@ -0,0 +1,189 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.protection;
import java.security.Permission;
import java.util.ArrayList;
import java.util.List;
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.context.ConfigurableApplicationContext;
import org.springframework.web.servlet.function.RouterFunction;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* Test for {@link SecurityProtectionAutoConfiguration}.
*/
@ExtendWith(MockitoExtension.class)
class SecurityProtectionAutoConfigurationTest {
@Mock
private ConfigurableApplicationContext applicationContext;
@Test
void testServletProtectionNoRouterFunctions() {
// Arrange
SecurityProtectionAutoConfiguration.ServletProtectionConfiguration config =
new SecurityProtectionAutoConfiguration.ServletProtectionConfiguration();
config.applicationContext = applicationContext;
config.routerFunctions = null;
// Act
config.afterPropertiesSet();
// Verify
// Should not call exit when no RouterFunctions present
verify(applicationContext, never()).close();
}
@Test
void testServletProtectionEmptyRouterFunctions() {
// Arrange
SecurityProtectionAutoConfiguration.ServletProtectionConfiguration config =
new SecurityProtectionAutoConfiguration.ServletProtectionConfiguration();
config.applicationContext = applicationContext;
config.routerFunctions = new ArrayList<>();
// Act
config.afterPropertiesSet();
// Verify
// Should not call exit when RouterFunctions list is empty
verify(applicationContext, never()).close();
}
@Test
void testServletProtectionWithRouterFunctions() {
// Arrange
SecurityProtectionAutoConfiguration.ServletProtectionConfiguration config =
new SecurityProtectionAutoConfiguration.ServletProtectionConfiguration();
config.applicationContext = mock(ConfigurableApplicationContext.class);
List<RouterFunction> routerFunctions = new ArrayList<>();
routerFunctions.add(mock(RouterFunction.class));
config.routerFunctions = routerFunctions;
SecurityManager originalSecurityManager = System.getSecurityManager();
System.setSecurityManager(new ExitSecurityManager());
try {
config.afterPropertiesSet();
}
catch (SecurityException e) {
// Ignore
}
finally {
System.setSecurityManager(originalSecurityManager);
}
}
@Test
void testReactiveProtectionNoRouterFunctions() {
// Arrange
SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration config =
new SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration();
config.applicationContext = applicationContext;
config.routerFunctions = null;
// Act
config.afterPropertiesSet();
// Verify
verify(applicationContext, never()).close();
}
@Test
void testReactiveProtectionEmptyRouterFunctions() {
// Arrange
SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration config =
new SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration();
config.applicationContext = applicationContext;
config.routerFunctions = new ArrayList<>();
// Act
config.afterPropertiesSet();
// Verify
verify(applicationContext, never()).close();
}
@Test
void testReactiveProtectionWithRouterFunctions() {
// Arrange
SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration config =
new SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration();
config.applicationContext = mock(ConfigurableApplicationContext.class);
List<org.springframework.web.reactive.function.server.RouterFunction> routerFunctions = new ArrayList<>();
routerFunctions.add(mock(org.springframework.web.reactive.function.server.RouterFunction.class));
config.routerFunctions = routerFunctions;
SecurityManager originalSecurityManager = System.getSecurityManager();
System.setSecurityManager(new ExitSecurityManager());
try {
config.afterPropertiesSet();
}
catch (SecurityException e) {
// Ignore
}
finally {
System.setSecurityManager(originalSecurityManager);
}
}
@Test
void testInterruptedExceptionHandling() throws InterruptedException {
// Arrange
ConfigurableApplicationContext mockContext = mock(ConfigurableApplicationContext.class);
Thread testThread = new Thread(() -> ExitUtils.exit(mockContext, 3000));
SecurityManager originalSecurityManager = System.getSecurityManager();
System.setSecurityManager(new ExitSecurityManager());
try {
// Act
testThread.start();
testThread.interrupt();
Thread.sleep(6000);
}
catch (SecurityException e) {
// Ignore
}
finally {
System.setSecurityManager(originalSecurityManager);
}
}
public static class ExitSecurityManager extends SecurityManager {
@Override
public void checkPermission(Permission perm) {
if (perm.getName().contains("exitVM")) {
throw new SecurityException("System.exit is not allowed");
}
}
}
}

@ -179,6 +179,16 @@
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>registry-memory</artifactId>
<exclusions>
<exclusion>
<artifactId>error_prone_annotations</artifactId>
<groupId>com.google.errorprone</groupId>
</exclusion>
<exclusion>
<artifactId>j2objc-annotations</artifactId>
<groupId>com.google.j2objc</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>

@ -126,6 +126,10 @@ public final class TsfCoreEnvironmentPostProcessor implements EnvironmentPostPro
defaultProperties.put("spring.cloud.polaris.discovery.zero-protection.enabled", "true");
defaultProperties.put("spring.cloud.polaris.discovery.zero-protection.is-need-test-connectivity", "true");
defaultProperties.put("spring.cloud.discovery.client.health-indicator.enabled", "false");
String warmupEnabled = environment.getProperty("spring.cloud.polaris.warmup.enabled");
if (StringUtils.isBlank(warmupEnabled)) {
defaultProperties.put("spring.cloud.polaris.warmup.enabled", true);
}
// contract
defaultProperties.put("spring.cloud.polaris.contract.enabled", environment.getProperty("tsf.swagger.enabled", "true"));

@ -0,0 +1,69 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.context.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
/**
* Failed event listener.
*
* @author skyehtzhang
*/
@Configuration
public class FailedEventApplicationListener implements ApplicationListener<ApplicationEvent>, ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(FailedEventApplicationListener.class);
private ApplicationContext applicationContext;
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationFailedEvent) {
ApplicationFailedEvent failedEvent = (ApplicationFailedEvent) event;
if (failedEvent.getException() != null) {
logger.error("[onApplicationEvent] exception in failed event", failedEvent.getException());
}
if (applicationContext instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) applicationContext).close();
}
try {
Thread.sleep(3000);
}
catch (InterruptedException e) {
// do nothing
}
System.exit(0);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

@ -6,7 +6,8 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.context.config.PolarisContextBootstrapAutoConfiguration,\
com.tencent.cloud.polaris.context.config.extend.tsf.TsfContextBootstrapConfiguration
org.springframework.context.ApplicationListener=\
com.tencent.cloud.polaris.context.logging.PolarisLoggingApplicationListener
com.tencent.cloud.polaris.context.logging.PolarisLoggingApplicationListener,\
com.tencent.cloud.polaris.context.listener.FailedEventApplicationListener
org.springframework.boot.env.EnvironmentPostProcessor=\
com.tencent.cloud.polaris.context.config.PolarisContextEnvironmentPostProcessor,\
com.tencent.cloud.polaris.context.config.extend.tsf.TsfCoreEnvironmentPostProcessor

@ -0,0 +1,154 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.context.listener;
import java.security.Permission;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import static org.mockito.Mockito.when;
class FailedEventApplicationListenerTest {
@Mock
private ConfigurableApplicationContext mockConfigurableContext;
@Mock
private ApplicationContext mockApplicationContext;
@Mock
private ApplicationFailedEvent mockFailedEvent;
private FailedEventApplicationListener listener;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
listener = new FailedEventApplicationListener();
}
@Test
void testSetApplicationContext() {
// Test setting application context
listener.setApplicationContext(mockApplicationContext);
}
@Test
void testOnApplicationEventWithConfigurableContext() {
// Arrange
listener.setApplicationContext(mockConfigurableContext);
when(mockFailedEvent.getException()).thenReturn(new RuntimeException("Test Exception"));
SecurityManager originalSecurityManager = System.getSecurityManager();
System.setSecurityManager(new ExitSecurityManager());
try {
// Act
listener.onApplicationEvent(mockFailedEvent);
}
catch (SecurityException e) {
// Ignore
}
finally {
System.setSecurityManager(originalSecurityManager);
}
}
@Test
void testOnApplicationEventWithNonConfigurableContext() {
// Arrange
listener.setApplicationContext(mockApplicationContext);
when(mockFailedEvent.getException()).thenReturn(new RuntimeException("Test Exception"));
SecurityManager originalSecurityManager = System.getSecurityManager();
System.setSecurityManager(new ExitSecurityManager());
try {
// Act
listener.onApplicationEvent(mockFailedEvent);
}
catch (SecurityException e) {
// Ignore
}
finally {
System.setSecurityManager(originalSecurityManager);
}
}
@Test
void testOnApplicationEventWithInterruptedException() {
// Arrange
listener.setApplicationContext(mockConfigurableContext);
Thread.currentThread().interrupt(); // Simulate interruption
SecurityManager originalSecurityManager = System.getSecurityManager();
System.setSecurityManager(new ExitSecurityManager());
try {
// Act
listener.onApplicationEvent(mockFailedEvent);
}
catch (SecurityException e) {
// Ignore
}
finally {
System.setSecurityManager(originalSecurityManager);
}
}
@Test
void testOnApplicationEventWithNullException() {
// Arrange
listener.setApplicationContext(mockConfigurableContext);
when(mockFailedEvent.getException()).thenReturn(null);
SecurityManager originalSecurityManager = System.getSecurityManager();
System.setSecurityManager(new ExitSecurityManager());
try {
// Act
listener.onApplicationEvent(mockFailedEvent);
}
catch (SecurityException e) {
// Ignore
}
finally {
System.setSecurityManager(originalSecurityManager);
}
}
public static class ExitSecurityManager extends SecurityManager {
@Override
public void checkPermission(Permission perm) {
if (perm.getName().contains("exitVM")) {
throw new SecurityException("System.exit is not allowed");
}
}
}
}

@ -98,6 +98,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>

@ -56,7 +56,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -180,11 +179,6 @@ public class RpcEnhancementAutoConfiguration {
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
protected static class PolarisRestTemplateAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = {"org.springframework.cloud.client.loadbalancer.LoadBalancerRequestTransformer"})

@ -145,7 +145,8 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered {
Route routeAttr = originExchange.getAttribute(GATEWAY_ROUTE_ATTR);
try {
return new URI(originExchange.getRequest().getURI().getScheme(),
routeAttr.getUri().getHost(), originExchange.getRequest().getURI().getPath(), originExchange.getRequest().getURI().getRawQuery());
routeAttr.getUri().getHost(), originExchange.getRequest().getURI()
.getPath(), originExchange.getRequest().getURI().getRawQuery());
}
catch (URISyntaxException e) {
return null;

@ -17,15 +17,22 @@
package com.tencent.cloud.rpc.enhancement.instrument.webclient;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Optional;
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.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
@ -53,6 +60,7 @@ public class EnhancedWebClientExchangeFilterFunction implements ExchangeFilterFu
.httpHeaders(originRequest.headers())
.httpMethod(originRequest.method())
.url(originRequest.url())
.serviceUrl(getServiceUri(originRequest))
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(originRequest);
@ -62,7 +70,21 @@ public class EnhancedWebClientExchangeFilterFunction implements ExchangeFilterFu
.getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), originRequest.url());
// Run post enhanced plugins.
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);
try {
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);
}
catch (CallAbortedException e) {
if (e.getFallbackInfo() == null) {
throw e;
}
HttpStatus httpStatus = HttpStatus.resolve(e.getFallbackInfo().getCode());
ClientResponse.Builder responseBuilder = ClientResponse.create(httpStatus != null ? httpStatus : HttpStatus.INTERNAL_SERVER_ERROR)
.body(Optional.of(e.getFallbackInfo().getBody()).orElse(""));
if (CollectionUtils.isNotEmpty(e.getFallbackInfo().getHeaders())) {
e.getFallbackInfo().getHeaders().forEach(responseBuilder::header);
}
return Mono.just(responseBuilder.build());
}
// request may be changed by plugin
ClientRequest request = (ClientRequest) enhancedPluginContext.getOriginRequest();
long startTime = System.currentTimeMillis();
@ -91,4 +113,21 @@ public class EnhancedWebClientExchangeFilterFunction implements ExchangeFilterFu
pluginRunner.run(EnhancedPluginType.Client.FINALLY, enhancedPluginContext);
});
}
private URI getServiceUri(ClientRequest clientRequest) {
Object instance = MetadataContextHolder.get()
.getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE);
if (!(instance instanceof ServiceInstance)) {
return null;
}
ServiceInstance serviceInstance = (ServiceInstance) instance;
URI uri = clientRequest.url();
try {
return new URI(uri.getScheme(), serviceInstance.getServiceId(), uri.getPath(), uri.getRawQuery());
}
catch (URISyntaxException e) {
return null;
}
}
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.zuul;
package com.tencent.cloud.rpc.enhancement.instrument.zuul;
import java.util.ArrayList;
import java.util.Collection;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.zuul;
package com.tencent.cloud.rpc.enhancement.instrument.zuul;
import java.util.ArrayList;
import java.util.Collection;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.zuul;
package com.tencent.cloud.rpc.enhancement.instrument.zuul;
import java.net.URI;
import java.net.URISyntaxException;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.zuul;
package com.tencent.cloud.rpc.enhancement.instrument.zuul;
import java.net.URI;
import java.net.URISyntaxException;

@ -73,6 +73,19 @@ public class DefaultEnhancedPluginRunner implements EnhancedPluginRunner {
}
}
static Registration getPolarisRegistration(List<Registration> registration) {
if (CollectionUtils.isEmpty(registration)) {
return null;
}
for (Registration reg : registration) {
if ("com.tencent.cloud.polaris.registry.PolarisRegistration".equals(reg.getClass().getName())) {
return reg;
}
}
return registration.get(0);
}
/**
* run the plugin.
*
@ -98,17 +111,4 @@ public class DefaultEnhancedPluginRunner implements EnhancedPluginRunner {
public ServiceInstance getLocalServiceInstance() {
return this.localServiceInstance;
}
private static Registration getPolarisRegistration(List<Registration> registration) {
if (CollectionUtils.isEmpty(registration)) {
return null;
}
for (Registration reg : registration) {
if ("com.tencent.cloud.polaris.registry.PolarisRegistration".equals(reg.getClass().getName())) {
return reg;
}
}
return registration.get(0);
}
}

@ -20,6 +20,7 @@ package com.tencent.cloud.rpc.enhancement.transformer;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.utils.StringUtils;
import org.springframework.cloud.client.ServiceInstance;
@ -41,10 +42,18 @@ public interface InstanceTransformer {
instance.setNamespace(MetadataContext.LOCAL_NAMESPACE);
instance.setService(serviceInstance.getServiceId());
instance.setProtocol(serviceInstance.getScheme());
instance.setId(serviceInstance.getInstanceId());
instance.setHost(serviceInstance.getHost());
instance.setPort(serviceInstance.getPort());
instance.setMetadata(serviceInstance.getMetadata());
instance.setWeight(100);
instance.setHealthy(true);
if (StringUtils.isNotEmpty(serviceInstance.getInstanceId())) {
instance.setId(serviceInstance.getInstanceId());
}
else {
instance.setId(serviceInstance.getHost() + ":" + serviceInstance.getPort());
}
}
void transformCustom(DefaultInstance instance, ServiceInstance serviceInstance);

@ -0,0 +1,90 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.plugin;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.serviceregistry.Registration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DefaultEnhancedPluginRunner}.
*
* @author Shedfree Wu
*/
@ExtendWith(MockitoExtension.class)
public class DefaultEnhancedPluginRunnerTest {
@Test
void getPolarisRegistration_WithPolarisRegistration_ReturnsPolarisRegistration() {
// Arrange
List<Registration> registrations = new ArrayList<>();
Registration normalReg = createMockRegistration();
Registration normalReg2 = createMockRegistration();
registrations.add(normalReg);
registrations.add(normalReg2);
// Act
Registration result = DefaultEnhancedPluginRunner.getPolarisRegistration(registrations);
// Assert
assertThat(result).isSameAs(normalReg);
}
// Helper method to create mock Registration objects
private Registration createMockRegistration() {
return new Registration() {
@Override
public String getServiceId() {
return null;
}
@Override
public String getHost() {
return null;
}
@Override
public int getPort() {
return 0;
}
@Override
public boolean isSecure() {
return false;
}
@Override
public java.net.URI getUri() {
return null;
}
@Override
public java.util.Map<String, String> getMetadata() {
return null;
}
};
}
}

@ -98,6 +98,7 @@ public class EnhancedPluginContextTest {
EnhancedRequestContext requestContext = new EnhancedRequestContext();
requestContext.setHttpHeaders(new HttpHeaders());
requestContext.setUrl(new URI("/"));
requestContext.setServiceUrl(new URI("http://test-service/path"));
requestContext.setHttpMethod(HttpMethod.GET);
EnhancedRequestContext requestContext1 = EnhancedRequestContext.builder()

Loading…
Cancel
Save