Merge branch '2020.0' into feature_2020_restTemplate

pull/300/head
weihu 3 years ago
commit 1f43310ef2

@ -5,24 +5,47 @@ name: Test with Junit
on: on:
push: push:
branches: [ 2020.0 ] branches:
- main
- 2021.0
- 2020.0
- greenwich
pull_request: pull_request:
branches: [ 2020.0 ] branches:
- main
- 2021.0
- 2020.0
- greenwich
jobs: jobs:
build: build:
strategy:
matrix:
java: [ 8, 11, 17 ]
os: [ 'windows-latest', 'macos-latest', 'ubuntu-latest' ]
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout codes - name: Checkout codes
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up JDK 8 - name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v2 uses: actions/setup-java@v3
with: with:
java-version: '8' distribution: 'temurin'
distribution: 'adopt' java-version: ${{ matrix.java }}
- name: Cache local Maven repository
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
# - name: Build with Maven # - name: Build with Maven
# run: mvn -B package --file pom.xml # run: mvn -B package --file pom.xml
- name: Test with Maven - name: Test with Maven
run: mvn -B test --file pom.xml run: mvn -B test --file pom.xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: '**/target/site/jacoco/jacoco.xml'

@ -6,3 +6,7 @@
- [Feature: Add config module unit test](https://github.com/Tencent/spring-cloud-tencent/pull/255) - [Feature: Add config module unit test](https://github.com/Tencent/spring-cloud-tencent/pull/255)
- [Upgrade: fix third-party lib CVEs & upgrade core spring libs version](https://github.com/Tencent/spring-cloud-tencent/pull/258) - [Upgrade: fix third-party lib CVEs & upgrade core spring libs version](https://github.com/Tencent/spring-cloud-tencent/pull/258)
- [feat:support reading configuration from application.yml or application.properties.](https://github.com/Tencent/spring-cloud-tencent/pull/261) - [feat:support reading configuration from application.yml or application.properties.](https://github.com/Tencent/spring-cloud-tencent/pull/261)
- [change the way from escape to encode in 2020.0](https://github.com/Tencent/spring-cloud-tencent/pull/257)
- [fix:fix ClassNotFoundException while not importing openfeign when using circuit-breaker module.](https://github.com/Tencent/spring-cloud-tencent/pull/270)
- [fix:solve the chaos code problem on rejectTips](https://github.com/Tencent/spring-cloud-tencent/pull/283)
- [fix:solve ratelimit-callee-service UnknownHostException.](https://github.com/Tencent/spring-cloud-tencent/pull/291)

@ -95,7 +95,7 @@
<spring.framework.version>5.3.21</spring.framework.version> <spring.framework.version>5.3.21</spring.framework.version>
<!-- Maven Plugin Versions --> <!-- Maven Plugin Versions -->
<jacoco.version>0.8.3</jacoco.version> <jacoco.version>0.8.8</jacoco.version>
<maven-source-plugin.version>3.2.0</maven-source-plugin.version> <maven-source-plugin.version>3.2.0</maven-source-plugin.version>
<flatten-maven-plugin.version>1.2.7</flatten-maven-plugin.version> <flatten-maven-plugin.version>1.2.7</flatten-maven-plugin.version>
<maven-gpg-plugin.version>3.0.1</maven-gpg-plugin.version> <maven-gpg-plugin.version>3.0.1</maven-gpg-plugin.version>

@ -25,6 +25,7 @@ import com.tencent.polaris.factory.api.DiscoveryAPIFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.openfeign.FeignAutoConfiguration; import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -41,6 +42,7 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
*/ */
@ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", havingValue = "true", matchIfMissing = true)
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.cloud.openfeign.FeignAutoConfiguration")
@AutoConfigureAfter(PolarisContextAutoConfiguration.class) @AutoConfigureAfter(PolarisContextAutoConfiguration.class)
@AutoConfigureBefore(FeignAutoConfiguration.class) @AutoConfigureBefore(FeignAutoConfiguration.class)
public class PolarisFeignClientAutoConfiguration { public class PolarisFeignClientAutoConfiguration {

@ -66,7 +66,7 @@ public class ConfigChangeListenerTest {
Sets.newHashSet("timeout")); Sets.newHashSet("timeout"));
applicationEventPublisher.publishEvent(event); applicationEventPublisher.publishEvent(event);
Thread.sleep(200);
//after change //after change
Assert.assertEquals(2, testConfig.getChangeCnt()); Assert.assertEquals(2, testConfig.getChangeCnt());
Assert.assertEquals(2000, testConfig.getTimeout()); Assert.assertEquals(2000, testConfig.getTimeout());

@ -55,7 +55,7 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB
/** /**
* Reactive filter to check quota. * Reactive filter to check quota.
* *
* @author Haotian Zhang, lepdou * @author Haotian Zhang, lepdou, cheese8
*/ */
public class QuotaCheckReactiveFilter implements WebFilter, Ordered { public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
@ -106,7 +106,7 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
ServerHttpResponse response = exchange.getResponse(); ServerHttpResponse response = exchange.getResponse();
response.setRawStatusCode(polarisRateLimitProperties.getRejectHttpCode()); response.setRawStatusCode(polarisRateLimitProperties.getRejectHttpCode());
response.getHeaders().setContentType(MediaType.APPLICATION_JSON); response.getHeaders().setContentType(MediaType.TEXT_HTML);
DataBuffer dataBuffer = response.bufferFactory().allocateBuffer() DataBuffer dataBuffer = response.bufferFactory().allocateBuffer()
.write(rejectTips.getBytes(StandardCharsets.UTF_8)); .write(rejectTips.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer)); return response.writeWith(Mono.just(dataBuffer));

@ -53,7 +53,7 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB
/** /**
* Servlet filter to check quota. * Servlet filter to check quota.
* *
* @author Haotian Zhang, lepdou * @author Haotian Zhang, lepdou, cheese8
*/ */
@Order(RateLimitConstant.FILTER_ORDER) @Order(RateLimitConstant.FILTER_ORDER)
public class QuotaCheckServletFilter extends OncePerRequestFilter { public class QuotaCheckServletFilter extends OncePerRequestFilter {
@ -99,6 +99,7 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
response.setStatus(polarisRateLimitProperties.getRejectHttpCode()); response.setStatus(polarisRateLimitProperties.getRejectHttpCode());
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(rejectTips); response.getWriter().write(rejectTips);
return; return;
} }

@ -65,7 +65,7 @@ import static org.mockito.Mockito.when;
/** /**
* Test for {@link QuotaCheckReactiveFilter}. * Test for {@link QuotaCheckReactiveFilter}.
* *
* @author Haotian Zhang * @author Haotian Zhang, cheese8
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
@SpringBootTest(classes = QuotaCheckReactiveFilterTest.TestApplication.class, properties = { @SpringBootTest(classes = QuotaCheckReactiveFilterTest.TestApplication.class, properties = {
@ -118,7 +118,7 @@ public class QuotaCheckReactiveFilterTest {
}); });
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties(); PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips"); polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips提示消息");
polarisRateLimitProperties.setRejectHttpCode(419); polarisRateLimitProperties.setRejectHttpCode(419);
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class); RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
@ -138,7 +138,7 @@ public class QuotaCheckReactiveFilterTest {
try { try {
Field rejectTips = QuotaCheckReactiveFilter.class.getDeclaredField("rejectTips"); Field rejectTips = QuotaCheckReactiveFilter.class.getDeclaredField("rejectTips");
rejectTips.setAccessible(true); rejectTips.setAccessible(true);
assertThat(rejectTips.get(quotaCheckReactiveFilter)).isEqualTo("RejectRequestTips"); assertThat(rejectTips.get(quotaCheckReactiveFilter)).isEqualTo("RejectRequestTips提示消息");
} }
catch (NoSuchFieldException | IllegalAccessException e) { catch (NoSuchFieldException | IllegalAccessException e) {
fail("Exception encountered.", e); fail("Exception encountered.", e);

@ -65,7 +65,7 @@ import static org.mockito.Mockito.when;
/** /**
* Test for {@link QuotaCheckServletFilter}. * Test for {@link QuotaCheckServletFilter}.
* *
* @author Haotian Zhang * @author Haotian Zhang, cheese8
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
@SpringBootTest(classes = QuotaCheckServletFilterTest.TestApplication.class, properties = { @SpringBootTest(classes = QuotaCheckServletFilterTest.TestApplication.class, properties = {
@ -77,6 +77,8 @@ public class QuotaCheckServletFilterTest {
private QuotaCheckServletFilter quotaCheckServletFilter; private QuotaCheckServletFilter quotaCheckServletFilter;
private QuotaCheckServletFilter quotaCheckWithHtmlRejectTipsServletFilter;
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic; private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
@BeforeClass @BeforeClass
@ -87,7 +89,6 @@ public class QuotaCheckServletFilterTest {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test"); .thenReturn("unit-test");
} }
@AfterClass @AfterClass
@ -118,13 +119,18 @@ public class QuotaCheckServletFilterTest {
}); });
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties(); PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips"); polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips提示消息");
polarisRateLimitProperties.setRejectHttpCode(419); polarisRateLimitProperties.setRejectHttpCode(419);
PolarisRateLimitProperties polarisRateLimitWithHtmlRejectTipsProperties = new PolarisRateLimitProperties();
polarisRateLimitWithHtmlRejectTipsProperties.setRejectRequestTips("<h1>RejectRequestTips提示消息</h1>");
polarisRateLimitWithHtmlRejectTipsProperties.setRejectHttpCode(419);
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class); RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.EMPTY_SET); when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.EMPTY_SET);
this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver); this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
this.quotaCheckWithHtmlRejectTipsServletFilter = new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleLabelResolver);
} }
@Test @Test
@ -133,7 +139,16 @@ public class QuotaCheckServletFilterTest {
try { try {
Field rejectTips = QuotaCheckServletFilter.class.getDeclaredField("rejectTips"); Field rejectTips = QuotaCheckServletFilter.class.getDeclaredField("rejectTips");
rejectTips.setAccessible(true); rejectTips.setAccessible(true);
assertThat(rejectTips.get(quotaCheckServletFilter)).isEqualTo("RejectRequestTips"); assertThat(rejectTips.get(quotaCheckServletFilter)).isEqualTo("RejectRequestTips提示消息");
}
catch (NoSuchFieldException | IllegalAccessException e) {
fail("Exception encountered.", e);
}
quotaCheckWithHtmlRejectTipsServletFilter.init();
try {
Field rejectTips = QuotaCheckServletFilter.class.getDeclaredField("rejectTips");
rejectTips.setAccessible(true);
assertThat(rejectTips.get(quotaCheckWithHtmlRejectTipsServletFilter)).isEqualTo("<h1>RejectRequestTips提示消息</h1>");
} }
catch (NoSuchFieldException | IllegalAccessException e) { catch (NoSuchFieldException | IllegalAccessException e) {
fail("Exception encountered.", e); fail("Exception encountered.", e);
@ -201,8 +216,11 @@ public class QuotaCheckServletFilterTest {
MetadataContext.LOCAL_SERVICE = "TestApp3"; MetadataContext.LOCAL_SERVICE = "TestApp3";
quotaCheckServletFilter.doFilterInternal(request, response, filterChain); quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
assertThat(response.getStatus()).isEqualTo(419); assertThat(response.getStatus()).isEqualTo(419);
assertThat(response.getContentAsString()).isEqualTo("RejectRequestTips"); assertThat(response.getContentAsString()).isEqualTo("RejectRequestTips提示消息");
quotaCheckWithHtmlRejectTipsServletFilter.doFilterInternal(request, response, filterChain);
assertThat(response.getStatus()).isEqualTo(419);
assertThat(response.getContentAsString()).isEqualTo("RejectRequestTips提示消息");
// Exception // Exception
MetadataContext.LOCAL_SERVICE = "TestApp4"; MetadataContext.LOCAL_SERVICE = "TestApp4";

@ -17,6 +17,9 @@
package com.tencent.cloud.polaris.router; package com.tencent.cloud.polaris.router;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -27,7 +30,6 @@ import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.pojo.PolarisServiceInstance; import com.tencent.cloud.common.pojo.PolarisServiceInstance;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils; import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils;
import com.tencent.cloud.polaris.router.config.PolarisMetadataRouterProperties; import com.tencent.cloud.polaris.router.config.PolarisMetadataRouterProperties;
@ -66,7 +68,7 @@ import org.springframework.util.CollectionUtils;
* And {@link PolarisRouterServiceInstanceListSupplier#get(Request)} provides the ability to pass in http headers, * And {@link PolarisRouterServiceInstanceListSupplier#get(Request)} provides the ability to pass in http headers,
* so routing capabilities are implemented through IRule. * so routing capabilities are implemented through IRule.
* *
* @author Haotian Zhang, lepdou * @author Haotian Zhang, lepdou, cheese8
*/ */
public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier { public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {
@ -123,19 +125,15 @@ public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceI
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get() routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)); .getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE));
labelHeaderValues.forEach(labelHeaderValue -> { Map<String, String> labelHeaderValuesMap = new HashMap<>();
Map<String, String> labels = JacksonUtils.deserialize2Map(labelHeaderValue); try {
if (!CollectionUtils.isEmpty(labels)) { String labelHeaderValuesContent = labelHeaderValues.stream().findFirst().get();
Map<String, String> unescapeLabels = new HashMap<>(labels.size()); labelHeaderValuesMap.putAll(JacksonUtils.deserialize2Map(URLDecoder.decode(labelHeaderValuesContent, StandardCharsets.UTF_8.name())));
for (Map.Entry<String, String> entry : labels.entrySet()) {
String escapedKey = ExpressionLabelUtils.unescape(entry.getKey());
String escapedValue = ExpressionLabelUtils.unescape(entry.getValue());
unescapeLabels.put(escapedKey, escapedValue);
} }
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, unescapeLabels); catch (UnsupportedEncodingException e) {
throw new RuntimeException("unsupported charset exception " + StandardCharsets.UTF_8.name());
} }
}); routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labelHeaderValuesMap);
return routerContext; return routerContext;
} }

@ -18,6 +18,9 @@
package com.tencent.cloud.polaris.router.feign; package com.tencent.cloud.polaris.router.feign;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@ -28,7 +31,6 @@ import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
@ -45,6 +47,7 @@ import org.springframework.util.CollectionUtils;
* Resolver labels from request. * Resolver labels from request.
* *
* @author lepdou 2022-05-12 * @author lepdou 2022-05-12
* @author cheese8 2022-06-20
*/ */
public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered { public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelFeignInterceptor.class); private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelFeignInterceptor.class);
@ -102,22 +105,20 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); .getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
labels.putAll(transitiveLabels); labels.putAll(transitiveLabels);
// Because when the label is placed in RequestTemplate.header,
// RequestTemplate will parse the header according to the regular, which conflicts with the expression.
// Avoid conflicts by escaping.
Map<String, String> escapeLabels = new HashMap<>(labels.size());
for (Map.Entry<String, String> entry : labels.entrySet()) {
String escapedKey = ExpressionLabelUtils.escape(entry.getKey());
String escapedValue = ExpressionLabelUtils.escape(entry.getValue());
escapeLabels.put(escapedKey, escapedValue);
}
// pass label by header // pass label by header
if (escapeLabels.size() == 0) { if (labels.size() == 0) {
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER); requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER);
return; return;
} }
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, JacksonUtils.serialize2Json(escapeLabels));
String encodedLabelsContent;
try {
encodedLabelsContent = URLEncoder.encode(JacksonUtils.serialize2Json(labels), StandardCharsets.UTF_8.name());
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException("unsupported charset exception " + StandardCharsets.UTF_8.name());
}
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, encodedLabelsContent);
} }
private Map<String, String> getRuleExpressionLabels(RequestTemplate requestTemplate, String peerService) { private Map<String, String> getRuleExpressionLabels(RequestTemplate requestTemplate, String peerService) {

@ -19,7 +19,10 @@
package com.tencent.cloud.polaris.router.resttemplate; package com.tencent.cloud.polaris.router.resttemplate;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@ -53,6 +56,7 @@ import org.springframework.util.CollectionUtils;
* Parses the label from the request and puts it into the RouterContext for routing. * Parses the label from the request and puts it into the RouterContext for routing.
* *
* @author lepdou 2022-05-18 * @author lepdou 2022-05-18
* @author cheese8 2022-06-20
*/ */
public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisLoadBalancerInterceptor.class); private static final Logger LOGGER = LoggerFactory.getLogger(PolarisLoadBalancerInterceptor.class);
@ -126,22 +130,19 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); .getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
labels.putAll(transitiveLabels); labels.putAll(transitiveLabels);
// Because when the label is placed in RequestTemplate.header,
// RequestTemplate will parse the header according to the regular, which conflicts with the expression.
// Avoid conflicts by escaping.
Map<String, String> escapeLabels = new HashMap<>(labels.size());
for (Map.Entry<String, String> entry : labels.entrySet()) {
String escapedKey = ExpressionLabelUtils.escape(entry.getKey());
String escapedValue = ExpressionLabelUtils.escape(entry.getValue());
escapeLabels.put(escapedKey, escapedValue);
}
// pass label by header // pass label by header
if (escapeLabels.size() == 0) { if (labels.size() == 0) {
request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, null); request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, null);
return; return;
} }
request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, JacksonUtils.serialize2Json(escapeLabels)); String encodedLabelsContent;
try {
encodedLabelsContent = URLEncoder.encode(JacksonUtils.serialize2Json(labels), StandardCharsets.UTF_8.name());
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException("unsupported charset exception " + StandardCharsets.UTF_8.name());
}
request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, encodedLabelsContent);
} }
private Map<String, String> getExpressionLabels(HttpRequest request, String peerServiceName) { private Map<String, String> getExpressionLabels(HttpRequest request, String peerServiceName) {

@ -18,6 +18,9 @@
package com.tencent.cloud.polaris.router.feign; package com.tencent.cloud.polaris.router.feign;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -29,7 +32,6 @@ import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
@ -50,6 +52,7 @@ import static org.mockito.Mockito.when;
/** /**
* test for {@link RouterLabelFeignInterceptor} * test for {@link RouterLabelFeignInterceptor}
* @author lepdou 2022-05-26 * @author lepdou 2022-05-26
* @author cheese8 2022-06-20
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class RouterLabelFeignInterceptorTest { public class RouterLabelFeignInterceptorTest {
@ -117,26 +120,24 @@ public class RouterLabelFeignInterceptorTest {
Collection<String> routerLabels = requestTemplate.headers().get(RouterConstants.ROUTER_LABEL_HEADER); Collection<String> routerLabels = requestTemplate.headers().get(RouterConstants.ROUTER_LABEL_HEADER);
Assert.assertNotNull(routerLabels); Map<String, String> routerLabelsMap = new HashMap<>();
for (String value : routerLabels) { try {
Map<String, String> labels = unescape(JacksonUtils.deserialize2Map(value)); String routerLabelContent = routerLabels.stream().findFirst().get();
routerLabelsMap.putAll(JacksonUtils.deserialize2Map(URLDecoder.decode(routerLabelContent, StandardCharsets.UTF_8.name())));
Assert.assertEquals("v1", labels.get("k1"));
Assert.assertEquals("v22", labels.get("k2"));
Assert.assertEquals("v3", labels.get("k3"));
Assert.assertEquals("v4", labels.get("k4"));
Assert.assertEquals(headerUidValue, labels.get("${http.header.uid}"));
Assert.assertEquals("", labels.get("${http.header.name}"));
}
}
} }
catch (UnsupportedEncodingException e) {
throw new RuntimeException("unsupported charset exception " + StandardCharsets.UTF_8.name());
} }
private Map<String, String> unescape(Map<String, String> labels) { Assert.assertNotNull(routerLabels);
Map<String, String> result = new HashMap<>(); Assert.assertEquals("v1", routerLabelsMap.get("k1"));
for (Map.Entry<String, String> entry : labels.entrySet()) { Assert.assertEquals("v22", routerLabelsMap.get("k2"));
result.put(ExpressionLabelUtils.unescape(entry.getKey()), ExpressionLabelUtils.unescape(entry.getValue())); Assert.assertEquals("v3", routerLabelsMap.get("k3"));
Assert.assertEquals("v4", routerLabelsMap.get("k4"));
Assert.assertEquals(headerUidValue, routerLabelsMap.get("${http.header.uid}"));
Assert.assertEquals("", routerLabelsMap.get("${http.header.name}"));
}
} }
return result;
} }
} }

@ -18,8 +18,9 @@
package com.tencent.cloud.polaris.router.resttemplate; package com.tencent.cloud.polaris.router.resttemplate;
import java.net.URI; import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -60,6 +61,7 @@ import static org.mockito.Mockito.when;
/** /**
* test for {@link PolarisLoadBalancerInterceptor} * test for {@link PolarisLoadBalancerInterceptor}
* @author lepdou 2022-05-26 * @author lepdou 2022-05-26
* @author cheese8 2022-06-20
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class PolarisLoadBalancerInterceptorTest { public class PolarisLoadBalancerInterceptorTest {
@ -184,13 +186,13 @@ public class PolarisLoadBalancerInterceptorTest {
verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService); verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService);
verify(routerLabelResolver).resolve(request, null); verify(routerLabelResolver).resolve(request, null);
Map<String, String> headers = JacksonUtils.deserialize2Map(request.getHeaders() Map<String, String> headers = JacksonUtils.deserialize2Map(URLDecoder.decode(request.getHeaders()
.get(RouterConstants.ROUTER_LABEL_HEADER).get(0)); .get(RouterConstants.ROUTER_LABEL_HEADER).get(0), StandardCharsets.UTF_8.name()));
Assert.assertEquals("v1", headers.get("k1")); Assert.assertEquals("v1", headers.get("k1"));
Assert.assertEquals("v22", headers.get("k2")); Assert.assertEquals("v22", headers.get("k2"));
Assert.assertEquals("v4", headers.get("k4")); Assert.assertEquals("v4", headers.get("k4"));
Assert.assertEquals("GET", headers.get("##@$@##http.method}")); Assert.assertEquals("GET", headers.get("${http.method}"));
Assert.assertEquals("/user/get", headers.get("##@$@##http.uri}")); Assert.assertEquals("/user/get", headers.get("${http.uri}"));
} }
static class MockedLoadBalancerRequest<T> implements LoadBalancerRequest<T> { static class MockedLoadBalancerRequest<T> implements LoadBalancerRequest<T> {

@ -41,6 +41,7 @@ import org.springframework.web.server.ServerWebExchange;
* the utils for parse label expression. * the utils for parse label expression.
* *
* @author lepdou 2022-05-13 * @author lepdou 2022-05-13
* @author cheese8 2022-06-20
*/ */
public class ExpressionLabelUtils { public class ExpressionLabelUtils {
@ -76,18 +77,10 @@ public class ExpressionLabelUtils {
* the expression of uri. * the expression of uri.
*/ */
public static final String LABEL_URI = "${http.uri}"; public static final String LABEL_URI = "${http.uri}";
/**
* the prefix of expression.
*/
public static final String LABEL_PREFIX = "${";
/** /**
* the suffix of expression. * the suffix of expression.
*/ */
public static final String LABEL_SUFFIX = "}"; public static final String LABEL_SUFFIX = "}";
/**
* the escape prefix of label.
*/
public static final String LABEL_ESCAPE_PREFIX = "##@$@##";
public static boolean isExpressionLabel(String labelKey) { public static boolean isExpressionLabel(String labelKey) {
if (StringUtils.isEmpty(labelKey)) { if (StringUtils.isEmpty(labelKey)) {
@ -103,14 +96,6 @@ public class ExpressionLabelUtils {
&& StringUtils.endsWith(labelKey, LABEL_SUFFIX); && StringUtils.endsWith(labelKey, LABEL_SUFFIX);
} }
public static String escape(String str) {
return StringUtils.replace(str, LABEL_PREFIX, LABEL_ESCAPE_PREFIX);
}
public static String unescape(String str) {
return StringUtils.replace(str, LABEL_ESCAPE_PREFIX, LABEL_PREFIX);
}
public static Map<String, String> resolve(HttpServletRequest request, Set<String> labelKeys) { public static Map<String, String> resolve(HttpServletRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) { if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap(); return Collections.emptyMap();

@ -23,6 +23,7 @@ import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;
/** /**
* Read file content from classpath resource. * Read file content from classpath resource.
@ -35,20 +36,15 @@ public final class ResourceFileUtils {
} }
public static String readFile(String path) throws IOException { public static String readFile(String path) throws IOException {
StringBuilder sb = new StringBuilder();
ClassPathResource classPathResource = new ClassPathResource(path); ClassPathResource classPathResource = new ClassPathResource(path);
if (classPathResource.exists() && classPathResource.isReadable()) { if (classPathResource.exists() && classPathResource.isReadable()) {
try (InputStream inputStream = classPathResource.getInputStream()) { try (InputStream inputStream = classPathResource.getInputStream()) {
byte[] buffer = new byte[1024 * 10]; return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
int len;
while ((len = inputStream.read(buffer)) != -1) {
sb.append(new String(buffer, 0, len, StandardCharsets.UTF_8));
} }
} }
} return "";
return sb.toString();
} }
} }

@ -39,6 +39,7 @@ import org.springframework.mock.web.server.MockServerWebExchange;
/** /**
* test for {@link ExpressionLabelUtils} * test for {@link ExpressionLabelUtils}
* @author lepdou 2022-05-27 * @author lepdou 2022-05-27
* @author cheese8 2022-06-20
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class ExpressionLabelUtilsTest { public class ExpressionLabelUtilsTest {
@ -76,39 +77,6 @@ public class ExpressionLabelUtilsTest {
Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel9)); Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel9));
} }
@Test
public void testEscape() {
String validLabel1 = "${http.query.uid}";
String validLabel2 = "${http.header.uid}";
String validLabel3 = "${http.cookie.uid}";
String validLabel4 = "${http.method}";
String validLabel5 = "${http.uri}";
String invalidLabel1 = "${http.queryuid}";
String invalidLabel2 = "{http.query.uid}";
String invalidLabel3 = "${http.query.uid";
String invalidLabel4 = "$ {http.query.uid}";
String invalidLabel5 = "${ http.query.uid}";
String invalidLabel6 = "${query.uid}";
String invalidLabel7 = "http.query.uid";
String invalidLabel8 = "$${http.uri}";
String invalidLabel9 = "#{http.uri}";
Assert.assertEquals(validLabel1, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(validLabel1)));
Assert.assertEquals(validLabel2, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(validLabel2)));
Assert.assertEquals(validLabel3, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(validLabel3)));
Assert.assertEquals(validLabel4, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(validLabel4)));
Assert.assertEquals(validLabel5, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(validLabel5)));
Assert.assertEquals(invalidLabel1, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel1)));
Assert.assertEquals(invalidLabel2, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel2)));
Assert.assertEquals(invalidLabel3, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel3)));
Assert.assertEquals(invalidLabel4, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel4)));
Assert.assertEquals(invalidLabel5, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel5)));
Assert.assertEquals(invalidLabel6, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel6)));
Assert.assertEquals(invalidLabel7, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel7)));
Assert.assertEquals(invalidLabel8, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel8)));
Assert.assertEquals(invalidLabel9, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel9)));
}
@Test @Test
public void testResolveHttpServletRequest() { public void testResolveHttpServletRequest() {
String validLabel1 = "${http.query.uid}"; String validLabel1 = "${http.query.uid}";

@ -35,7 +35,7 @@ public class ResourceFileUtilsTest {
@Test @Test
public void testReadExistedFile() throws IOException { public void testReadExistedFile() throws IOException {
String content = ResourceFileUtils.readFile("test.txt"); String content = ResourceFileUtils.readFile("test.txt");
Assert.assertEquals("just for test\n", content); Assert.assertEquals("just for test", content);
} }
@Test @Test

@ -18,6 +18,11 @@
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId> <artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId>

Loading…
Cancel
Save