Merge branch '2020.0' into 2020_feature_optimize_code

# Conflicts:
#	CHANGELOG.md
pull/482/head
weihu 3 years ago
commit 5169b44b47

@ -1,117 +0,0 @@
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

Binary file not shown.

@ -1,2 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar

@ -1,4 +1,16 @@
# Change Log
---
- [Bugfix: optimize ratelimit actuator](https://github.com/Tencent/spring-cloud-tencent/pull/419)
- [Feature: add rate limit filter debug log](https://github.com/Tencent/spring-cloud-tencent/pull/436)
- [Optimize: add EncodeTransferMedataRestTemplateInterceptor to RestTemplate](https://github.com/Tencent/spring-cloud-tencent/pull/439)
- [Add configurable heartbeat interval support](https://github.com/Tencent/spring-cloud-tencent/pull/443)
- [feat:enhance Feign and RestTemplate and support Polaris monitor.](https://github.com/Tencent/spring-cloud-tencent/pull/446)
- [Optimize feign & rest-template circuit-breaker logic](https://github.com/Tencent/spring-cloud-tencent/pull/453)
- [Bugfix: Removed unused class for circuit-breaker module](https://github.com/Tencent/spring-cloud-tencent/pull/456)
- [Feature: Add disposable metadata transfer support](https://github.com/Tencent/spring-cloud-tencent/pull/458)
- [loadBalancer module add unit test.](https://github.com/Tencent/spring-cloud-tencent/pull/468)
- [docs:update mvnw.](https://github.com/Tencent/spring-cloud-tencent/pull/475)
- [docs:update configuration metadata.](https://github.com/Tencent/spring-cloud-tencent/pull/473)
- [Feature: delete implement ServiceInstance](https://github.com/Tencent/spring-cloud-tencent/pull/481)

@ -42,6 +42,7 @@ Spring Cloud Tencent提供的能力包括但不限于
- 密码polaris
- 控制面地址: `grpc://183.47.111.80:8091`
-
`spring-cloud-tencent-example` 下 example 地址都默认指向了体验服务地址(`grpc://183.47.111.80:8091`),如果您只是体验 Spring Cloud
Tencent可直接一键运行任何 example。
@ -53,6 +54,10 @@ Tencent可直接一键运行任何 example。
Spring Cloud Tencent 所有组件都已上传到 Maven 中央仓库,只需要引入依赖即可。
> 注意:
>
> Spring Cloud Tencent 的版本列表可以查看 [Spring Cloud Tencent 版本管理](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86) 。
例如:
```` xml
@ -80,18 +85,38 @@ Spring Cloud Tencent 所有组件都已上传到 Maven 中央仓库,只需要
````
- ### 快速开始
- [Spring Cloud Tencent 版本管理](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86)
- [Spring Cloud Tencent 服务注册与发现](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Discovery-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 配置中心](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Config-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 限流](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Rate-Limit-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 熔断](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Circuitbreaker-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 服务路由](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Router-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 标签传递](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Metadata-Transfer-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
- ### 开发文档
- [项目概览](https://github.com/Tencent/spring-cloud-tencent/wiki/%E9%A1%B9%E7%9B%AE%E6%A6%82%E8%A7%88)
- [参与共建](https://github.com/Tencent/spring-cloud-tencent/wiki/%E5%8F%82%E4%B8%8E%E5%85%B1%E5%BB%BA)
## 开发入门
You can build this project with command:
```shell
## MacOS or Linux
./mvnw clean package
## Win
.\mvnw.cmd clean package
```
## 文档
- 使用文档
- [Spring Cloud Tencent Version Management](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86)
- [Spring Cloud Tencent Discovery](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Discovery-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent Config](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Config-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent Rate Limit](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Rate-Limit-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent CircuitBreaker](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Circuitbreaker-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent Router](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Router-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Starter Tencent RPC Enhancement](https://github.com/Tencent/spring-cloud-tencent/wiki/RPC%E5%A2%9E%E5%BC%BA)
- [Spring Cloud Tencent Metadata Transfer](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Metadata-Transfer-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
- [Actuator Endpoint Extension](https://github.com/Tencent/spring-cloud-tencent/wiki/Actuator-Endpoint-%E6%89%A9%E5%B1%95)
- 最佳实践
- [Multi-feature environment](https://github.com/Tencent/spring-cloud-tencent/wiki/多特性环境)
- [Multi-registration and multi-discovery](https://github.com/Tencent/spring-cloud-tencent/wiki/Multi-registration-and-multi-discovery)
- 开发文档
- [Project Structure Overview](https://github.com/Tencent/spring-cloud-tencent/wiki/%E9%A1%B9%E7%9B%AE%E6%A6%82%E8%A7%88)
- [Participate in co-construction](https://github.com/Tencent/spring-cloud-tencent/wiki/%E5%8F%82%E4%B8%8E%E5%85%B1%E5%BB%BA)
## 交流群

@ -56,6 +56,10 @@ directly with one click.
All the components of Spring Cloud Tencent have been uploaded to the Maven central repository, just need to introduce
dependencies.
> Notice:
>
> The version list of Spring Cloud Tencent can be found in [Spring Cloud Tencent Version Management](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86).
For example:
```` xml
@ -83,16 +87,36 @@ For example:
````
- ### Quick Start
## Develop Guide
You can build this project with command:
```shell
## MacOS or Linux
./mvnw clean package
## Win
.\mvnw.cmd clean package
```
## Documents
- Usage Doc
- [Spring Cloud Tencent Version Management](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86)
- [Spring Cloud Tencent Discovery](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Discovery-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent Config](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Config-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent Rate Limit](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Rate-Limit-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent CircuitBreaker](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Circuitbreaker-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent Router](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Router-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Starter Tencent RPC Enhancement](https://github.com/Tencent/spring-cloud-tencent/wiki/RPC%E5%A2%9E%E5%BC%BA)
- [Spring Cloud Tencent Metadata Transfer](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Metadata-Transfer-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
- [Actuator Endpoint Extension](https://github.com/Tencent/spring-cloud-tencent/wiki/Actuator-Endpoint-%E6%89%A9%E5%B1%95)
- Best Practices
- [Multi-feature environment](https://github.com/Tencent/spring-cloud-tencent/wiki/多特性环境)
- [Multi-registration and multi-discovery](https://github.com/Tencent/spring-cloud-tencent/wiki/Multi-registration-and-multi-discovery)
- ### Development Documentation
- Development documentation
- [Project Structure Overview](https://github.com/Tencent/spring-cloud-tencent/wiki/%E9%A1%B9%E7%9B%AE%E6%A6%82%E8%A7%88)
- [Participate in co-construction](https://github.com/Tencent/spring-cloud-tencent/wiki/%E5%8F%82%E4%B8%8E%E5%85%B1%E5%BB%BA)

20
mvnw vendored

@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
@ -36,6 +36,10 @@
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
@ -145,7 +149,7 @@ if [ -z "$JAVACMD" ] ; then
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
@ -212,9 +216,9 @@ else
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
@ -233,9 +237,9 @@ else
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
@ -305,6 +309,8 @@ WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

32
mvnw.cmd vendored

@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@ -46,8 +46,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
@ -120,9 +120,9 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@ -134,7 +134,7 @@ if exist %WRAPPER_JAR% (
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
@ -158,7 +158,13 @@ if exist %WRAPPER_JAR% (
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
@ -168,15 +174,15 @@ set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
exit /B %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

@ -40,6 +40,7 @@
<modules>
<module>spring-cloud-tencent-commons</module>
<module>spring-cloud-tencent-polaris-context</module>
<module>spring-cloud-tencent-rpc-enhancement</module>
<module>spring-cloud-tencent-polaris-loadbalancer</module>
<module>spring-cloud-starter-tencent-metadata-transfer</module>
<module>spring-cloud-starter-tencent-polaris-config</module>

@ -18,8 +18,9 @@
package com.tencent.cloud.metadata.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.metadata.core.DecodeTransferMetadataReactiveFilter;
@ -28,19 +29,15 @@ import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMedataScgFilter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import static javax.servlet.DispatcherType.ASYNC;
@ -127,9 +124,10 @@ public class MetadataTransferAutoConfiguration {
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
protected static class MetadataTransferRestTemplateConfig implements ApplicationContextAware {
protected static class MetadataTransferRestTemplateConfig {
private ApplicationContext context;
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor() {
@ -137,64 +135,12 @@ public class MetadataTransferAutoConfiguration {
}
@Bean
BeanPostProcessor encodeTransferMetadataRestTemplatePostProcessor(
EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor) {
// Coping with multiple bean injection scenarios
Map<String, RestTemplate> beans = this.context
.getBeansOfType(RestTemplate.class);
// If the restTemplate has been created when the
// MetadataRestTemplatePostProcessor Bean
// is initialized, then manually set the interceptor.
if (!CollectionUtils.isEmpty(beans)) {
for (RestTemplate restTemplate : beans.values()) {
List<ClientHttpRequestInterceptor> interceptors = restTemplate
.getInterceptors();
// Avoid setting interceptor repeatedly.
if (!interceptors.contains(encodeTransferMedataRestTemplateInterceptor)) {
interceptors.add(encodeTransferMedataRestTemplateInterceptor);
restTemplate.setInterceptors(interceptors);
}
}
}
return new EncodeTransferMetadataRestTemplatePostProcessor(
encodeTransferMedataRestTemplateInterceptor);
}
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext)
throws BeansException {
this.context = applicationContext;
}
public static class EncodeTransferMetadataRestTemplatePostProcessor
implements BeanPostProcessor {
private final EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor;
EncodeTransferMetadataRestTemplatePostProcessor(
EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor) {
this.encodeTransferMedataRestTemplateInterceptor = encodeTransferMedataRestTemplateInterceptor;
}
@Override
public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) {
return bean;
}
@Override
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) {
if (bean instanceof RestTemplate) {
RestTemplate restTemplate = (RestTemplate) bean;
List<ClientHttpRequestInterceptor> interceptors = restTemplate
.getInterceptors();
// Avoid setting interceptor repeatedly.
if (!interceptors.contains(encodeTransferMedataRestTemplateInterceptor)) {
interceptors.add(this.encodeTransferMedataRestTemplateInterceptor);
restTemplate.setInterceptors(interceptors);
}
}
return bean;
}
public SmartInitializingSingleton addEncodeTransferMedataInterceptorForRestTemplate(EncodeTransferMedataRestTemplateInterceptor interceptor) {
return () -> restTemplates.forEach(restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
list.add(interceptor);
restTemplate.setInterceptors(list);
});
}
}
}

@ -39,6 +39,8 @@ import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
/**
* Filter used for storing the metadata from upstream temporarily when web application is
@ -60,14 +62,17 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
// Get metadata string from http header.
ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest();
Map<String, String> internalTransitiveMetadata = getIntervalTransitiveMetadata(serverHttpRequest);
Map<String, String> internalTransitiveMetadata = getIntervalMetadata(serverHttpRequest, CUSTOM_METADATA);
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(serverWebExchange);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
MetadataContextHolder.init(mergedTransitiveMetadata);
Map<String, String> internalDisposableMetadata = getIntervalMetadata(serverHttpRequest, CUSTOM_DISPOSABLE_METADATA);
Map<String, String> mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata);
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata);
// Save to ServerWebExchange.
serverWebExchange.getAttributes().put(
@ -80,10 +85,9 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
.doFinally((type) -> MetadataContextHolder.remove());
}
private Map<String, String> getIntervalTransitiveMetadata(ServerHttpRequest serverHttpRequest) {
private Map<String, String> getIntervalMetadata(ServerHttpRequest serverHttpRequest, String headerName) {
HttpHeaders httpHeaders = serverHttpRequest.getHeaders();
String customMetadataStr = httpHeaders
.getFirst(MetadataConstant.HeaderName.CUSTOM_METADATA);
String customMetadataStr = httpHeaders.getFirst(headerName);
try {
if (StringUtils.hasText(customMetadataStr)) {
customMetadataStr = URLDecoder.decode(customMetadataStr, UTF_8);

@ -36,10 +36,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
/**
* Filter used for storing the metadata from upstream temporarily when web application is
@ -53,29 +56,27 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataServletFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, FilterChain filterChain)
protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest,
@NonNull HttpServletResponse httpServletResponse, FilterChain filterChain)
throws ServletException, IOException {
Map<String, String> internalTransitiveMetadata = getInternalTransitiveMetadata(httpServletRequest);
Map<String, String> internalTransitiveMetadata = getInternalMetadata(httpServletRequest, CUSTOM_METADATA);
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(httpServletRequest);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
try {
MetadataContextHolder.init(mergedTransitiveMetadata);
Map<String, String> internalDisposableMetadata = getInternalMetadata(httpServletRequest, CUSTOM_DISPOSABLE_METADATA);
Map<String, String> mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
catch (IOException | ServletException | RuntimeException e) {
throw e;
}
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private Map<String, String> getInternalTransitiveMetadata(HttpServletRequest httpServletRequest) {
private Map<String, String> getInternalMetadata(HttpServletRequest httpServletRequest, String headerName) {
// Get custom metadata string from http header.
String customMetadataStr = httpServletRequest.getHeader(MetadataConstant.HeaderName.CUSTOM_METADATA);
String customMetadataStr = httpServletRequest.getHeader(headerName);
try {
if (StringUtils.hasText(customMetadataStr)) {
customMetadataStr = URLDecoder.decode(customMetadataStr, UTF_8);

@ -19,7 +19,7 @@
package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
@ -33,9 +33,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
import static java.net.URLEncoder.encode;
/**
* Interceptor used for adding the metadata in http headers from context when web client
@ -57,17 +60,37 @@ public class EncodeTransferMedataFeignInterceptor implements RequestInterceptor,
// get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Map<String, String> disposableMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_DISPOSABLE);
if (!CollectionUtils.isEmpty(customMetadata)) {
String encodedTransitiveMetadata = JacksonUtils.serialize2Json(customMetadata);
requestTemplate.removeHeader(CUSTOM_METADATA);
// Clean up one-time metadata coming from upstream .
Map<String, String> newestCustomMetadata = new HashMap<>();
customMetadata.forEach((key, value) -> {
if (!disposableMetadata.containsKey(key)) {
newestCustomMetadata.put(key, value);
}
});
this.buildMetadataHeader(requestTemplate, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
// process custom metadata finally
this.buildMetadataHeader(requestTemplate, newestCustomMetadata, CUSTOM_METADATA);
}
/**
* Set metadata into the request header for {@link RestTemplate} .
* @param requestTemplate instance of {@link RestTemplate}
* @param metadata metadata map .
* @param headerName target metadata http header name .
*/
private void buildMetadataHeader(RequestTemplate requestTemplate, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
String encodedMetadata = JacksonUtils.serialize2Json(metadata);
requestTemplate.removeHeader(headerName);
try {
requestTemplate.header(CUSTOM_METADATA,
URLEncoder.encode(encodedTransitiveMetadata, UTF_8));
requestTemplate.header(headerName, encode(encodedMetadata, UTF_8));
}
catch (UnsupportedEncodingException e) {
LOG.error("Set header failed.", e);
requestTemplate.header(CUSTOM_METADATA, encodedTransitiveMetadata);
requestTemplate.header(headerName, encodedMetadata);
}
}
}

@ -21,6 +21,7 @@ package com.tencent.cloud.metadata.core;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
@ -33,9 +34,12 @@ import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
/**
* Interceptor used for adding the metadata in http headers from context when web client
@ -51,22 +55,44 @@ public class EncodeTransferMedataRestTemplateInterceptor implements ClientHttpRe
}
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
public ClientHttpResponse intercept(@NonNull HttpRequest httpRequest, @NonNull byte[] bytes,
@NonNull ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
// get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Map<String, String> disposableMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_DISPOSABLE);
if (!CollectionUtils.isEmpty(customMetadata)) {
String encodedTransitiveMetadata = JacksonUtils.serialize2Json(customMetadata);
Map<String, String> newestCustomMetadata = new HashMap<>();
customMetadata.forEach((key, value) -> {
if (!disposableMetadata.containsKey(key)) {
newestCustomMetadata.put(key, value);
}
});
// build custom disposable metadata request header
this.buildMetadataHeader(httpRequest, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
// build custom metadata request header
this.buildMetadataHeader(httpRequest, newestCustomMetadata, CUSTOM_METADATA);
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
/**
* Set metadata into the request header for {@link HttpRequest} .
*
* @param request instance of {@link HttpRequest}
* @param metadata metadata map .
* @param headerName target metadata http header name .
*/
private void buildMetadataHeader(HttpRequest request, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
String encodedMetadata = JacksonUtils.serialize2Json(metadata);
try {
httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA,
URLEncoder.encode(encodedTransitiveMetadata, UTF_8));
request.getHeaders().set(headerName, URLEncoder.encode(encodedMetadata, UTF_8));
}
catch (UnsupportedEncodingException e) {
httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA, encodedTransitiveMetadata);
request.getHeaders().set(headerName, encodedMetadata);
}
}
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
}

@ -20,6 +20,7 @@ package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
@ -36,6 +37,8 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
import static org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER;
/**
@ -59,22 +62,42 @@ public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered {
// get metadata of current thread
MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT);
// add new metadata and cover old
if (metadataContext == null) {
metadataContext = MetadataContextHolder.get();
}
Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
if (!CollectionUtils.isEmpty(customMetadata)) {
String metadataStr = JacksonUtils.serialize2Json(customMetadata);
Map<String, String> disposableMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_DISPOSABLE);
// Clean upstream disposable metadata.
Map<String, String> newestCustomMetadata = new HashMap<>();
customMetadata.forEach((key, value) -> {
if (!disposableMetadata.containsKey(key)) {
newestCustomMetadata.put(key, value);
}
});
this.buildMetadataHeader(builder, newestCustomMetadata, CUSTOM_METADATA);
this.buildMetadataHeader(builder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
return chain.filter(exchange.mutate().request(builder.build()).build());
}
/**
* Set metadata into the request header for {@link ServerHttpRequest.Builder} .
* @param builder instance of {@link ServerHttpRequest.Builder}
* @param metadata metadata map .
* @param headerName target metadata http header name .
*/
private void buildMetadataHeader(ServerHttpRequest.Builder builder, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
String encodedMetadata = JacksonUtils.serialize2Json(metadata);
try {
builder.header(MetadataConstant.HeaderName.CUSTOM_METADATA, URLEncoder.encode(metadataStr, UTF_8));
builder.header(headerName, URLEncoder.encode(encodedMetadata, UTF_8));
}
catch (UnsupportedEncodingException e) {
builder.header(MetadataConstant.HeaderName.CUSTOM_METADATA, metadataStr);
builder.header(headerName, encodedMetadata);
}
}
return chain.filter(exchange.mutate().request(builder.build()).build());
}
}

@ -18,6 +18,12 @@
package com.tencent.cloud.metadata.config;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor;
import org.assertj.core.api.Assertions;
@ -25,7 +31,12 @@ import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
/**
* Test for {@link MetadataTransferAutoConfiguration}.
@ -49,12 +60,49 @@ public class MetadataTransferAutoConfigurationTest {
Assertions.assertThat(context)
.hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class);
Assertions.assertThat(context).hasSingleBean(EncodeTransferMedataRestTemplateInterceptor.class);
Assertions.assertThat(context).hasSingleBean(
MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig
.EncodeTransferMetadataRestTemplatePostProcessor.class);
Assertions.assertThat(context)
.hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferScgFilterConfig.class);
Assertions.assertThat(context).hasSingleBean(GlobalFilter.class);
});
}
@Test
public void test2() {
this.applicationContextRunner
.withConfiguration(
AutoConfigurations.of(MetadataTransferAutoConfiguration.class, RestTemplateConfiguration.class))
.run(context -> {
Assertions.assertThat(context)
.hasSingleBean(EncodeTransferMedataFeignInterceptor.class);
EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor = context.getBean(EncodeTransferMedataRestTemplateInterceptor.class);
Map<String, RestTemplate> restTemplateMap = context.getBeansOfType(RestTemplate.class);
Assertions.assertThat(restTemplateMap.size()).isEqualTo(2);
for (String beanName : Arrays.asList("restTemplate", "loadBalancedRestTemplate")) {
RestTemplate restTemplate = restTemplateMap.get(beanName);
Assertions.assertThat(restTemplate).isNotNull();
List<ClientHttpRequestInterceptor> encodeTransferMedataFeignInterceptorList = restTemplate.getInterceptors()
.stream()
.filter(interceptor -> Objects.equals(interceptor, encodeTransferMedataRestTemplateInterceptor))
.collect(Collectors.toList());
//EncodeTransferMedataFeignInterceptor is not added repeatedly
Assertions.assertThat(encodeTransferMedataFeignInterceptorList.size()).isEqualTo(1);
}
});
}
@Configuration
static class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@LoadBalanced
@Bean
public RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
}
}

@ -18,36 +18,14 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>
</dependency>
<!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start -->
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-discovery-factory</artifactId>
<exclusions>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-rule</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-metadata</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-canary</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-set</artifactId>
</exclusion>
</exclusions>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
</dependency>
<!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start -->
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-circuitbreaker-factory</artifactId>
@ -84,28 +62,10 @@
</dependency>
<!-- Polaris dependencies end -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</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>

@ -17,84 +17,53 @@
package com.tencent.cloud.polaris.circuitbreaker.config;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignBeanPostProcessor;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisResponseErrorHandler;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisRestTemplateModifier;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisRestTemplateResponseErrorHandler;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.factory.api.DiscoveryAPIFactory;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisConfigModifier;
import com.tencent.polaris.api.config.consumer.ServiceRouterConfig;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import com.tencent.polaris.plugins.router.healthy.RecoverRouterConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.client.RestTemplate;
import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
/**
* Auto Configuration for Polaris {@link feign.Feign} OR {@link RestTemplate} which can automatically bring in the call
* results for reporting.
* Autoconfiguration at bootstrap phase.
*
* @author <a href="mailto:iskp.me@gmail.com">Palmer.Xu</a> 2022-06-29
* @author lepdou 2022-03-29
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
@ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", havingValue = "true", matchIfMissing = true)
public class PolarisCircuitBreakerAutoConfiguration {
/**
* Configuration for Polaris {@link feign.Feign} which can automatically bring in the call
* results for reporting.
*
* @author Haotian Zhang
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.cloud.openfeign.FeignAutoConfiguration")
@AutoConfigureAfter(PolarisContextAutoConfiguration.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", havingValue = "true", matchIfMissing = true)
protected static class PolarisFeignClientAutoConfiguration {
@Bean
public CircuitBreakerConfigModifier circuitBreakerConfigModifier() {
return new CircuitBreakerConfigModifier();
}
@Bean
public ConsumerAPI consumerAPI(SDKContext context) {
return DiscoveryAPIFactory.createConsumerAPIByContext(context);
}
public static class CircuitBreakerConfigModifier implements PolarisConfigModifier {
@Bean
@Order(HIGHEST_PRECEDENCE)
public PolarisFeignBeanPostProcessor polarisFeignBeanPostProcessor(ConsumerAPI consumerAPI) {
return new PolarisFeignBeanPostProcessor(consumerAPI);
}
}
@Override
public void modify(ConfigurationImpl configuration) {
// Turn on circuitbreaker configuration
configuration.getConsumer().getCircuitBreaker().setEnable(true);
// Set excludeCircuitBreakInstances to false
RecoverRouterConfig recoverRouterConfig = configuration.getConsumer().getServiceRouter()
.getPluginConfig(ServiceRouterConfig.DEFAULT_ROUTER_RECOVER, RecoverRouterConfig.class);
/**
* Configuration for Polaris {@link RestTemplate} which can automatically bring in the call
* results for reporting.
*
* @author wh 2022/6/21
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(PolarisContextAutoConfiguration.class)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", havingValue = "true", matchIfMissing = true)
protected static class PolarisRestTemplateAutoConfiguration {
recoverRouterConfig.setExcludeCircuitBreakInstances(true);
@Bean
public PolarisRestTemplateResponseErrorHandler polarisRestTemplateResponseErrorHandler(
ConsumerAPI consumerAPI, @Autowired(required = false) PolarisResponseErrorHandler polarisResponseErrorHandler) {
return new PolarisRestTemplateResponseErrorHandler(consumerAPI, polarisResponseErrorHandler);
// Update modified config to source properties
configuration.getConsumer().getServiceRouter()
.setPluginConfig(ServiceRouterConfig.DEFAULT_ROUTER_RECOVER, recoverRouterConfig);
}
@Bean
public PolarisRestTemplateModifier polarisRestTemplateBeanPostProcessor(
PolarisRestTemplateResponseErrorHandler restTemplateResponseErrorHandler) {
return new PolarisRestTemplateModifier(restTemplateResponseErrorHandler);
@Override
public int getOrder() {
return ContextConstant.ModifierOrder.CIRCUIT_BREAKER_ORDER;
}
}
}

@ -17,54 +17,18 @@
package com.tencent.cloud.polaris.circuitbreaker.config;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisConfigModifier;
import com.tencent.polaris.api.config.consumer.ServiceRouterConfig;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import com.tencent.polaris.plugins.router.healthy.RecoverRouterConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Auto configuration at bootstrap phase.
* Autoconfiguration at bootstrap phase.
*
* @author lepdou 2022-03-29
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
@ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", havingValue = "true",
matchIfMissing = true)
@ConditionalOnProperty("spring.cloud.polaris.enabled")
@Import(PolarisCircuitBreakerAutoConfiguration.class)
public class PolarisCircuitBreakerBootstrapConfiguration {
@Bean
public CircuitBreakerConfigModifier circuitBreakerConfigModifier() {
return new CircuitBreakerConfigModifier();
}
public static class CircuitBreakerConfigModifier implements PolarisConfigModifier {
@Override
public void modify(ConfigurationImpl configuration) {
// Turn on circuitbreaker configuration
configuration.getConsumer().getCircuitBreaker().setEnable(true);
// Set excludeCircuitBreakInstances to false
RecoverRouterConfig recoverRouterConfig = configuration.getConsumer().getServiceRouter()
.getPluginConfig(ServiceRouterConfig.DEFAULT_ROUTER_RECOVER, RecoverRouterConfig.class);
recoverRouterConfig.setExcludeCircuitBreakInstances(true);
// Update modified config to source properties
configuration.getConsumer().getServiceRouter()
.setPluginConfig(ServiceRouterConfig.DEFAULT_ROUTER_RECOVER, recoverRouterConfig);
}
@Override
public int getOrder() {
return ContextConstant.ModifierOrder.CIRCUIT_BREAKER_ORDER;
}
}
}

@ -1,4 +1,4 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerBootstrapConfiguration

@ -15,12 +15,8 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker;
package com.tencent.cloud.polaris.circuitbreaker.config;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignBeanPostProcessor;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.polaris.api.core.ConsumerAPI;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -33,20 +29,16 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Haotian Zhang
*/
public class PolarisFeignClientAutoConfigurationTest {
public class PolarisCircuitBreakerAutoConfigurationTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(
PolarisContextAutoConfiguration.class,
PolarisCircuitBreakerAutoConfiguration.class))
.withConfiguration(AutoConfigurations.of(PolarisCircuitBreakerAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(ConsumerAPI.class);
assertThat(context).hasSingleBean(PolarisFeignBeanPostProcessor.class);
assertThat(context).hasSingleBean(
PolarisCircuitBreakerAutoConfiguration.CircuitBreakerConfigModifier.class);
});
}
}

@ -15,9 +15,8 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker;
package com.tencent.cloud.polaris.circuitbreaker.config;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerBootstrapConfiguration;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -31,15 +30,16 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Haotian Zhang
*/
public class PolarisCircuitBreakerBootstrapConfigurationTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PolarisCircuitBreakerBootstrapConfiguration.class))
.withPropertyValues("spring.cloud.polaris.enabled=true")
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(
PolarisCircuitBreakerBootstrapConfiguration.CircuitBreakerConfigModifier.class);
PolarisCircuitBreakerAutoConfiguration.CircuitBreakerConfigModifier.class);
});
}
}

@ -1,15 +0,0 @@
spring:
cloud:
polaris:
address: grpc://127.0.0.1:8091
feign:
polaris:
enable: true
compression:
request:
enabled: false
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: false

@ -19,44 +19,13 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>
</dependency>
<!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start -->
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-discovery-factory</artifactId>
<exclusions>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-rule</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-metadata</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-canary</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-set</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-isolated</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-healthy</artifactId>
</exclusion>
</exclusions>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
</dependency>
<!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start -->
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-test-common</artifactId>

@ -20,11 +20,6 @@ package com.tencent.cloud.polaris;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler;
import com.tencent.cloud.polaris.extend.consul.ConsulContextProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.core.ProviderAPI;
import com.tencent.polaris.api.exception.PolarisException;
import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.factory.api.DiscoveryAPIFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@ -41,18 +36,6 @@ import org.springframework.context.annotation.Import;
@Import({PolarisDiscoveryProperties.class, ConsulContextProperties.class})
public class DiscoveryPropertiesAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ProviderAPI polarisProvider(SDKContext polarisContext) throws PolarisException {
return DiscoveryAPIFactory.createProviderAPIByContext(polarisContext);
}
@Bean
@ConditionalOnMissingBean
public ConsumerAPI polarisConsumer(SDKContext polarisContext) throws PolarisException {
return DiscoveryAPIFactory.createConsumerAPIByContext(polarisContext);
}
@Bean
@ConditionalOnMissingBean
public PolarisDiscoveryHandler polarisDiscoveryHandler() {

@ -30,6 +30,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import static com.tencent.cloud.common.constant.ContextConstant.DEFAULT_REGISTRY_HEARTBEAT_TIME_INTERVAL;
/**
* Properties for Polaris.
*
@ -95,6 +97,13 @@ public class PolarisDiscoveryProperties {
@Value("${spring.cloud.polaris.discovery.heartbeat.enabled:#{true}}")
private Boolean heartbeatEnabled = true;
/**
* Heart beat interval (The time interval must be greater than zero).
* Time unit: millisecond. Default: 5000.
* @see ContextConstant#DEFAULT_REGISTRY_HEARTBEAT_TIME_INTERVAL
*/
private Integer heartbeatInterval = 5000;
/**
* Custom health check url to override default.
*/
@ -202,6 +211,17 @@ public class PolarisDiscoveryProperties {
this.serviceListRefreshInterval = serviceListRefreshInterval;
}
public Integer getHeartbeatInterval() {
if (this.heartbeatEnabled && this.heartbeatInterval <= 0) {
return DEFAULT_REGISTRY_HEARTBEAT_TIME_INTERVAL;
}
return heartbeatInterval;
}
public void setHeartbeatInterval(Integer heartbeatInterval) {
this.heartbeatInterval = heartbeatInterval;
}
@Override
public String toString() {
return "PolarisDiscoveryProperties{" +
@ -215,6 +235,7 @@ public class PolarisDiscoveryProperties {
", enabled=" + enabled +
", registerEnabled=" + registerEnabled +
", heartbeatEnabled=" + heartbeatEnabled +
", heartbeatInterval=" + heartbeatInterval +
", healthCheckUrl='" + healthCheckUrl + '\'' +
", serviceListRefreshInterval=" + serviceListRefreshInterval +
'}';

@ -18,6 +18,7 @@
package com.tencent.cloud.polaris.discovery;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ -29,6 +30,7 @@ import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.api.rpc.InstancesResponse;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.util.CollectionUtils;
/**
* Polaris service discovery service.
@ -65,7 +67,10 @@ public class PolarisServiceDiscovery {
* @throws PolarisException polarisException
*/
public List<String> getServices() throws PolarisException {
return polarisDiscoveryHandler.GetServices().getServices().stream().map(ServiceInfo::getService)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(polarisDiscoveryHandler.GetServices().getServices())) {
return Collections.emptyList();
}
return polarisDiscoveryHandler.GetServices().getServices().stream()
.map(ServiceInfo::getService).collect(Collectors.toList());
}
}

@ -20,7 +20,6 @@ package com.tencent.cloud.polaris.registry;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
@ -42,6 +41,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.springframework.util.ReflectionUtils.rethrowRuntimeException;
/**
@ -53,8 +53,6 @@ public class PolarisServiceRegistry implements ServiceRegistry<Registration> {
private static final Logger log = LoggerFactory.getLogger(PolarisServiceRegistry.class);
private static final int ttl = 5;
private final PolarisDiscoveryProperties polarisDiscoveryProperties;
private final PolarisDiscoveryHandler polarisDiscoveryHandler;
@ -98,7 +96,7 @@ public class PolarisServiceRegistry implements ServiceRegistry<Registration> {
instanceRegisterRequest.setZone(staticMetadataManager.getZone());
instanceRegisterRequest.setCampus(staticMetadataManager.getCampus());
if (null != heartbeatExecutor) {
instanceRegisterRequest.setTtl(ttl);
instanceRegisterRequest.setTtl(polarisDiscoveryProperties.getHeartbeatInterval());
}
instanceRegisterRequest.setMetadata(registration.getMetadata());
instanceRegisterRequest.setProtocol(polarisDiscoveryProperties.getProtocol());
@ -218,6 +216,6 @@ public class PolarisServiceRegistry implements ServiceRegistry<Registration> {
catch (Exception e) {
log.error("polaris heartbeat runtime error", e);
}
}, ttl, ttl, TimeUnit.SECONDS);
}, polarisDiscoveryProperties.getHeartbeatInterval(), polarisDiscoveryProperties.getHeartbeatInterval(), MILLISECONDS);
}
}

@ -24,6 +24,12 @@
"defaultValue": true,
"description": "enable polaris registration or not."
},
{
"name": "spring.cloud.polaris.discovery.heartbeat-interval",
"type": "java.lang.Integer",
"defaultValue": "5000",
"description": "Millis interval of Heart beat. Default: 5000."
},
{
"name": "spring.cloud.polaris.discovery.health-check-url",
"type": "java.lang.String",

@ -41,6 +41,10 @@ public class PolarisDiscoveryPropertiesTest {
polarisDiscoveryProperties.setHeartbeatEnabled(true);
assertThat(polarisDiscoveryProperties.isHeartbeatEnabled()).isTrue();
// HeartbeatEnabled
polarisDiscoveryProperties.setHeartbeatInterval(2000);
assertThat(polarisDiscoveryProperties.getHeartbeatInterval()).isEqualTo(2000);
// Namespace
polarisDiscoveryProperties.setNamespace(NAMESPACE_TEST);
assertThat(polarisDiscoveryProperties.getNamespace()).isEqualTo(NAMESPACE_TEST);
@ -96,6 +100,7 @@ public class PolarisDiscoveryPropertiesTest {
+ ", enabled=true"
+ ", registerEnabled=true"
+ ", heartbeatEnabled=true"
+ ", heartbeatInterval=2000"
+ ", healthCheckUrl='/health'"
+ ", serviceListRefreshInterval=1000}");
}

@ -22,11 +22,16 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.polaris.client.pb.RateLimitProto;
import com.tencent.polaris.client.pb.RoutingProto;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
@ -42,6 +47,8 @@ import org.springframework.util.CollectionUtils;
@Endpoint(id = "polaris-ratelimit")
public class PolarisRateLimitRuleEndpoint {
private static final Logger LOG = LoggerFactory.getLogger(PolarisRateLimitRuleEndpoint.class);
private final ServiceRuleManager serviceRuleManager;
private final PolarisRateLimitProperties polarisRateLimitProperties;
@ -74,26 +81,15 @@ public class PolarisRateLimitRuleEndpoint {
}
for (RateLimitProto.Rule rule : rateLimit.getRulesList()) {
Map<String, Object> ruleMap = new HashMap<>();
ruleMap.put("id", rule.getId());
ruleMap.put("priority", rule.getPriority());
ruleMap.put("resource", rule.getResource());
ruleMap.put("type", rule.getType());
ruleMap.put("labels", rule.getLabelsMap());
ruleMap.put("amounts", rule.getAmountsList());
ruleMap.put("action", rule.getAction());
ruleMap.put("disable", rule.getDisable());
ruleMap.put("report", rule.getReport());
ruleMap.put("create_time", rule.getCtime());
ruleMap.put("modify_time", rule.getMtime());
ruleMap.put("revision", rule.getRevision());
ruleMap.put("service_token", rule.getServiceToken());
ruleMap.put("adjuster", rule.getAdjuster());
ruleMap.put("regex_combine", rule.getRegexCombine());
ruleMap.put("amount_mode", rule.getAmountMode());
ruleMap.put("failover", rule.getFailover());
ruleMap.put("cluster", rule.getCluster());
rateLimitRule.add(ruleMap);
String ruleJson = "";
try {
ruleJson = JsonFormat.printer().print(rule);
}
catch (InvalidProtocolBufferException e) {
LOG.error("rule to Json failed. check rule {}.", rule, e);
throw new RuntimeException("Json failed.", e);
}
rateLimitRule.add(JacksonUtils.deserialize2Map(ruleJson));
}
return rateLimitRule;
}

@ -114,6 +114,7 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
}
// Unirate
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultOk && quotaResponse.getWaitMs() > 0) {
LOG.debug("The request of [{}] will waiting for {}ms.", path, quotaResponse.getWaitMs());
return Mono.delay(Duration.ofMillis(quotaResponse.getWaitMs())).flatMap(e -> chain.filter(exchange));
}
}

@ -110,7 +110,7 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
}
// Unirate
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultOk && quotaResponse.getWaitMs() > 0) {
LOG.debug("Unirate sleep for {}ms.", quotaResponse.getWaitMs());
LOG.debug("The request of [{}] will waiting for {}ms.", request.getRequestURI(), quotaResponse.getWaitMs());
Thread.sleep(quotaResponse.getWaitMs());
}
@ -150,8 +150,7 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
return labelResolver.resolve(request);
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}",
labelResolver.getClass().getName(), e);
LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e);
}
}
return Collections.emptyMap();

@ -19,6 +19,10 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId>

@ -36,6 +36,11 @@ public final class ContextConstant {
*/
public static final String UTF_8 = StandardCharsets.UTF_8.name();
/**
* Default registry heartbeat time interval, default: 5000 (ms).
*/
public static final Integer DEFAULT_REGISTRY_HEARTBEAT_TIME_INTERVAL = 5000;
private ContextConstant() {
}
@ -68,5 +73,10 @@ public final class ContextConstant {
* Order of configuration modifier.
*/
public static Integer CONFIG_ORDER = 1;
/**
* Order of stat reporter configuration modifier.
*/
public static Integer STAT_REPORTER_ORDER = 1;
}
}

@ -26,6 +26,12 @@ import org.springframework.core.Ordered;
*/
public final class MetadataConstant {
/**
* Default Private Constructor.
*/
private MetadataConstant() {
}
/**
* Order of filter, interceptor, ...
*/
@ -57,6 +63,11 @@ public final class MetadataConstant {
*/
public static final String CUSTOM_METADATA = "SCT-CUSTOM-METADATA";
/**
* Custom Disposable Metadata.
*/
public static final String CUSTOM_DISPOSABLE_METADATA = "SCT-CUSTOM-DISPOSABLE-METADATA";
/**
* System Metadata.
*/

@ -40,6 +40,17 @@ public class MetadataContext {
* transitive context.
*/
public static final String FRAGMENT_TRANSITIVE = "transitive";
/**
* disposable Context.
*/
public static final String FRAGMENT_DISPOSABLE = "disposable";
/**
* upstream disposable Context.
*/
public static final String FRAGMENT_UPSTREAM_DISPOSABLE = "upstream-disposable";
private static final Logger LOG = LoggerFactory.getLogger(MetadataContext.class);
/**
* Namespace of local instance.

@ -20,12 +20,17 @@ package com.tencent.cloud.common.metadata;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.metadata.MetadataContext.FRAGMENT_DISPOSABLE;
import static com.tencent.cloud.common.metadata.MetadataContext.FRAGMENT_TRANSITIVE;
import static com.tencent.cloud.common.metadata.MetadataContext.FRAGMENT_UPSTREAM_DISPOSABLE;
/**
* Metadata Context Holder.
*
@ -36,6 +41,7 @@ public final class MetadataContextHolder {
private static final ThreadLocal<MetadataContext> METADATA_CONTEXT = new InheritableThreadLocal<>();
private static MetadataLocalProperties metadataLocalProperties;
private static StaticMetadataManager staticMetadataManager;
private MetadataContextHolder() {
@ -51,24 +57,55 @@ public final class MetadataContextHolder {
}
if (metadataLocalProperties == null) {
metadataLocalProperties = (MetadataLocalProperties) ApplicationContextAwareUtils
.getApplicationContext().getBean("metadataLocalProperties");
metadataLocalProperties = ApplicationContextAwareUtils.getApplicationContext().getBean(MetadataLocalProperties.class);
}
if (staticMetadataManager == null) {
staticMetadataManager = (StaticMetadataManager) ApplicationContextAwareUtils
.getApplicationContext().getBean("metadataManager");
staticMetadataManager = ApplicationContextAwareUtils.getApplicationContext().getBean(StaticMetadataManager.class);
}
// init static transitive metadata
MetadataContext metadataContext = new MetadataContext();
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE,
staticMetadataManager.getMergedStaticTransitiveMetadata());
metadataContext.putFragmentContext(FRAGMENT_TRANSITIVE, staticMetadataManager.getMergedStaticTransitiveMetadata());
metadataContext.putFragmentContext(FRAGMENT_DISPOSABLE, staticMetadataManager.getMergedStaticDisposableMetadata());
METADATA_CONTEXT.set(metadataContext);
return METADATA_CONTEXT.get();
}
/**
* Get disposable metadata value from thread local .
* @param key metadata key .
* @param upstream upstream disposable , otherwise will return local static disposable metadata .
* @return target disposable metadata value .
*/
public static Optional<String> getDisposableMetadata(String key, boolean upstream) {
MetadataContext context = get();
if (upstream) {
return Optional.ofNullable(context.getContext(FRAGMENT_UPSTREAM_DISPOSABLE, key));
}
else {
return Optional.ofNullable(context.getContext(FRAGMENT_DISPOSABLE, key));
}
}
/**
* Get all disposable metadata value from thread local .
* @param upstream upstream disposable , otherwise will return local static disposable metadata .
* @return target disposable metadata value .
*/
public static Map<String, String> getAllDisposableMetadata(boolean upstream) {
Map<String, String> disposables = new HashMap<>();
MetadataContext context = get();
if (upstream) {
disposables.putAll(context.getFragmentContext(FRAGMENT_UPSTREAM_DISPOSABLE));
}
else {
disposables.putAll(context.getFragmentContext(FRAGMENT_DISPOSABLE));
}
return Collections.unmodifiableMap(disposables);
}
/**
* Set metadata context.
* @param metadataContext metadata context
@ -80,21 +117,26 @@ public final class MetadataContextHolder {
/**
* Save metadata map to thread local.
* @param dynamicTransitiveMetadata custom metadata collection
* @param dynamicDisposableMetadata custom disposable metadata connection
*/
public static void init(Map<String, String> dynamicTransitiveMetadata) {
public static void init(Map<String, String> dynamicTransitiveMetadata, Map<String, String> dynamicDisposableMetadata) {
// Init ThreadLocal.
MetadataContextHolder.remove();
MetadataContext metadataContext = MetadataContextHolder.get();
// Save transitive metadata to ThreadLocal.
if (!CollectionUtils.isEmpty(dynamicTransitiveMetadata)) {
Map<String, String> staticTransitiveMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Map<String, String> staticTransitiveMetadata = metadataContext.getFragmentContext(FRAGMENT_TRANSITIVE);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(staticTransitiveMetadata);
mergedTransitiveMetadata.putAll(dynamicTransitiveMetadata);
metadataContext.putFragmentContext(FRAGMENT_TRANSITIVE, Collections.unmodifiableMap(mergedTransitiveMetadata));
Map<String, String> mergedDisposableMetadata = new HashMap<>(dynamicDisposableMetadata);
metadataContext.putFragmentContext(FRAGMENT_UPSTREAM_DISPOSABLE, Collections.unmodifiableMap(mergedDisposableMetadata));
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE,
Collections.unmodifiableMap(mergedTransitiveMetadata));
Map<String, String> staticDisposableMetadata = metadataContext.getFragmentContext(FRAGMENT_DISPOSABLE);
metadataContext.putFragmentContext(FRAGMENT_DISPOSABLE, Collections.unmodifiableMap(staticDisposableMetadata));
}
MetadataContextHolder.set(metadataContext);
}

@ -54,18 +54,22 @@ public class StaticMetadataManager {
private static final String ENV_METADATA_PREFIX = "SCT_METADATA_CONTENT_";
private static final int ENV_METADATA_PREFIX_LENGTH = ENV_METADATA_PREFIX.length();
private static final String ENV_METADATA_CONTENT_TRANSITIVE = "SCT_METADATA_CONTENT_TRANSITIVE";
private static final String ENV_METADATA_CONTENT_DISPOSABLE = "SCT_METADATA_CONTENT_DISPOSABLE";
private static final String ENV_METADATA_ZONE = "SCT_METADATA_ZONE";
private static final String ENV_METADATA_REGION = "SCT_METADATA_REGION";
private static final String ENV_METADATA_CAMPUS = "SCT_METADATA_CAMPUS";
private Map<String, String> envMetadata;
private Map<String, String> envTransitiveMetadata;
private Map<String, String> envDisposableMetadata;
private Map<String, String> configMetadata;
private Map<String, String> configTransitiveMetadata;
private Map<String, String> configDisposableMetadata;
private Map<String, String> customSPIMetadata;
private Map<String, String> customSPITransitiveMetadata;
private Map<String, String> customSPIDisposableMetadata;
private Map<String, String> mergedStaticMetadata;
private Map<String, String> mergedStaticTransitiveMetadata;
private Map<String, String> mergedStaticDisposableMetadata;
private String zone;
private String region;
private String campus;
@ -85,6 +89,7 @@ public class StaticMetadataManager {
LOGGER.info("[SCT] Loaded static metadata info. {}", this);
}
@SuppressWarnings("DuplicatedCode")
private void parseEnvMetadata() {
Map<String, String> allEnvs = System.getenv();
@ -118,27 +123,54 @@ public class StaticMetadataManager {
}
}
envTransitiveMetadata = Collections.unmodifiableMap(envTransitiveMetadata);
envDisposableMetadata = new HashMap<>();
// parse disposable metadata
String disposableKeys = allEnvs.get(ENV_METADATA_CONTENT_DISPOSABLE);
if (StringUtils.isNotBlank(disposableKeys)) {
String[] keyArr = StringUtils.split(disposableKeys, ",");
if (keyArr != null && keyArr.length > 0) {
for (String key : keyArr) {
String value = envMetadata.get(key);
if (StringUtils.isNotBlank(value)) {
envDisposableMetadata.put(key, value);
}
}
}
}
envDisposableMetadata = Collections.unmodifiableMap(envDisposableMetadata);
}
private void parseConfigMetadata(MetadataLocalProperties metadataLocalProperties) {
Map<String, String> allMetadata = metadataLocalProperties.getContent();
List<String> transitiveKeys = metadataLocalProperties.getTransitive();
List<String> disposableKeys = metadataLocalProperties.getDisposable();
Map<String, String> result = new HashMap<>();
Map<String, String> transitiveResult = new HashMap<>();
for (String key : transitiveKeys) {
if (allMetadata.containsKey(key)) {
result.put(key, allMetadata.get(key));
transitiveResult.put(key, allMetadata.get(key));
}
}
configTransitiveMetadata = Collections.unmodifiableMap(result);
Map<String, String> disposableResult = new HashMap<>();
for (String key : disposableKeys) {
if (allMetadata.containsKey(key)) {
disposableResult.put(key, allMetadata.get(key));
}
}
configTransitiveMetadata = Collections.unmodifiableMap(transitiveResult);
configDisposableMetadata = Collections.unmodifiableMap(disposableResult);
configMetadata = Collections.unmodifiableMap(allMetadata);
}
@SuppressWarnings("DuplicatedCode")
private void parseCustomMetadata(InstanceMetadataProvider instanceMetadataProvider) {
if (instanceMetadataProvider == null) {
customSPIMetadata = Collections.emptyMap();
customSPITransitiveMetadata = Collections.emptyMap();
customSPIDisposableMetadata = Collections.emptyMap();
return;
}
@ -162,6 +194,17 @@ public class StaticMetadataManager {
}
}
customSPITransitiveMetadata = Collections.unmodifiableMap(transitiveMetadata);
Set<String> disposableKeys = instanceMetadataProvider.getDisposableMetadataKeys();
Map<String, String> disposableMetadata = new HashMap<>();
if (!CollectionUtils.isEmpty(disposableKeys)) {
for (String key : disposableKeys) {
if (customSPIMetadata.containsKey(key)) {
disposableMetadata.put(key, customSPIMetadata.get(key));
}
}
}
customSPIDisposableMetadata = Collections.unmodifiableMap(disposableMetadata);
}
private void merge() {
@ -173,15 +216,19 @@ public class StaticMetadataManager {
mergedMetadataResult.putAll(customSPIMetadata);
// set location info as metadata
mergedMetadataResult.putAll(getLocationMetadata());
this.mergedStaticMetadata = Collections.unmodifiableMap(mergedMetadataResult);
Map<String, String> mergedTransitiveMetadataResult = new HashMap<>();
mergedTransitiveMetadataResult.putAll(configTransitiveMetadata);
mergedTransitiveMetadataResult.putAll(envTransitiveMetadata);
mergedTransitiveMetadataResult.putAll(customSPITransitiveMetadata);
this.mergedStaticTransitiveMetadata = Collections.unmodifiableMap(mergedTransitiveMetadataResult);
Map<String, String> mergedDisposableMetadataResult = new HashMap<>();
mergedDisposableMetadataResult.putAll(configDisposableMetadata);
mergedDisposableMetadataResult.putAll(envDisposableMetadata);
mergedDisposableMetadataResult.putAll(customSPIDisposableMetadata);
this.mergedStaticDisposableMetadata = Collections.unmodifiableMap(mergedDisposableMetadataResult);
}
private void parseLocationMetadata(MetadataLocalProperties metadataLocalProperties,
@ -228,6 +275,10 @@ public class StaticMetadataManager {
return envTransitiveMetadata;
}
public Map<String, String> getEnvDisposableMetadata() {
return envDisposableMetadata;
}
public Map<String, String> getAllConfigMetadata() {
return configMetadata;
}
@ -236,6 +287,10 @@ public class StaticMetadataManager {
return configTransitiveMetadata;
}
public Map<String, String> getConfigDisposableMetadata() {
return configDisposableMetadata;
}
public Map<String, String> getAllCustomMetadata() {
return customSPIMetadata;
}
@ -244,6 +299,10 @@ public class StaticMetadataManager {
return customSPITransitiveMetadata;
}
public Map<String, String> getCustomSPIDisposableMetadata() {
return customSPIDisposableMetadata;
}
public Map<String, String> getMergedStaticMetadata() {
return mergedStaticMetadata;
}
@ -252,6 +311,10 @@ public class StaticMetadataManager {
return mergedStaticTransitiveMetadata;
}
public Map<String, String> getMergedStaticDisposableMetadata() {
return mergedStaticDisposableMetadata;
}
public String getZone() {
return zone;
}

@ -43,6 +43,11 @@ public class MetadataLocalProperties {
*/
private List<String> transitive;
/**
* A disposable metadata key list .
*/
private List<String> disposable;
public Map<String, String> getContent() {
if (CollectionUtils.isEmpty(content)) {
content = new HashMap<>();
@ -64,4 +69,15 @@ public class MetadataLocalProperties {
public void setTransitive(List<String> transitive) {
this.transitive = transitive;
}
public List<String> getDisposable() {
if (CollectionUtils.isEmpty(disposable)) {
disposable = new ArrayList<>();
}
return disposable;
}
public void setDisposable(List<String> disposable) {
this.disposable = disposable;
}
}

@ -45,9 +45,14 @@ public class PolarisMetadataEndpoint {
result.put("Env", staticMetadataManager.getAllEnvMetadata());
result.put("EnvTransitive", staticMetadataManager.getEnvTransitiveMetadata());
result.put("ConfigTransitive", staticMetadataManager.getConfigTransitiveMetadata());
result.put("ConfigDisposable", staticMetadataManager.getConfigDisposableMetadata());
result.put("Config", staticMetadataManager.getAllConfigMetadata());
result.put("MergeStatic", staticMetadataManager.getMergedStaticMetadata());
result.put("CustomSPI", staticMetadataManager.getCustomSPITransitiveMetadata());
result.put("MergeStaticTransitive", staticMetadataManager.getMergedStaticTransitiveMetadata());
result.put("MergeStaticDisposable", staticMetadataManager.getMergedStaticDisposableMetadata());
result.put("CustomSPI", staticMetadataManager.getAllCustomMetadata());
result.put("CustomSPITransitive", staticMetadataManager.getCustomSPITransitiveMetadata());
result.put("CustomSPIDisposable", staticMetadataManager.getCustomSPIDisposableMetadata());
result.put("zone", staticMetadataManager.getZone());
result.put("region", staticMetadataManager.getRegion());
result.put("campus", staticMetadataManager.getCampus());

@ -43,6 +43,13 @@ public interface InstanceMetadataProvider {
return Collections.emptySet();
}
/**
* @return the keys of disposable metadata.
*/
default Set<String> getDisposableMetadataKeys() {
return Collections.emptySet();
}
/**
* The region of current instance.
*

@ -22,6 +22,7 @@ import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -42,7 +43,6 @@ public final class JacksonUtils {
private static final Logger LOG = LoggerFactory.getLogger(JacksonUtils.class);
private JacksonUtils() {
}
/**
@ -52,8 +52,25 @@ public final class JacksonUtils {
* @return Json String
*/
public static <T> String serialize2Json(T object) {
return serialize2Json(object, false);
}
/**
* Object to Json.
* @param object object to be serialized
* @param pretty pretty print
* @param <T> type of object
* @return Json String
*/
public static <T> String serialize2Json(T object, boolean pretty) {
try {
return OM.writeValueAsString(object);
if (pretty) {
ObjectWriter objectWriter = OM.writerWithDefaultPrettyPrinter();
return objectWriter.writeValueAsString(object);
}
else {
return OM.writeValueAsString(object);
}
}
catch (JsonProcessingException e) {
LOG.error("Object to Json failed. {}", object, e);

@ -9,6 +9,11 @@
"name": "spring.cloud.tencent.metadata.transitive",
"type": "java.util.List",
"description": "Custom transitive metadata key list."
},
{
"name": "spring.cloud.tencent.metadata.disposable",
"type": "java.util.List",
"description": "Custom disposable metadata key list."
}
]
}

@ -58,7 +58,7 @@ public class MetadataContextHolderTest {
customMetadata.put("a", "1");
customMetadata.put("b", "22");
customMetadata.put("c", "3");
MetadataContextHolder.init(customMetadata);
MetadataContextHolder.init(customMetadata, new HashMap<>());
metadataContext = MetadataContextHolder.get();
customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Assertions.assertThat(customMetadata.get("a")).isEqualTo("1");

@ -1,63 +1,68 @@
<?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</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
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</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-coverage</artifactId>
<name>Spring Cloud Tencent Coverage</name>
<packaging>pom</packaging>
<artifactId>spring-cloud-tencent-coverage</artifactId>
<name>Spring Cloud Tencent Coverage</name>
<packaging>pom</packaging>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-commons</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-commons</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-context</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-circuitbreaker</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-circuitbreaker</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
@ -65,24 +70,24 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>report-aggregate</id>
<phase>test</phase>
<goals>
<goal>report-aggregate</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/../target/site/jacoco</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>report-aggregate</id>
<phase>test</phase>
<goals>
<goal>report-aggregate</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/../target/site/jacoco</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -73,7 +73,7 @@
<revision>1.7.0-2020.0.5-SNAPSHOT</revision>
<!-- Dependencies -->
<polaris.version>1.7.0</polaris.version>
<polaris.version>1.7.2</polaris.version>
<logback.version>1.2.11</logback.version>
<mocktio.version>4.5.1</mocktio.version>
<byte-buddy.version>1.12.10</byte-buddy.version>
@ -110,6 +110,12 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>

@ -4,7 +4,7 @@
本样例将介绍如何在Spring Cloud项目中使用```spring-cloud-starter-tencent-metadata-transfer```以使用其各项功能。
本样例包括```metadata-callee-service```、```metadata-caller-service```。
本样例包括```metadata-frontend```、```metadata-middle```、```metadata-backend```。
## 使用说明
@ -41,8 +41,9 @@ spring:
##### IDEA启动
分别启动
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-callee-service```的```MetadataCalleeService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-caller-service```的```MetadataCallerService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-frontend```的```MetadataFrontendService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-middle```的```MetadataMiddleService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-backend```的```MetadataBackendService```
##### Maven打包启动
@ -53,7 +54,7 @@ spring:
mvn clean package
```
然后在```metadata-callee-service```、```metadata-caller-service```下找到生成的jar包运行
然后在```metadata-frontend```、```metadata-middle```、```metadata-backend```下找到生成的jar包运行
```
java -jar ${app.jar}
@ -63,7 +64,7 @@ java -jar ${app.jar}
### 元数据配置
在```spring-cloud-tencent-examples/metadata-transfer-example/metadata-caller-service```项目的```bootstrap.yml```配置文件中
- 在```spring-cloud-tencent-examples/metadata-transfer-example/metadata-frontend```项目的```bootstrap.yml```配置文件中
```yaml
spring:
@ -76,10 +77,37 @@ spring:
CUSTOM-METADATA-KEY-LOCAL: CUSTOM-VALUE-LOCAL
# 示例:可传递元数据
CUSTOM-METADATA-KEY-TRANSITIVE: CUSTOM-VALUE-TRANSITIVE
# 示例:一次性元数据
CUSTOM-METADATA-KEY-DISPOSABLE: CUSTOM-VALUE-DISPOSABLE-FRONTEND
# 指定哪个元数据的键值将沿着链接传递
transitive:
- CUSTOM-METADATA-KEY-TRANSITIVE
# 指定哪个元数据的键值只进行一次性传递(一跳)
disposable:
- CUSTOM-METADATA-KEY-DISPOSABLE
```
- 在```spring-cloud-tencent-examples/metadata-transfer-example/metadata-frontend```项目的```bootstrap.yml```配置文件中
```yaml
spring:
cloud:
tencent:
metadata:
# 定义元数据的键值对
content:
# 示例:本地元数据,默认不在链路中传递
CUSTOM-METADATA-KEY-LOCAL-2: CUSTOM-VALUE-LOCAL-2
# 示例:可传递元数据
CUSTOM-METADATA-KEY-TRANSITIVE-2: CUSTOM-VALUE-TRANSITIVE-2
# 示例:一次性元数据
CUSTOM-METADATA-KEY-DISPOSABLE: CUSTOM-VALUE-DISPOSABLE-MIDDLE
# 指定哪个元数据的键值将沿着链接传递
transitive:
- CUSTOM-METADATA-KEY-TRANSITIVE-2
# 指定哪个元数据的键值只进行一次性传递(一跳)
disposable:
- CUSTOM-METADATA-KEY-DISPOSABLE
```
### 验证
@ -87,31 +115,85 @@ spring:
#### 请求调用
```shell
curl -L -X GET 'http://127.0.0.1:48080/metadata/service/caller/feign/info'
curl -L -X GET 'http://127.0.0.1:48080/metadata/service/frontend/feign/info'
```
预期返回值
```
```json
{
"caller-metadata-contents": {
"frontend-transitive-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE"
},
"frontend-upstream-disposable-metadata": {
},
"frontend-local-disposable-metadata": {
"CUSTOM-METADATA-KEY-DISPOSABLE": "CUSTOM-VALUE-DISPOSABLE-FRONTEND"
},
"middle-transitive-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE",
"CUSTOM-METADATA-KEY-LOCAL": "CUSTOM-VALUE-LOCAL"
"CUSTOM-METADATA-KEY-TRANSITIVE-2": "CUSTOM-VALUE-TRANSITIVE-2"
},
"callee-transitive-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE"
"middle-upstream-disposable-metadata": {
"CUSTOM-METADATA-KEY-DISPOSABLE": "CUSTOM-VALUE-DISPOSABLE-FRONTEND"
},
"caller-transitive-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE"
"middle-local-disposable-metadata": {
"CUSTOM-METADATA-KEY-DISPOSABLE": "CUSTOM-VALUE-DISPOSABLE-MIDDLE"
},
"backend-transitive-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE",
"CUSTOM-METADATA-KEY-TRANSITIVE-2": "CUSTOM-VALUE-TRANSITIVE-2"
},
"backend-upstream-disposable-metadata": {
"CUSTOM-METADATA-KEY-DISPOSABLE": "CUSTOM-VALUE-DISPOSABLE-MIDDLE"
},
"backend-local-disposable-metadata": {
}
}
```
返回值解析
- Key `caller-metadata-contents` 表示 `metadata-caller-service` 项目中默认配置的所有的元数据。
- Key `caller-transitive-metadata` 表示 `metadata-caller-service` 项目中指定的可以在链路中传递的元数据列表。
- Key `callee-transitive-metadata` 表示 `metadata-callee-service` 项目被 `metadata-caller-service` 调用时传递过来的上游的元数据列表。
> `*`(星号),代表示例中的`frontend`、`middle`、`backend`。
- Key `*-transitive-metadata` 表示服务中默认配置的所有的可传递(全链路)的元数据。
- Key `*-upstream-disposable-metadata` 表示服务中从上游请求中获取到的一次性传递的元数据。
- Key `*-local-disposable-metadata` 表示当前服务配置的往下游传递的一次性的元数据。
### 如何通过Api获取传递的元数据
- 获取全局传递的元数据
```java
MetadataContext context = MetadataContextHolder.get();
Map<String, String> customMetadataMap = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
customMetadataMap.forEach((key, value) -> {
// ...
});
```
- 获取上游传递过来的一次性元数据
```java
Map<String, String> upstreamDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(true);
upstreamDisposableMetadatas.forEach((key, value) -> {
// ...
});
```
- 获取本地配置的一次性元数据
```java
Map<String, String> localDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(false);
localDisposableMetadatas.forEach((key, value) -> {
// ...
});
```
### Wiki参考

@ -2,9 +2,10 @@
## Example Introduction
This example shows how to use ```spring-cloud-starter-tencent-metadata-transfer``` in Spring Cloud project for its features.
This example shows how to use ```spring-cloud-starter-tencent-metadata-transfer``` in Spring Cloud project for its
features.
This example contains ```metadata-callee-service```、```metadata-caller-service```.
This example contains ```metadata-frontend```、```metadata-middle```、```metadata-backend```.
## Instruction
@ -40,8 +41,10 @@ Reference to [Polaris Getting Started](https://github.com/PolarisMesh/polaris#ge
- IDEA Launching
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-callee-service```s```MetadataCalleeService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-caller-service```'s```MetadataCallerService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-frontend```'s ```MetadataFrontendService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-middle```'s ```MetadataMiddleService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-backend```'s ```MetadataBackendService```
- Maven Package Launching
@ -51,7 +54,7 @@ Execute under ```spring-cloud-tencent-examples/metadata-transfer-example```
mvn clean package
```
Then find the jars under ```metadata-callee-service```、```metadata-caller-service```, and run it:
Then find the jars under ```metadata-frontend```、```metadata-middle```、```metadata-backend```, and run it:
```
java -jar ${app.jar}
@ -62,7 +65,7 @@ Launch application, change ${app.jar} to jar's package name.
### Metadata Configuration
In the ```bootstrap.yml``` configuration file of the ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-caller-service``` project
- In the ```bootstrap.yml``` configuration file of the ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-frontend``` project
```yaml
spring:
@ -75,9 +78,38 @@ spring:
CUSTOM-METADATA-KEY-LOCAL: CUSTOM-VALUE-LOCAL
# Example: transitive
CUSTOM-METADATA-KEY-TRANSITIVE: CUSTOM-VALUE-TRANSITIVE
# Example: disposable
CUSTOM-METADATA-KEY-DISPOSABLE: CUSTOM-VALUE-DISPOSABLE-FRONTEND
# Assigned which metadata key-value will be passed along the link
transitive:
- CUSTOM-METADATA-KEY-TRANSITIVE
# Specify which metadata key value will be passed only once (one-step)
disposable:
- CUSTOM-METADATA-KEY-DISPOSABLE
```
- In the ```bootstrap.yml``` configuration file of the ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-middle``` project
```yaml
spring:
cloud:
tencent:
metadata:
# Defined your metadata keys & values
content:
# Example: intransitive
CUSTOM-METADATA-KEY-LOCAL-2: CUSTOM-VALUE-LOCAL-2
# Example: transitive
CUSTOM-METADATA-KEY-TRANSITIVE-2: CUSTOM-VALUE-TRANSITIVE-2
# Example: disposable
CUSTOM-METADATA-KEY-DISPOSABLE: CUSTOM-VALUE-DISPOSABLE-MIDDLE
# Assigned which metadata key-value will be passed along the link
transitive:
- CUSTOM-METADATA-KEY-TRANSITIVE-2
# Specify which metadata key value will be passed only once (one-step)
disposable:
- CUSTOM-METADATA-KEY-DISPOSABLE
```
@ -93,24 +125,77 @@ Expected return rate
```
{
"caller-metadata-contents": {
"frontend-transitive-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE"
},
"frontend-upstream-disposable-metadata": {
},
"frontend-local-disposable-metadata": {
"CUSTOM-METADATA-KEY-DISPOSABLE": "CUSTOM-VALUE-DISPOSABLE-FRONTEND"
},
"middle-transitive-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE",
"CUSTOM-METADATA-KEY-LOCAL": "CUSTOM-VALUE-LOCAL"
"CUSTOM-METADATA-KEY-TRANSITIVE-2": "CUSTOM-VALUE-TRANSITIVE-2"
},
"callee-transitive-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE"
"middle-upstream-disposable-metadata": {
"CUSTOM-METADATA-KEY-DISPOSABLE": "CUSTOM-VALUE-DISPOSABLE-FRONTEND"
},
"caller-transitive-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE"
"middle-local-disposable-metadata": {
"CUSTOM-METADATA-KEY-DISPOSABLE": "CUSTOM-VALUE-DISPOSABLE-MIDDLE"
},
"backend-transitive-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE",
"CUSTOM-METADATA-KEY-TRANSITIVE-2": "CUSTOM-VALUE-TRANSITIVE-2"
},
"backend-upstream-disposable-metadata": {
"CUSTOM-METADATA-KEY-DISPOSABLE": "CUSTOM-VALUE-DISPOSABLE-MIDDLE"
},
"backend-local-disposable-metadata": {
}
}
```
Response value description
- Key `caller-metadata-contents` represents all metadata configured by default in the `metadata-caller-service` project.
- Key `caller-transitive-metadata` represents the list of metadata that can be passed in the link specified in the `metadata-caller-service` item.
- Key `callee-transitive-metadata` represents the list of upstream metadata passed when the `metadata-callee-service` project is called by `metadata-caller-service`.
> `*` (asterisk), representing `frontend`, `middle`, `backend` in the example.
- Key `*-transitive-metadata` represents all the passable (fully linked) metadata configured by default in the service.
- Key `*-upstream-disposable-metadata` indicates the one-time transmissible metadata obtained from upstream requests in the service.
- Key `*-local-disposable-metadata` indicates the one-time metadata passed downstream as configured by the current service.
### How to get the passed metadata via Api
- Get the metadata passed globally
```java
MetadataContext context = MetadataContextHolder.get();
Map<String, String> customMetadataMap = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
customMetadataMap.forEach((key, value) -> {
// ...
});
```
- Get disposable(one-time) metadata passed from upstream
```java
Map<String, String> upstreamDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(true);
upstreamDisposableMetadatas.forEach((key, value) -> {
// ...
});
```
- Get disposable(one-time) metadata for local configuration
```java
Map<String, String> localDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(false);
localDisposableMetadatas.forEach((key, value) -> {
// ...
});
```
### Wiki Reference

@ -10,8 +10,8 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>metadata-caller-service</artifactId>
<name>Spring Cloud Tencent Metadata Transfer Caller Service</name>
<artifactId>metadata-backend</artifactId>
<name>Spring Cloud Tencent Metadata Transfer Backend Service</name>
<dependencies>
<dependency>

@ -15,8 +15,9 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.callee;
package com.tencent.cloud.metadata.service.backend;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
@ -35,10 +36,10 @@ import org.springframework.web.bind.annotation.RestController;
* @author Palmer Xu
*/
@RestController
@RequestMapping("/metadata/service/callee")
public class MetadataCalleeController {
@RequestMapping("/metadata/service/backend")
public class MetadataBackendController {
private static final Logger LOG = LoggerFactory.getLogger(MetadataCalleeController.class);
private static final Logger LOG = LoggerFactory.getLogger(MetadataBackendController.class);
@Value("${server.port:0}")
private int port;
@ -48,18 +49,36 @@ public class MetadataCalleeController {
* @return information of callee
*/
@GetMapping("/info")
public Map<String, String> info() {
LOG.info("Metadata Service Callee [{}] is called.", port);
public Map<String, Map<String, String>> info() {
LOG.info("Metadata Backend Service [{}] is called.", port);
Map<String, Map<String, String>> ret = new HashMap<>();
// Get Custom Metadata From Context
MetadataContext context = MetadataContextHolder.get();
Map<String, String> customMetadataMap = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
customMetadataMap.forEach((key, value) -> {
LOG.info("Custom Metadata (Key-Value): {} : {}", key, value);
LOG.info("Metadata Backend Custom Metadata (Key-Value): {} : {}", key, value);
});
return customMetadataMap;
}
ret.put("backend-transitive-metadata", customMetadataMap);
// Get All Disposable metadata from upstream service
Map<String, String> upstreamDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(true);
upstreamDisposableMetadatas.forEach((key, value) -> {
LOG.info("Upstream Disposable Metadata (Key-Value): {} : {}", key, value);
});
ret.put("backend-upstream-disposable-metadata", upstreamDisposableMetadatas);
// Get All Disposable metadata from upstream service
Map<String, String> localDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(false);
localDisposableMetadatas.forEach((key, value) -> {
LOG.info("Local Custom Disposable Metadata (Key-Value): {} : {}", key, value);
});
ret.put("backend-local-disposable-metadata", localDisposableMetadatas);
return ret;
}
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.callee;
package com.tencent.cloud.metadata.service.backend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -26,10 +26,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @author Palmer Xu
*/
@SpringBootApplication
public class MetadataCalleeService {
public class MetadataBackendService {
public static void main(String[] args) {
SpringApplication.run(MetadataCalleeService.class, args);
SpringApplication.run(MetadataBackendService.class, args);
}
}

@ -1,8 +1,8 @@
server:
port: 48084
port: 48088
spring:
application:
name: MetadataCalleeService
name: MetadataBackendService
cloud:
polaris:
address: grpc://183.47.111.80:8091
@ -11,3 +11,10 @@ spring:
discovery:
enabled: true
register: true
management:
endpoints:
web:
exposure:
include:
- polaris-metadata

@ -1,109 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.caller;
import java.util.Map;
import com.google.common.collect.Maps;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* Metadata caller controller.
*
* @author Palmer Xu
*/
@RestController
@RequestMapping("/metadata/service/caller")
public class MetadataCallerController {
private final RestTemplate restTemplate;
private final MetadataCalleeService metadataCalleeService;
private final MetadataLocalProperties metadataLocalProperties;
public MetadataCallerController(RestTemplate restTemplate,
MetadataCalleeService metadataCalleeService,
MetadataLocalProperties metadataLocalProperties) {
this.restTemplate = restTemplate;
this.metadataCalleeService = metadataCalleeService;
this.metadataLocalProperties = metadataLocalProperties;
}
/**
* Get metadata info from remote service.
* @return metadata map
*/
@GetMapping("/feign/info")
public Map<String, Map<String, String>> feign() {
Map<String, Map<String, String>> ret = Maps.newHashMap();
// Call remote service with feign client
Map<String, String> calleeMetadata = metadataCalleeService.info();
ret.put("callee-transitive-metadata", calleeMetadata);
// Get Custom Metadata From Context
MetadataContext context = MetadataContextHolder.get();
Map<String, String> callerTransitiveMetadata = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
ret.put("caller-transitive-metadata", callerTransitiveMetadata);
ret.put("caller-metadata-contents", metadataLocalProperties.getContent());
return ret;
}
/**
* Get metadata information of callee.
* @return information of callee
*/
@SuppressWarnings("unchecked")
@GetMapping("/rest/info")
public Map<String, Map<String, String>> rest() {
Map<String, Map<String, String>> ret = Maps.newHashMap();
// Call remote service with RestTemplate
Map<String, String> calleeMetadata = restTemplate.getForObject(
"http://MetadataCalleeService/metadata/service/callee/info",
Map.class);
ret.put("callee-transitive-metadata", calleeMetadata);
// Get Custom Metadata From Context
MetadataContext context = MetadataContextHolder.get();
Map<String, String> callerTransitiveMetadata = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
ret.put("caller-transitive-metadata", callerTransitiveMetadata);
ret.put("caller-metadata-contents", metadataLocalProperties.getContent());
return ret;
}
/**
* health check.
* @return health check info
*/
@GetMapping("/healthCheck")
public String healthCheck() {
return "pk ok";
}
}

@ -0,0 +1,51 @@
<?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>metadata-transfer-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>metadata-frontend</artifactId>
<name>Spring Cloud Tencent Metadata Transfer Frontent Service</name>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,155 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.frontend;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* Metadata caller controller.
*
* @author Palmer Xu
*/
@RestController
@RequestMapping("/metadata/service/frontend")
public class MetadataFrontendController {
private static final Logger LOG = LoggerFactory.getLogger(MetadataFrontendController.class);
private final RestTemplate restTemplate;
private final MetadataMiddleService metadataMiddleService;
public MetadataFrontendController(RestTemplate restTemplate,
MetadataMiddleService metadataMiddleService) {
this.restTemplate = restTemplate;
this.metadataMiddleService = metadataMiddleService;
}
/**
* Get metadata info from remote service.
*
* @return metadata map
*/
@GetMapping("/feign/info")
public Map<String, Map<String, String>> feign() {
Map<String, Map<String, String>> ret = new HashMap<>();
// Call remote service with feign client
Map<String, Map<String, String>> middleResult = metadataMiddleService.info();
if (middleResult != null) {
ret.putAll(middleResult);
}
// Get Custom Metadata From Context
MetadataContext context = MetadataContextHolder.get();
Map<String, String> customMetadataMap = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
customMetadataMap.forEach((key, value) -> {
LOG.info("Metadata Middle Custom Metadata (Key-Value): {} : {}", key, value);
});
ret.put("frontend-transitive-metadata", customMetadataMap);
// Get All Disposable metadata from upstream service
Map<String, String> upstreamDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(true);
upstreamDisposableMetadatas.forEach((key, value) -> {
LOG.info("Upstream Custom Disposable Metadata (Key-Value): {} : {}", key, value);
});
ret.put("frontend-upstream-disposable-metadata", upstreamDisposableMetadatas);
// Get All Disposable metadata from upstream service
Map<String, String> localDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(false);
localDisposableMetadatas.forEach((key, value) -> {
LOG.info("Local Custom Disposable Metadata (Key-Value): {} : {}", key, value);
});
ret.put("frontend-local-disposable-metadata", localDisposableMetadatas);
return ret;
}
/**
* Get metadata information of callee.
*
* @return information of callee
*/
@SuppressWarnings("unchecked")
@GetMapping("/rest/info")
public Map<String, Map<String, String>> rest() {
Map<String, Map<String, String>> ret = new HashMap<>();
// Call remote service with RestTemplate
Map<String, Map<String, String>> middleResult = restTemplate.getForObject(
"http://MetadataMiddleService/metadata/service/middle/info", Map.class);
if (middleResult != null) {
ret.putAll(middleResult);
}
// Get Custom Metadata From Context
MetadataContext context = MetadataContextHolder.get();
Map<String, String> customMetadataMap = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
customMetadataMap.forEach((key, value) -> {
LOG.info("Metadata Middle Custom Metadata (Key-Value): {} : {}", key, value);
});
ret.put("frontend-transitive-metadata", customMetadataMap);
// Get All Disposable metadata from upstream service
Map<String, String> upstreamDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(true);
upstreamDisposableMetadatas.forEach((key, value) -> {
LOG.info("Upstream Custom Disposable Metadata (Key-Value): {} : {}", key, value);
});
ret.put("frontend-upstream-disposable-metadata", upstreamDisposableMetadatas);
// Get All Disposable metadata from upstream service
Map<String, String> localDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(false);
localDisposableMetadatas.forEach((key, value) -> {
LOG.info("Local Custom Disposable Metadata (Key-Value): {} : {}", key, value);
});
ret.put("frontend-local-disposable-metadata", localDisposableMetadatas);
return ret;
}
/**
* health check.
*
* @return health check info
*/
@GetMapping("/healthCheck")
public String healthCheck() {
return "pk ok";
}
}

@ -15,11 +15,10 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.caller;
package com.tencent.cloud.metadata.service.frontend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
@ -31,12 +30,11 @@ import org.springframework.web.client.RestTemplate;
* @author Palmer Xu
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class MetadataCallerService {
public class MetadataFrontendService {
public static void main(String[] args) {
SpringApplication.run(MetadataCallerService.class, args);
SpringApplication.run(MetadataFrontendService.class, args);
}
@Bean
@ -44,5 +42,4 @@ public class MetadataCallerService {
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.caller;
package com.tencent.cloud.metadata.service.frontend;
import java.util.Map;
@ -27,15 +27,14 @@ import org.springframework.web.bind.annotation.GetMapping;
*
* @author Palmer Xu
*/
@FeignClient(value = "MetadataCalleeService",
fallback = MetadataCalleeServiceFallback.class)
public interface MetadataCalleeService {
@FeignClient(value = "MetadataMiddleService",
fallback = MetadataMiddleServiceFallback.class)
public interface MetadataMiddleService {
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/metadata/service/callee/info")
Map<String, String> info();
@GetMapping("/metadata/service/middle/info")
Map<String, Map<String, String>> info();
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.caller;
package com.tencent.cloud.metadata.service.frontend;
import java.util.Map;
@ -29,11 +29,10 @@ import org.springframework.stereotype.Component;
* @author Palmer Xu
*/
@Component
public class MetadataCalleeServiceFallback implements MetadataCalleeService {
public class MetadataMiddleServiceFallback implements MetadataMiddleService {
@Override
public Map<String, String> info() {
public Map<String, Map<String, String>> info() {
return Maps.newHashMap();
}
}

@ -2,7 +2,7 @@ server:
port: 48080
spring:
application:
name: MetadataCallerService
name: MetadataFrontendService
cloud:
polaris:
address: grpc://183.47.111.80:8091
@ -12,18 +12,22 @@ spring:
enabled: true
register: true
heartbeat-enabled: true
health-check-url: /metadata/service/caller/healthCheck
health-check-url: /metadata/service/frontend/healthCheck
tencent:
metadata:
# Defined your metadata keys & values
content:
# Example: intransitive
CUSTOM-METADATA-KEY-LOCAL: CUSTOM-VALUE-LOCAL
# Example: disposable
CUSTOM-METADATA-KEY-DISPOSABLE: CUSTOM-VALUE-DISPOSABLE
# Example: transitive
CUSTOM-METADATA-KEY-TRANSITIVE: CUSTOM-VALUE-TRANSITIVE
CUSTOM-METADATA-KEY-TRANSITIVE: CUSTOM-VALUE-TRANSITIVE-FRONTEND
# Assigned which metadata key-value will be passed along the link
transitive:
- CUSTOM-METADATA-KEY-TRANSITIVE
disposable:
- CUSTOM-METADATA-KEY-DISPOSABLE
management:
endpoints:
web:

@ -10,8 +10,8 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>metadata-callee-service</artifactId>
<name>Spring Cloud Tencent Metadata Transfer Callee Service</name>
<artifactId>metadata-middle</artifactId>
<name>Spring Cloud Tencent Metadata Transfer Middle Service</name>
<dependencies>
<dependency>

@ -0,0 +1,40 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.middle;
import java.util.Map;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Metadata callee feign client.
*
* @author Palmer Xu
*/
@FeignClient(value = "MetadataBackendService",
fallback = MetadataBackendServiceFallback.class)
public interface MetadataBackendService {
/**
* Get information of callee.
* @return information of callee
*/
@GetMapping("/metadata/service/backend/info")
Map<String, Map<String, String>> info();
}

@ -0,0 +1,38 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.middle;
import java.util.Map;
import com.google.common.collect.Maps;
import org.springframework.stereotype.Component;
/**
* Metadata callee feign client fallback.
*
* @author Palmer Xu
*/
@Component
public class MetadataBackendServiceFallback implements MetadataBackendService {
@Override
public Map<String, Map<String, String>> info() {
return Maps.newHashMap();
}
}

@ -0,0 +1,122 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.middle;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import static com.tencent.cloud.common.util.JacksonUtils.serialize2Json;
/**
* Metadata callee controller.
*
* @author Palmer Xu
*/
@RestController
@RequestMapping("/metadata/service/middle")
public class MetadataMiddleController {
private static final Logger LOG = LoggerFactory.getLogger(MetadataMiddleController.class);
@Value("${server.port:0}")
private int port;
private final MetadataBackendService metadataBackendService;
private final RestTemplate restTemplate;
public MetadataMiddleController(MetadataBackendService metadataBackendService, RestTemplate restTemplate) {
this.metadataBackendService = metadataBackendService;
this.restTemplate = restTemplate;
}
/**
* Get information of callee.
*
* @return information of callee
*/
@GetMapping("/info")
public Map<String, Map<String, String>> info() {
// Build result
Map<String, Map<String, String>> ret = new HashMap<>();
LOG.info("Metadata Middle Service [{}] is called.", port);
// Call remote service with RestTemplate
Map<String, Map<String, String>> backendResult = restTemplate.getForObject(
"http://MetadataBackendService/metadata/service/backend/info", Map.class);
if (backendResult != null) {
LOG.info("RestTemplate Backend Metadata");
LOG.info("\r{}", serialize2Json(backendResult, true));
backendResult.clear();
}
// Call remote service with Feign
backendResult = metadataBackendService.info();
if (backendResult != null) {
LOG.info("Feign Backend Metadata");
LOG.info("\r{}", serialize2Json(backendResult, true));
}
if (backendResult != null) {
ret.putAll(backendResult);
}
// Get Custom Metadata From Context
MetadataContext context = MetadataContextHolder.get();
Map<String, String> customMetadataMap = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
customMetadataMap.forEach((key, value) -> {
LOG.info("Metadata Middle Custom Metadata (Key-Value): {} : {}", key, value);
});
ret.put("middle-transitive-metadata", customMetadataMap);
// Get All Disposable metadata from upstream service
Map<String, String> upstreamDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(true);
upstreamDisposableMetadatas.forEach((key, value) -> {
LOG.info("Upstream Custom Disposable Metadata (Key-Value): {} : {}", key, value);
});
ret.put("middle-upstream-disposable-metadata", upstreamDisposableMetadatas);
// Get All Disposable metadata from upstream service
Map<String, String> localDisposableMetadatas = MetadataContextHolder.getAllDisposableMetadata(false);
localDisposableMetadatas.forEach((key, value) -> {
LOG.info("Local Custom Disposable Metadata (Key-Value): {} : {}", key, value);
});
ret.put("middle-local-disposable-metadata", localDisposableMetadatas);
return ret;
}
}

@ -0,0 +1,46 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.metadata.service.middle;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* Metadata callee application.
*
* @author Palmer Xu
*/
@SpringBootApplication
@EnableFeignClients
public class MetadataMiddleService {
public static void main(String[] args) {
SpringApplication.run(MetadataMiddleService.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

@ -0,0 +1,35 @@
server:
port: 48084
spring:
application:
name: MetadataMiddleService
cloud:
polaris:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
discovery:
enabled: true
register: true
tencent:
metadata:
# Defined your metadata keys & values
content:
# Example: intransitive
CUSTOM-METADATA-KEY-LOCAL-2: CUSTOM-VALUE-LOCAL-2
# Example: transitive
CUSTOM-METADATA-KEY-TRANSITIVE-2: CUSTOM-VALUE-TRANSITIVE-2
# Example: disposable
CUSTOM-METADATA-KEY-DISPOSABLE: CUSTOM-VALUE-DISPOSABLE-MIDDLE
# Assigned which metadata key-value will be passed along the link
transitive:
- CUSTOM-METADATA-KEY-TRANSITIVE-2
disposable:
- CUSTOM-METADATA-KEY-DISPOSABLE
management:
endpoints:
web:
exposure:
include:
- polaris-metadata

@ -15,8 +15,9 @@
<name>Spring Cloud Starter Tencent Metadata Transfer Example</name>
<modules>
<module>metadata-callee-service</module>
<module>metadata-caller-service</module>
<module>metadata-frontend</module>
<module>metadata-middle</module>
<module>metadata-backend</module>
</modules>
<dependencies>

@ -10,8 +10,19 @@ spring:
enabled: true
circuitbreaker:
enabled: true
stat:
enabled: true
port: 28081
loadbalancer:
configurations: polaris
tencent:
rpc-enhancement:
enabled: true
reporter:
ignore-internal-server-error: true
series: server_error
statuses: gateway_timeout, bad_gateway, service_unavailable
feign:
circuitbreaker:
enabled: true

@ -8,3 +8,6 @@ spring:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
stat:
enabled: true
port: 28082

@ -8,3 +8,6 @@ spring:
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
stat:
enabled: true
port: 28083

@ -11,6 +11,9 @@ spring:
discovery:
enabled: true
register: true
stat:
enabled: true
port: 28082
tencent:
metadata:
content:

@ -19,6 +19,9 @@ spring:
heartbeat:
enabled: true
health-check-url: /discovery/service/caller/healthCheck
stat:
enabled: true
port: 28081
# consul:
# port: 8500
# host: 127.0.0.1
@ -41,4 +44,4 @@ management:
web:
exposure:
include:
- polaris-discovery
- polaris-discovery

@ -27,6 +27,33 @@
</dependency>
<!-- Polaris dependencies start -->
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-discovery-factory</artifactId>
<exclusions>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-rule</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-metadata</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-canary</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-set</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-client</artifactId>
@ -62,17 +89,6 @@
<artifactId>flow-cache-expired</artifactId>
</dependency>
<!-- Discovery depended on base routers -->
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-isolated</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-healthy</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>loadbalancer-random</artifactId>

@ -21,8 +21,11 @@ package com.tencent.cloud.polaris.context.config;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.ModifyAddress;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.core.ProviderAPI;
import com.tencent.polaris.api.exception.PolarisException;
import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.factory.api.DiscoveryAPIFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -46,6 +49,18 @@ public class PolarisContextAutoConfiguration {
return SDKContext.initContextByConfig(properties.configuration());
}
@Bean
@ConditionalOnMissingBean
public ProviderAPI polarisProvider(SDKContext polarisContext) throws PolarisException {
return DiscoveryAPIFactory.createProviderAPIByContext(polarisContext);
}
@Bean
@ConditionalOnMissingBean
public ConsumerAPI polarisConsumer(SDKContext polarisContext) throws PolarisException {
return DiscoveryAPIFactory.createConsumerAPIByContext(polarisContext);
}
@Bean
@ConditionalOnMissingBean
public ModifyAddress polarisConfigModifier() {

@ -0,0 +1,56 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.context.logging;
import com.tencent.polaris.logging.PolarisLogging;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.logging.LoggingApplicationListener;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.GenericApplicationListener;
import org.springframework.core.ResolvableType;
/**
* Reload of Polaris logging configuration.
*
* @author Haotian Zhang
*/
public class PolarisLoggingApplicationListener implements GenericApplicationListener {
private static final int ORDER = LoggingApplicationListener.DEFAULT_ORDER + 2;
@Override
public boolean supportsEventType(ResolvableType resolvableType) {
Class<?> type = resolvableType.getRawClass();
if (type == null) {
return false;
}
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(type)
|| ApplicationFailedEvent.class.isAssignableFrom(type);
}
@Override
public int getOrder() {
return ORDER;
}
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
PolarisLogging.getInstance().loadConfiguration();
}
}

@ -3,3 +3,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.context.config.PolarisContextPostConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.context.config.PolarisContextBootstrapAutoConfiguration
org.springframework.context.ApplicationListener=\
com.tencent.cloud.polaris.context.logging.PolarisLoggingApplicationListener

@ -0,0 +1,126 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.loadbalancer;
import java.util.ArrayList;
import java.util.List;
import com.tencent.cloud.common.pojo.PolarisServiceInstance;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceResponse;
import org.assertj.core.api.Assertions;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_NAMESPACE;
import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_SERVICE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Test for {@link PolarisLoadBalancer}.
*
* @author rod.xu
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisLoadBalancerTest {
@Mock
private RouterAPI routerAPI;
@Mock
private ObjectProvider<ServiceInstanceListSupplier> supplierObjectProvider;
@Mock
private PolarisLoadBalancerProperties loadBalancerProperties;
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static Instance testInstance;
@BeforeClass
public static void beforeClass() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
testInstance = Instance.createDefaultInstance("instance-id", LOCAL_NAMESPACE,
LOCAL_SERVICE, "host", 8090);
}
@AfterClass
public static void afterClass() {
mockedApplicationContextAwareUtils.close();
}
@Test
public void chooseNormalLogicTest_thenReturnAvailablePolarisInstance() {
Request request = Mockito.mock(Request.class);
List<ServiceInstance> mockInstanceList = new ArrayList<>();
mockInstanceList.add(new PolarisServiceInstance(testInstance));
ServiceInstanceListSupplier serviceInstanceListSupplier = Mockito.mock(ServiceInstanceListSupplier.class);
when(serviceInstanceListSupplier.get(request)).thenReturn(Flux.just(mockInstanceList));
when(supplierObjectProvider.getIfAvailable(any())).thenReturn(serviceInstanceListSupplier);
when(loadBalancerProperties.getEnabled()).thenReturn(true);
ProcessLoadBalanceResponse mockLbRes = new ProcessLoadBalanceResponse(testInstance);
when(routerAPI.processLoadBalance(any())).thenReturn(mockLbRes);
// request construct and execute invoke
PolarisLoadBalancer polarisLoadBalancer = new PolarisLoadBalancer(LOCAL_SERVICE, supplierObjectProvider,
loadBalancerProperties, routerAPI);
Mono<Response<ServiceInstance>> responseMono = polarisLoadBalancer.choose(request);
ServiceInstance serviceInstance = responseMono.block().getServer();
// verify method has invoked
verify(loadBalancerProperties).getEnabled();
verify(supplierObjectProvider).getIfAvailable(any());
//result assert
Assertions.assertThat(serviceInstance).isNotNull();
Assertions.assertThat(serviceInstance instanceof PolarisServiceInstance).isTrue();
PolarisServiceInstance polarisServiceInstance = (PolarisServiceInstance) serviceInstance;
Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getId()).isEqualTo("instance-id");
Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getNamespace()).isEqualTo(LOCAL_NAMESPACE);
Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getService()).isEqualTo(LOCAL_SERVICE);
Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getHost()).isEqualTo("host");
Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getPort()).isEqualTo(8090);
}
}

@ -0,0 +1,89 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.loadbalancer;
import java.util.ArrayList;
import java.util.List;
import com.tencent.cloud.common.pojo.PolarisServiceInstance;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_NAMESPACE;
import static org.mockito.ArgumentMatchers.anyString;
/**
* Test for {@link PolarisServiceInstanceListSupplier}.
*
* @author rod.xu
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisServiceInstanceListSupplierTest {
@Mock
private ServiceInstanceListSupplier serviceInstanceListSupplier;
@Test
public void chooseInstancesTest() {
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("test-unit");
PolarisServiceInstanceListSupplier instanceListSupplier =
new PolarisServiceInstanceListSupplier(serviceInstanceListSupplier);
List<ServiceInstance> allServers = new ArrayList<>();
ServiceInstance instance1 = new DefaultServiceInstance("unit-test-instanceId-01",
"unit-test-serviceId", "unit-test-host-01", 8090, false);
ServiceInstance instance2 = new DefaultServiceInstance("unit-test-instanceId-02",
"unit-test-serviceId", "unit-test-host-02", 8090, false);
allServers.add(instance1);
allServers.add(instance2);
List<ServiceInstance> polarisInstanceList = instanceListSupplier.chooseInstances(allServers);
Assertions.assertThat(polarisInstanceList).isNotNull();
Assertions.assertThat(polarisInstanceList.size()).isEqualTo(allServers.size());
for (ServiceInstance serviceInstance : polarisInstanceList) {
Assertions.assertThat(serviceInstance instanceof PolarisServiceInstance).isTrue();
PolarisServiceInstance polarisServiceInstance = (PolarisServiceInstance) serviceInstance;
Assertions.assertThat(polarisServiceInstance.isSecure()).isFalse();
Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getService()).isEqualTo("unit-test-serviceId");
Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getNamespace()).isEqualTo(LOCAL_NAMESPACE);
Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getPort()).isEqualTo(8090);
Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getId().startsWith("unit-test-instanceId")).isTrue();
Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getHost().startsWith("unit-test-host")).isTrue();
}
}
}
}

@ -0,0 +1,62 @@
<?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</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
<name>Spring Cloud Starter Tencent RPC Enhancement</name>
<dependencies>
<!-- Spring Cloud Tencent dependencies start -->
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>
</dependency>
<!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start -->
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>stat-prometheus</artifactId>
</dependency>
<!-- Polaris dependencies end -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-test-common</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</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,119 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import static org.springframework.http.HttpStatus.BAD_GATEWAY;
import static org.springframework.http.HttpStatus.BANDWIDTH_LIMIT_EXCEEDED;
import static org.springframework.http.HttpStatus.GATEWAY_TIMEOUT;
import static org.springframework.http.HttpStatus.HTTP_VERSION_NOT_SUPPORTED;
import static org.springframework.http.HttpStatus.INSUFFICIENT_STORAGE;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.LOOP_DETECTED;
import static org.springframework.http.HttpStatus.NETWORK_AUTHENTICATION_REQUIRED;
import static org.springframework.http.HttpStatus.NOT_EXTENDED;
import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED;
import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE;
import static org.springframework.http.HttpStatus.VARIANT_ALSO_NEGOTIATES;
/**
* Abstract Polaris Reporter Adapter .
*
* @author <a href="mailto:iskp.me@gmail.com">Elve.Xu</a> 2022-07-11
*/
public abstract class AbstractPolarisReporterAdapter {
private static final Logger LOG = LoggerFactory.getLogger(AbstractPolarisReporterAdapter.class);
private static final List<HttpStatus> HTTP_STATUSES = toList(NOT_IMPLEMENTED, BAD_GATEWAY,
SERVICE_UNAVAILABLE, GATEWAY_TIMEOUT, HTTP_VERSION_NOT_SUPPORTED, VARIANT_ALSO_NEGOTIATES,
INSUFFICIENT_STORAGE, LOOP_DETECTED, BANDWIDTH_LIMIT_EXCEEDED, NOT_EXTENDED, NETWORK_AUTHENTICATION_REQUIRED);
protected final RpcEnhancementReporterProperties properties;
/**
* Constructor With {@link RpcEnhancementReporterProperties} .
*
* @param properties instance of {@link RpcEnhancementReporterProperties}.
*/
protected AbstractPolarisReporterAdapter(RpcEnhancementReporterProperties properties) {
this.properties = properties;
}
/**
* Convert items to List.
*
* @param items item arrays
* @param <T> Object Generics.
* @return list
*/
@SafeVarargs
private static <T> List<T> toList(T... items) {
return new ArrayList<>(Arrays.asList(items));
}
/**
* Callback after completion of request processing, Check if business meltdown reporting is required.
*
* @param httpStatus request http status code
* @return true , otherwise return false .
*/
protected boolean apply(@Nullable HttpStatus httpStatus) {
if (Objects.isNull(httpStatus)) {
return false;
}
else {
// statuses > series
List<HttpStatus> status = properties.getStatuses();
if (status.isEmpty()) {
List<HttpStatus.Series> series = properties.getSeries();
// Check INTERNAL_SERVER_ERROR (500) status.
if (properties.isIgnoreInternalServerError() && Objects.equals(httpStatus, INTERNAL_SERVER_ERROR)) {
return false;
}
if (series.isEmpty()) {
return HTTP_STATUSES.contains(httpStatus);
}
else {
try {
return series.contains(HttpStatus.Series.valueOf(httpStatus));
}
catch (Exception e) {
LOG.warn("Decode http status failed.", e);
}
}
}
else {
// Use the user-specified fuse status code.
return status.contains(httpStatus);
}
}
// DEFAULT RETURN FALSE.
return false;
}
}

@ -0,0 +1,113 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.config;
import java.util.List;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignBeanPostProcessor;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.ExceptionPolarisReporter;
import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.SuccessPolarisReporter;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateModifier;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter;
import com.tencent.cloud.rpc.enhancement.resttemplate.PolarisResponseErrorHandler;
import com.tencent.polaris.api.core.ConsumerAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.client.RestTemplate;
import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
/**
* Auto Configuration for Polaris {@link feign.Feign} OR {@link RestTemplate} which can automatically bring in the call
* results for reporting.
*
* @author <a href="mailto:iskp.me@gmail.com">Palmer.Xu</a> 2022-06-29
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(RpcEnhancementReporterProperties.class)
@AutoConfigureAfter(PolarisContextAutoConfiguration.class)
public class RpcEnhancementAutoConfiguration {
/**
* Configuration for Polaris {@link feign.Feign} which can automatically bring in the call
* results for reporting.
*
* @author Haotian Zhang
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.cloud.openfeign.FeignAutoConfiguration")
@AutoConfigureBefore(name = "org.springframework.cloud.openfeign.FeignAutoConfiguration")
protected static class PolarisFeignClientAutoConfiguration {
@Bean
@Order(HIGHEST_PRECEDENCE)
public EnhancedFeignBeanPostProcessor polarisFeignBeanPostProcessor(
@Autowired(required = false) List<EnhancedFeignPlugin> enhancedFeignPlugins) {
return new EnhancedFeignBeanPostProcessor(enhancedFeignPlugins);
}
@Configuration
static class PolarisReporterConfig {
@Bean
public SuccessPolarisReporter successPolarisReporter(RpcEnhancementReporterProperties properties) {
return new SuccessPolarisReporter(properties);
}
@Bean
public ExceptionPolarisReporter exceptionPolarisReporter() {
return new ExceptionPolarisReporter();
}
}
}
/**
* Configuration for Polaris {@link RestTemplate} which can automatically bring in the call
* results for reporting.
*
* @author wh 2022/6/21
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
protected static class PolarisRestTemplateAutoConfiguration {
@Bean
public EnhancedRestTemplateReporter polarisRestTemplateResponseErrorHandler(
RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI,
@Autowired(required = false) PolarisResponseErrorHandler polarisResponseErrorHandler) {
return new EnhancedRestTemplateReporter(properties, consumerAPI, polarisResponseErrorHandler);
}
@Bean
public EnhancedRestTemplateModifier polarisRestTemplateBeanPostProcessor(
EnhancedRestTemplateReporter restTemplateResponseErrorHandler) {
return new EnhancedRestTemplateModifier(restTemplateResponseErrorHandler);
}
}
}

@ -0,0 +1,86 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpStatus;
/**
* Properties of Polaris CircuitBreaker .
*
* @author <a href="mailto:iskp.me@gmail.com">Elve.Xu</a> 2022-07-08
*/
@ConfigurationProperties("spring.cloud.tencent.rpc-enhancement.reporter")
public class RpcEnhancementReporterProperties {
/**
* Specify the Http status code(s) that needs to be reported as FAILED.
*/
private List<HttpStatus> statuses = new ArrayList<>();
/**
* Specify List of HTTP status series that needs to be reported as FAILED when status list is empty.
*/
private List<HttpStatus.Series> series = toList(HttpStatus.Series.SERVER_ERROR);
/**
* If ignore "Internal Server Error Http Status Code (500)",
* Only takes effect if the attribute {@link RpcEnhancementReporterProperties#series} is not empty.
*/
private boolean ignoreInternalServerError = true;
/**
* Convert items to List.
*
* @param items item arrays
* @param <T> Object Generics.
* @return list
*/
@SafeVarargs
private static <T> List<T> toList(T... items) {
return new ArrayList<>(Arrays.asList(items));
}
public List<HttpStatus> getStatuses() {
return statuses;
}
public void setStatuses(List<HttpStatus> statuses) {
this.statuses = statuses;
}
public List<HttpStatus.Series> getSeries() {
return series;
}
public void setSeries(List<HttpStatus.Series> series) {
this.series = series;
}
public boolean isIgnoreInternalServerError() {
return ignoreInternalServerError;
}
public void setIgnoreInternalServerError(boolean ignoreInternalServerError) {
this.ignoreInternalServerError = ignoreInternalServerError;
}
}

@ -15,9 +15,11 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.rpc.enhancement.feign;
import com.tencent.polaris.api.core.ConsumerAPI;
import java.util.List;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
import feign.Client;
import org.springframework.beans.BeansException;
@ -36,14 +38,14 @@ import org.springframework.lang.NonNull;
*
* @author Haotian Zhang
*/
public class PolarisFeignBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
public class EnhancedFeignBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private final ConsumerAPI consumerAPI;
private final List<EnhancedFeignPlugin> enhancedFeignPlugins;
private BeanFactory factory;
public PolarisFeignBeanPostProcessor(ConsumerAPI consumerAPI) {
this.consumerAPI = consumerAPI;
public EnhancedFeignBeanPostProcessor(List<EnhancedFeignPlugin> enhancedFeignPlugins) {
this.enhancedFeignPlugins = enhancedFeignPlugins;
}
@Override
@ -63,7 +65,7 @@ public class PolarisFeignBeanPostProcessor implements BeanPostProcessor, BeanFac
delegate = ((FeignBlockingLoadBalancerClient) bean).getDelegate();
}
if (delegate != null) {
return new PolarisFeignBlockingLoadBalancerClient(createPolarisFeignClient(delegate),
return new EnhancedFeignBlockingLoadBalancerClient(createPolarisFeignClient(delegate),
factory.getBean(BlockingLoadBalancerClient.class),
factory.getBean(LoadBalancerProperties.class),
factory.getBean(LoadBalancerClientFactory.class));
@ -75,12 +77,12 @@ public class PolarisFeignBeanPostProcessor implements BeanPostProcessor, BeanFac
}
private boolean isNeedWrap(Object bean) {
return bean instanceof Client && !(bean instanceof PolarisFeignClient)
&& !(bean instanceof PolarisFeignBlockingLoadBalancerClient);
return bean instanceof Client && !(bean instanceof EnhancedFeignClient)
&& !(bean instanceof EnhancedFeignBlockingLoadBalancerClient);
}
private PolarisFeignClient createPolarisFeignClient(Client delegate) {
return new PolarisFeignClient(delegate, consumerAPI);
private EnhancedFeignClient createPolarisFeignClient(Client delegate) {
return new EnhancedFeignClient(delegate, enhancedFeignPlugins);
}
@Override

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.rpc.enhancement.feign;
import feign.Client;
@ -29,9 +29,9 @@ import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalance
*
* @author Haotian Zhang
*/
public class PolarisFeignBlockingLoadBalancerClient extends FeignBlockingLoadBalancerClient {
public class EnhancedFeignBlockingLoadBalancerClient extends FeignBlockingLoadBalancerClient {
public PolarisFeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
public EnhancedFeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
super(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
}

@ -0,0 +1,153 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import feign.Client;
import feign.Request;
import feign.Request.Options;
import feign.Response;
import org.springframework.util.CollectionUtils;
import static feign.Util.checkNotNull;
/**
* Wrap for {@link Client}.
*
* @author Haotian Zhang
*/
public class EnhancedFeignClient implements Client {
private final Client delegate;
private List<EnhancedFeignPlugin> preEnhancedFeignPlugins;
private List<EnhancedFeignPlugin> postEnhancedFeignPlugins;
private List<EnhancedFeignPlugin> exceptionEnhancedFeignPlugins;
private List<EnhancedFeignPlugin> finallyEnhancedFeignPlugins;
public EnhancedFeignClient(Client target, List<EnhancedFeignPlugin> enhancedFeignPlugins) {
this.delegate = checkNotNull(target, "target");
// Init the EnhancedFeignPlugins list.
this.preEnhancedFeignPlugins = new ArrayList<>();
this.postEnhancedFeignPlugins = new ArrayList<>();
this.exceptionEnhancedFeignPlugins = new ArrayList<>();
this.finallyEnhancedFeignPlugins = new ArrayList<>();
if (!CollectionUtils.isEmpty(enhancedFeignPlugins)) {
for (EnhancedFeignPlugin feignPlugin : enhancedFeignPlugins) {
if (feignPlugin.getType().equals(EnhancedFeignPluginType.PRE)) {
this.preEnhancedFeignPlugins.add(feignPlugin);
}
else if (feignPlugin.getType().equals(EnhancedFeignPluginType.POST)) {
this.postEnhancedFeignPlugins.add(feignPlugin);
}
else if (feignPlugin.getType().equals(EnhancedFeignPluginType.EXCEPTION)) {
this.exceptionEnhancedFeignPlugins.add(feignPlugin);
}
else if (feignPlugin.getType().equals(EnhancedFeignPluginType.FINALLY)) {
this.finallyEnhancedFeignPlugins.add(feignPlugin);
}
}
}
// Set the ordered enhanced feign plugins.
this.preEnhancedFeignPlugins = getSortedEnhancedFeignPlugin(this.preEnhancedFeignPlugins);
this.postEnhancedFeignPlugins = getSortedEnhancedFeignPlugin(this.postEnhancedFeignPlugins);
this.exceptionEnhancedFeignPlugins = getSortedEnhancedFeignPlugin(this.exceptionEnhancedFeignPlugins);
this.finallyEnhancedFeignPlugins = getSortedEnhancedFeignPlugin(this.finallyEnhancedFeignPlugins);
}
@Override
public Response execute(Request request, Options options) throws IOException {
EnhancedFeignContext enhancedFeignContext = new EnhancedFeignContext();
enhancedFeignContext.setRequest(request);
enhancedFeignContext.setOptions(options);
// Run pre enhanced feign plugins.
for (EnhancedFeignPlugin plugin : preEnhancedFeignPlugins) {
try {
plugin.run(enhancedFeignContext);
}
catch (Throwable throwable) {
plugin.handlerThrowable(enhancedFeignContext, throwable);
}
}
try {
Response response = delegate.execute(request, options);
enhancedFeignContext.setResponse(response);
// Run post enhanced feign plugins.
for (EnhancedFeignPlugin plugin : postEnhancedFeignPlugins) {
try {
plugin.run(enhancedFeignContext);
}
catch (Throwable throwable) {
plugin.handlerThrowable(enhancedFeignContext, throwable);
}
}
return response;
}
catch (IOException origin) {
enhancedFeignContext.setException(origin);
// Run exception enhanced feign plugins.
for (EnhancedFeignPlugin plugin : exceptionEnhancedFeignPlugins) {
try {
plugin.run(enhancedFeignContext);
}
catch (Throwable throwable) {
plugin.handlerThrowable(enhancedFeignContext, throwable);
}
}
throw origin;
}
finally {
// Run finally enhanced feign plugins.
for (EnhancedFeignPlugin plugin : finallyEnhancedFeignPlugins) {
try {
plugin.run(enhancedFeignContext);
}
catch (Throwable throwable) {
plugin.handlerThrowable(enhancedFeignContext, throwable);
}
}
}
}
/**
* Ascending, which means the lower order number, the earlier executing enhanced feign plugin.
*
* @return sorted feign pre plugin list
*/
private List<EnhancedFeignPlugin> getSortedEnhancedFeignPlugin(List<EnhancedFeignPlugin> preEnhancedFeignPlugins) {
return new ArrayList<>(preEnhancedFeignPlugins)
.stream()
.sorted(Comparator.comparing(EnhancedFeignPlugin::getOrder))
.collect(Collectors.toList());
}
}

@ -0,0 +1,69 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin;
import feign.Request;
import feign.Response;
/**
* Context used by EnhancedFeignPlugin.
*
* @author Haotian Zhang
*/
public class EnhancedFeignContext {
private Request request;
private Request.Options options;
private Response response;
private Exception exception;
public Request getRequest() {
return request;
}
public void setRequest(Request request) {
this.request = request;
}
public Request.Options getOptions() {
return options;
}
public void setOptions(Request.Options options) {
this.options = options;
}
public Response getResponse() {
return response;
}
public void setResponse(Response response) {
this.response = response;
}
public Exception getException() {
return exception;
}
public void setException(Exception exception) {
this.exception = exception;
}
}

@ -0,0 +1,62 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin;
import org.springframework.core.Ordered;
/**
* Pre plugin used by EnhancedFeignClient.
*
* @author Haotian Zhang
*/
public interface EnhancedFeignPlugin extends Ordered {
/**
* Get name of plugin.
*
* @return name
*/
default String getName() {
return this.getClass().getName();
}
/**
* Get type of plugin.
*
* @return {@link EnhancedFeignPluginType}
*/
EnhancedFeignPluginType getType();
/**
* Run the plugin.
*
* @param context context in enhanced feign client.
* @throws Throwable throwable thrown from run method.
*/
void run(EnhancedFeignContext context) throws Throwable;
/**
* Handler throwable from {@link EnhancedFeignPlugin#run(EnhancedFeignContext)}.
*
* @param context context in enhanced feign client.
* @param throwable throwable thrown from run method.
*/
default void handlerThrowable(EnhancedFeignContext context, Throwable throwable) {
}
}

@ -0,0 +1,46 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin;
/**
* Type of EnhancedFeignPlugin.
*
* @author Haotian Zhang
*/
public enum EnhancedFeignPluginType {
/**
* Pre feign plugin.
*/
PRE,
/**
* Post feign plugin.
*/
POST,
/**
* Exception feign plugin.
*/
EXCEPTION,
/**
* Finally feign plugin.
*/
FINALLY
}

@ -0,0 +1,85 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
import java.net.SocketTimeoutException;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import feign.Request;
import feign.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
/**
* Polaris reporter when feign call is successful.
*
* @author Haotian Zhang
*/
public class ExceptionPolarisReporter implements EnhancedFeignPlugin {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionPolarisReporter.class);
@Autowired(required = false)
private ConsumerAPI consumerAPI;
@Override
public String getName() {
return ExceptionPolarisReporter.class.getName();
}
@Override
public EnhancedFeignPluginType getType() {
return EnhancedFeignPluginType.EXCEPTION;
}
@Override
public void run(EnhancedFeignContext context) {
if (consumerAPI != null) {
Request request = context.getRequest();
Response response = context.getResponse();
Exception exception = context.getException();
RetStatus retStatus = RetStatus.RetFail;
if (exception instanceof SocketTimeoutException) {
retStatus = RetStatus.RetTimeout;
}
LOG.debug("Will report result of {}. Request=[{}]. Response=[{}].", retStatus.name(), request, response);
ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(request, retStatus);
consumerAPI.updateServiceCallResult(resultRequest);
}
}
@Override
public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) {
Request request = context.getRequest();
Response response = context.getResponse();
LOG.error("ExceptionPolarisReporter runs failed. Request=[{}]. Response=[{}].", request, response, throwable);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

@ -15,69 +15,28 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
import java.io.IOException;
import java.net.URI;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import feign.Client;
import feign.Request;
import feign.Request.Options;
import feign.Response;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static feign.Util.checkNotNull;
/**
* Wrap for {@link Client}.
* Util for polaris reporter.
*
* @author Haotian Zhang
*/
public class PolarisFeignClient implements Client {
private static final Logger LOG = LoggerFactory.getLogger(PolarisFeignClient.class);
private final Client delegate;
private final ConsumerAPI consumerAPI;
public final class ReporterUtils {
public PolarisFeignClient(Client target, ConsumerAPI consumerAPI) {
this.delegate = checkNotNull(target, "target");
this.consumerAPI = checkNotNull(consumerAPI, "CircuitBreakAPI");
}
@Override
public Response execute(Request request, Options options) throws IOException {
final ServiceCallResult resultRequest = createServiceCallResult(request);
try {
Response response = delegate.execute(request, options);
// HTTP code greater than 500 is an exception
if (response.status() > 500) {
resultRequest.setRetStatus(RetStatus.RetFail);
}
LOG.debug("Will report result of {}. Request=[{}]. Response=[{}].",
resultRequest.getRetStatus().name(), request, response);
return response;
}
catch (IOException origin) {
resultRequest.setRetStatus(RetStatus.RetFail);
LOG.debug("Will report result of {}. Request=[{}].", resultRequest.getRetStatus().name(), request, origin);
throw origin;
}
finally {
consumerAPI.updateServiceCallResult(resultRequest);
}
private ReporterUtils() {
}
private ServiceCallResult createServiceCallResult(final Request request) {
public static ServiceCallResult createServiceCallResult(final Request request, RetStatus retStatus) {
ServiceCallResult resultRequest = new ServiceCallResult();
resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE);
@ -85,7 +44,7 @@ public class PolarisFeignClient implements Client {
resultRequest.setService(serviceName);
URI uri = URI.create(request.url());
resultRequest.setMethod(uri.getPath());
resultRequest.setRetStatus(RetStatus.RetSuccess);
resultRequest.setRetStatus(retStatus);
String sourceNamespace = MetadataContext.LOCAL_NAMESPACE;
String sourceService = MetadataContext.LOCAL_SERVICE;
if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) {

@ -0,0 +1,88 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import feign.Request;
import feign.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
/**
* Polaris reporter when feign call is successful.
*
* @author Haotian Zhang
*/
public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedFeignPlugin {
private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class);
@Autowired(required = false)
private ConsumerAPI consumerAPI;
public SuccessPolarisReporter(RpcEnhancementReporterProperties properties) {
super(properties);
}
@Override
public String getName() {
return SuccessPolarisReporter.class.getName();
}
@Override
public EnhancedFeignPluginType getType() {
return EnhancedFeignPluginType.POST;
}
@Override
public void run(EnhancedFeignContext context) {
if (consumerAPI != null) {
Request request = context.getRequest();
Response response = context.getResponse();
RetStatus retStatus = RetStatus.RetSuccess;
if (apply(HttpStatus.resolve(response.status()))) {
retStatus = RetStatus.RetFail;
}
LOG.debug("Will report result of {}. Request=[{}]. Response=[{}].", retStatus.name(), request, response);
ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(request, retStatus);
consumerAPI.updateServiceCallResult(resultRequest);
}
}
@Override
public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) {
Request request = context.getRequest();
Response response = context.getResponse();
LOG.error("SuccessPolarisReporter runs failed. Request=[{}]. Response=[{}].", request, response, throwable);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
package com.tencent.cloud.rpc.enhancement.resttemplate;
import java.util.Map;
@ -28,19 +28,19 @@ import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestTemplate;
/**
* Auto configuration RestTemplate, Find the RestTemplate bean annotated with {@link LoadBalanced},
* Autoconfiguration RestTemplate, Find the RestTemplate bean annotated with {@link LoadBalanced},
* then replace {@link org.springframework.web.client.ResponseErrorHandler}
* with {@link PolarisRestTemplateResponseErrorHandler}.
* with {@link EnhancedRestTemplateReporter} .
*
* @author wh 2022/6/21
*/
public class PolarisRestTemplateModifier implements ApplicationContextAware, SmartInitializingSingleton {
public class EnhancedRestTemplateModifier implements ApplicationContextAware, SmartInitializingSingleton {
private final PolarisRestTemplateResponseErrorHandler polarisRestTemplateResponseErrorHandler;
private final EnhancedRestTemplateReporter enhancedRestTemplateReporter;
private ApplicationContext applicationContext;
public PolarisRestTemplateModifier(PolarisRestTemplateResponseErrorHandler polarisRestTemplateResponseErrorHandler) {
this.polarisRestTemplateResponseErrorHandler = polarisRestTemplateResponseErrorHandler;
public EnhancedRestTemplateModifier(EnhancedRestTemplateReporter enhancedRestTemplateReporter) {
this.enhancedRestTemplateReporter = enhancedRestTemplateReporter;
}
@Override
@ -54,7 +54,7 @@ public class PolarisRestTemplateModifier implements ApplicationContextAware, Sma
private void initRestTemplate(String beanName, Object bean) {
if (bean instanceof RestTemplate) {
RestTemplate restTemplate = (RestTemplate) bean;
restTemplate.setErrorHandler(polarisRestTemplateResponseErrorHandler);
restTemplate.setErrorHandler(enhancedRestTemplateReporter);
}
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
package com.tencent.cloud.rpc.enhancement.resttemplate;
import java.io.IOException;
import java.net.HttpURLConnection;
@ -25,6 +25,8 @@ import java.util.Objects;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ReflectionUtils;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey;
@ -43,9 +45,9 @@ import org.springframework.web.client.ResponseErrorHandler;
*
* @author wh 2022/6/21
*/
public class PolarisRestTemplateResponseErrorHandler implements ResponseErrorHandler {
public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter implements ResponseErrorHandler {
private static final Logger LOG = LoggerFactory.getLogger(PolarisRestTemplateResponseErrorHandler.class);
private static final Logger LOG = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class);
private static final String FIELD_NAME = "connection";
@ -53,8 +55,9 @@ public class PolarisRestTemplateResponseErrorHandler implements ResponseErrorHan
private final PolarisResponseErrorHandler polarisResponseErrorHandler;
public PolarisRestTemplateResponseErrorHandler(
ConsumerAPI consumerAPI, PolarisResponseErrorHandler polarisResponseErrorHandler) {
public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI,
PolarisResponseErrorHandler polarisResponseErrorHandler) {
super(properties);
this.consumerAPI = consumerAPI;
this.polarisResponseErrorHandler = polarisResponseErrorHandler;
}
@ -74,7 +77,8 @@ public class PolarisRestTemplateResponseErrorHandler implements ResponseErrorHan
}
@Override
public void handleError(@NonNull URI url, @NonNull HttpMethod method, @NonNull ClientHttpResponse response) throws IOException {
public void handleError(@NonNull URI url, @NonNull HttpMethod method, @NonNull ClientHttpResponse response)
throws IOException {
ServiceCallResult resultRequest = createServiceCallResult(url);
try {
HttpURLConnection connection = (HttpURLConnection) ReflectionUtils.getFieldValue(response, FIELD_NAME);
@ -84,15 +88,18 @@ public class PolarisRestTemplateResponseErrorHandler implements ResponseErrorHan
resultRequest.setPort(realURL.getPort());
}
if (response.getStatusCode().value() > 500) {
if (apply(response.getStatusCode())) {
resultRequest.setRetStatus(RetStatus.RetFail);
}
}
catch (Exception e) {
LOG.error("Will report response of {} url {}", response, url, e);
resultRequest.setRetStatus(RetStatus.RetFail);
throw e;
}
finally {
LOG.debug("Will report result of {}. URL=[{}]. Response=[{}].", resultRequest.getRetStatus().name(),
url, response);
consumerAPI.updateServiceCallResult(resultRequest);
}
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
package com.tencent.cloud.rpc.enhancement.resttemplate;
import org.springframework.web.client.ResponseErrorHandler;

@ -0,0 +1,82 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.stat.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* The properties for stat reporter.
*
* @author Haotian Zhang
*/
@ConfigurationProperties("spring.cloud.polaris.stat")
public class PolarisStatProperties {
/**
* If state reporter enabled.
*/
private boolean enabled = false;
/**
* Local host for prometheus to pull.
*/
private String host;
/**
* Port for prometheus to pull.
*/
private int port = 28080;
/**
* Path for prometheus to pull.
*/
private String path = "/metrics";
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}

@ -0,0 +1,43 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.stat.config;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
/**
* Autoconfiguration of stat reporter.
*
* @author Haotian Zhang
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
@Import({PolarisStatProperties.class})
public class PolarisStatPropertiesAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public StatConfigModifier statReporterConfigModifier(PolarisStatProperties polarisStatProperties, Environment environment) {
return new StatConfigModifier(polarisStatProperties, environment);
}
}

@ -0,0 +1,34 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.stat.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Autoconfiguration of stat reporter at bootstrap phase.
*
* @author lepdou 2022-03-29
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty("spring.cloud.polaris.enabled")
@Import(PolarisStatPropertiesAutoConfiguration.class)
public class PolarisStatPropertiesBootstrapConfiguration {
}

@ -0,0 +1,70 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.stat.config;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.polaris.context.PolarisConfigModifier;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import com.tencent.polaris.plugins.stat.prometheus.handler.PrometheusHandlerConfig;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import static com.tencent.polaris.api.config.global.StatReporterConfig.DEFAULT_REPORTER_PROMETHEUS;
/**
* Config modifier for stat reporter.
*
* @author Haotian Zhang
*/
public class StatConfigModifier implements PolarisConfigModifier {
private final PolarisStatProperties polarisStatProperties;
private final Environment environment;
public StatConfigModifier(PolarisStatProperties polarisStatProperties, Environment environment) {
this.polarisStatProperties = polarisStatProperties;
this.environment = environment;
}
@Override
public void modify(ConfigurationImpl configuration) {
// Turn on stat reporter configuration.
configuration.getGlobal().getStatReporter().setEnable(polarisStatProperties.isEnabled());
// Set prometheus plugin.
if (polarisStatProperties.isEnabled()) {
PrometheusHandlerConfig prometheusHandlerConfig = configuration.getGlobal().getStatReporter()
.getPluginConfig(DEFAULT_REPORTER_PROMETHEUS, PrometheusHandlerConfig.class);
if (!StringUtils.hasText(polarisStatProperties.getHost())) {
polarisStatProperties.setHost(environment.getProperty("spring.cloud.client.ip-address"));
}
prometheusHandlerConfig.setHost(polarisStatProperties.getHost());
prometheusHandlerConfig.setPort(polarisStatProperties.getPort());
prometheusHandlerConfig.setPath(polarisStatProperties.getPath());
configuration.getGlobal().getStatReporter()
.setPluginConfig(DEFAULT_REPORTER_PROMETHEUS, prometheusHandlerConfig);
}
}
@Override
public int getOrder() {
return ContextConstant.ModifierOrder.STAT_REPORTER_ORDER;
}
}

@ -0,0 +1,51 @@
{
"properties": [
{
"name": "spring.cloud.tencent.rpc-enhancement.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "If rpc enhancement enabled."
},
{
"name": "spring.cloud.tencent.rpc-enhancement.reporter.ignore-internal-server-error",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "If ignore \"Internal Server Error Http Status Code (500)\"."
},
{
"name": "spring.cloud.tencent.rpc-enhancement.reporter.series",
"type": "java.util.List<org.springframework.http.HttpStatus.Series>",
"defaultValue": "HttpStatus.Series.SERVER_ERROR",
"description": "Specify List of HTTP status series that needs to be reported as FAILED when status list is empty."
},
{
"name": "spring.cloud.tencent.rpc-enhancement.reporter.statuses",
"type": "java.util.List<org.springframework.http.HttpStatus>",
"defaultValue": "",
"description": "Specify the Http status code(s) that needs to be reported as FAILED."
},
{
"name": "spring.cloud.polaris.stat.enabled",
"type": "java.lang.Boolean",
"defaultValue": false,
"description": "Enable polaris stat reporter or not."
},
{
"name": "spring.cloud.polaris.stat.host",
"type": "java.lang.String",
"description": "Local host for prometheus to pull."
},
{
"name": "spring.cloud.polaris.stat.port",
"type": "java.lang.Integer",
"defaultValue": "28080",
"description": "Port for prometheus to pull."
},
{
"name": "spring.cloud.polaris.stat.path",
"type": "java.lang.String",
"defaultValue": "/metrics",
"description": "Path for prometheus to pull."
}
]
}

@ -0,0 +1,5 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatPropertiesBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration,\
com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatPropertiesAutoConfiguration

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save