pull/489/head
DerekYRC 3 years ago
parent ab349a5378
commit 8d64a8daa7

@ -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 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/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar

@ -3,4 +3,13 @@
- [Bugfix: optimize ratelimit actuator](https://github.com/Tencent/spring-cloud-tencent/pull/419) - [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) - [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/434) - [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/482)

@ -42,6 +42,7 @@ Spring Cloud Tencent提供的能力包括但不限于
- 密码polaris - 密码polaris
- 控制面地址: `grpc://183.47.111.80:8091` - 控制面地址: `grpc://183.47.111.80:8091`
- -
`spring-cloud-tencent-example` 下 example 地址都默认指向了体验服务地址(`grpc://183.47.111.80:8091`),如果您只是体验 Spring Cloud `spring-cloud-tencent-example` 下 example 地址都默认指向了体验服务地址(`grpc://183.47.111.80:8091`),如果您只是体验 Spring Cloud
Tencent可直接一键运行任何 example。 Tencent可直接一键运行任何 example。
@ -53,6 +54,10 @@ Tencent可直接一键运行任何 example。
Spring Cloud Tencent 所有组件都已上传到 Maven 中央仓库,只需要引入依赖即可。 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 ```` 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) You can build this project with command:
- [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) ```shell
- [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) ## MacOS or Linux
- [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) ./mvnw clean package
- [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)
## Win
- ### 开发文档 .\mvnw.cmd clean package
- [项目概览](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)
## 文档
- 使用文档
- [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 All the components of Spring Cloud Tencent have been uploaded to the Maven central repository, just need to introduce
dependencies. 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: For example:
```` xml ```` 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 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 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 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 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 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 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) - [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) - [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) - [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 # "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at # 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, # Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an # software distributed under the License is distributed on an
@ -36,6 +36,10 @@
if [ -z "$MAVEN_SKIP_RC" ] ; then if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc . /etc/mavenrc
fi fi
@ -145,7 +149,7 @@ if [ -z "$JAVACMD" ] ; then
JAVACMD="$JAVA_HOME/bin/java" JAVACMD="$JAVA_HOME/bin/java"
fi fi
else else
JAVACMD="`which java`" JAVACMD="`\\unset -f command; \\command -v java`"
fi fi
fi fi
@ -212,9 +216,9 @@ else
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi fi
if [ -n "$MVNW_REPOURL" ]; then 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 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 fi
while IFS="=" read key value; do while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;; case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
@ -233,9 +237,9 @@ else
echo "Found wget ... using wget" echo "Found wget ... using wget"
fi fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else 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 fi
elif command -v curl > /dev/null; then elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then if [ "$MVNW_VERBOSE" = true ]; then
@ -305,6 +309,8 @@ WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \ exec "$JAVACMD" \
$MAVEN_OPTS \ $MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ -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 "$@" ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

26
mvnw.cmd vendored

@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance @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 with the License. You may obtain a copy of the License at
@REM @REM
@REM http://www.apache.org/licenses/LICENSE-2.0 @REM https://www.apache.org/licenses/LICENSE-2.0
@REM @REM
@REM Unless required by applicable law or agreed to in writing, @REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an @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 @REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending @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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre :skipRcPre
@setlocal @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_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 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 IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
) )
@ -134,7 +134,7 @@ if exist %WRAPPER_JAR% (
) )
) else ( ) else (
if not "%MVNW_REPOURL%" == "" ( 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" ( if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ... 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. @REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%* 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 if ERRORLEVEL 1 goto error
goto end goto end
@ -170,8 +176,8 @@ set ERROR_CODE=1
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 @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 "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost :skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
@ -179,4 +185,4 @@ 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> <modules>
<module>spring-cloud-tencent-commons</module> <module>spring-cloud-tencent-commons</module>
<module>spring-cloud-tencent-polaris-context</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-tencent-polaris-loadbalancer</module>
<module>spring-cloud-starter-tencent-metadata-transfer</module> <module>spring-cloud-starter-tencent-metadata-transfer</module>
<module>spring-cloud-starter-tencent-polaris-config</module> <module>spring-cloud-starter-tencent-polaris-config</module>

@ -39,6 +39,8 @@ import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebFilterChain;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; 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 * 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. // Get metadata string from http header.
ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest(); 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> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(serverWebExchange);
Map<String, String> mergedTransitiveMetadata = new HashMap<>(); Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata); mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata); 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. // Save to ServerWebExchange.
serverWebExchange.getAttributes().put( serverWebExchange.getAttributes().put(
@ -80,10 +85,9 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
.doFinally((type) -> MetadataContextHolder.remove()); .doFinally((type) -> MetadataContextHolder.remove());
} }
private Map<String, String> getIntervalTransitiveMetadata(ServerHttpRequest serverHttpRequest) { private Map<String, String> getIntervalMetadata(ServerHttpRequest serverHttpRequest, String headerName) {
HttpHeaders httpHeaders = serverHttpRequest.getHeaders(); HttpHeaders httpHeaders = serverHttpRequest.getHeaders();
String customMetadataStr = httpHeaders String customMetadataStr = httpHeaders.getFirst(headerName);
.getFirst(MetadataConstant.HeaderName.CUSTOM_METADATA);
try { try {
if (StringUtils.hasText(customMetadataStr)) { if (StringUtils.hasText(customMetadataStr)) {
customMetadataStr = URLDecoder.decode(customMetadataStr, UTF_8); customMetadataStr = URLDecoder.decode(customMetadataStr, UTF_8);

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

@ -19,7 +19,7 @@
package com.tencent.cloud.metadata.core; package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.MetadataConstant;
@ -33,9 +33,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.util.CollectionUtils; 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.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 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 * 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 // get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get(); MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Map<String, String> disposableMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_DISPOSABLE);
if (!CollectionUtils.isEmpty(customMetadata)) { // Clean up one-time metadata coming from upstream .
String encodedTransitiveMetadata = JacksonUtils.serialize2Json(customMetadata); Map<String, String> newestCustomMetadata = new HashMap<>();
requestTemplate.removeHeader(CUSTOM_METADATA); 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 { try {
requestTemplate.header(CUSTOM_METADATA, requestTemplate.header(headerName, encode(encodedMetadata, UTF_8));
URLEncoder.encode(encodedTransitiveMetadata, UTF_8));
} }
catch (UnsupportedEncodingException e) { catch (UnsupportedEncodingException e) {
LOG.error("Set header failed.", 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.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant; 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.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; 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 * Interceptor used for adding the metadata in http headers from context when web client
@ -51,22 +55,44 @@ public class EncodeTransferMedataRestTemplateInterceptor implements ClientHttpRe
} }
@Override @Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, public ClientHttpResponse intercept(@NonNull HttpRequest httpRequest, @NonNull byte[] bytes,
ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { @NonNull ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
// get metadata of current thread // get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get(); MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Map<String, String> disposableMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_DISPOSABLE);
if (!CollectionUtils.isEmpty(customMetadata)) { Map<String, String> newestCustomMetadata = new HashMap<>();
String encodedTransitiveMetadata = JacksonUtils.serialize2Json(customMetadata); 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 { try {
httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA, request.getHeaders().set(headerName, URLEncoder.encode(encodedMetadata, UTF_8));
URLEncoder.encode(encodedTransitiveMetadata, UTF_8));
} }
catch (UnsupportedEncodingException e) { 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.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.MetadataConstant;
@ -36,6 +37,8 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; 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; 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 // get metadata of current thread
MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT); MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT);
// add new metadata and cover old
if (metadataContext == null) { if (metadataContext == null) {
metadataContext = MetadataContextHolder.get(); metadataContext = MetadataContextHolder.get();
} }
Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
if (!CollectionUtils.isEmpty(customMetadata)) { Map<String, String> disposableMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_DISPOSABLE);
String metadataStr = JacksonUtils.serialize2Json(customMetadata);
// 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 { try {
builder.header(MetadataConstant.HeaderName.CUSTOM_METADATA, URLEncoder.encode(metadataStr, UTF_8)); builder.header(headerName, URLEncoder.encode(encodedMetadata, UTF_8));
} }
catch (UnsupportedEncodingException e) { catch (UnsupportedEncodingException e) {
builder.header(MetadataConstant.HeaderName.CUSTOM_METADATA, metadataStr); builder.header(headerName, encodedMetadata);
} }
} }
return chain.filter(exchange.mutate().request(builder.build()).build());
} }
} }

@ -18,36 +18,14 @@
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId> <artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>
</dependency> </dependency>
<!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start -->
<dependency> <dependency>
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>polaris-discovery-factory</artifactId> <artifactId>spring-cloud-tencent-rpc-enhancement</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>
<!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start -->
<dependency> <dependency>
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.polaris</groupId>
<artifactId>polaris-circuitbreaker-factory</artifactId> <artifactId>polaris-circuitbreaker-factory</artifactId>
@ -84,28 +62,10 @@
</dependency> </dependency>
<!-- Polaris dependencies end --> <!-- 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> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

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

@ -17,16 +17,9 @@
package com.tencent.cloud.polaris.circuitbreaker.config; 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.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/** /**
* Autoconfiguration at bootstrap phase. * Autoconfiguration at bootstrap phase.
@ -34,37 +27,8 @@ import org.springframework.context.annotation.Configuration;
* @author lepdou 2022-03-29 * @author lepdou 2022-03-29
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled @ConditionalOnProperty("spring.cloud.polaris.enabled")
@ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", havingValue = "true", @Import(PolarisCircuitBreakerAutoConfiguration.class)
matchIfMissing = true)
public class PolarisCircuitBreakerBootstrapConfiguration { 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=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration 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. * 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.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -33,20 +29,16 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
public class PolarisFeignClientAutoConfigurationTest { public class PolarisCircuitBreakerAutoConfigurationTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration( .withConfiguration(AutoConfigurations.of(PolarisCircuitBreakerAutoConfiguration.class))
AutoConfigurations.of(
PolarisContextAutoConfiguration.class,
PolarisCircuitBreakerAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true"); .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@Test @Test
public void testDefaultInitialization() { public void testDefaultInitialization() {
this.contextRunner.run(context -> { this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(ConsumerAPI.class); assertThat(context).hasSingleBean(
assertThat(context).hasSingleBean(PolarisFeignBeanPostProcessor.class); PolarisCircuitBreakerAutoConfiguration.CircuitBreakerConfigModifier.class);
}); });
} }
} }

@ -15,9 +15,8 @@
* specific language governing permissions and limitations under the License. * 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.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -31,15 +30,16 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Haotian Zhang * @author Haotian Zhang
*/ */
public class PolarisCircuitBreakerBootstrapConfigurationTest { public class PolarisCircuitBreakerBootstrapConfigurationTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PolarisCircuitBreakerBootstrapConfiguration.class)) .withConfiguration(AutoConfigurations.of(PolarisCircuitBreakerBootstrapConfiguration.class))
.withPropertyValues("spring.cloud.polaris.enabled=true")
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true"); .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@Test @Test
public void testDefaultInitialization() { public void testDefaultInitialization() {
this.contextRunner.run(context -> { this.contextRunner.run(context -> {
assertThat(context).hasSingleBean( 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> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId> <artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>
</dependency> </dependency>
<!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start -->
<dependency> <dependency>
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>polaris-discovery-factory</artifactId> <artifactId>spring-cloud-tencent-rpc-enhancement</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>
</dependency> </dependency>
<!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start -->
<dependency> <dependency>
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.polaris</groupId>
<artifactId>polaris-test-common</artifactId> <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.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler; import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler;
import com.tencent.cloud.polaris.extend.consul.ConsulContextProperties; 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.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -41,18 +36,6 @@ import org.springframework.context.annotation.Import;
@Import({PolarisDiscoveryProperties.class, ConsulContextProperties.class}) @Import({PolarisDiscoveryProperties.class, ConsulContextProperties.class})
public class DiscoveryPropertiesAutoConfiguration { 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 @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public PolarisDiscoveryHandler polarisDiscoveryHandler() { public PolarisDiscoveryHandler polarisDiscoveryHandler() {

@ -30,6 +30,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import static com.tencent.cloud.common.constant.ContextConstant.DEFAULT_REGISTRY_HEARTBEAT_TIME_INTERVAL;
/** /**
* Properties for Polaris. * Properties for Polaris.
* *
@ -95,6 +97,13 @@ public class PolarisDiscoveryProperties {
@Value("${spring.cloud.polaris.discovery.heartbeat.enabled:#{true}}") @Value("${spring.cloud.polaris.discovery.heartbeat.enabled:#{true}}")
private Boolean heartbeatEnabled = 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. * Custom health check url to override default.
*/ */
@ -202,6 +211,17 @@ public class PolarisDiscoveryProperties {
this.serviceListRefreshInterval = serviceListRefreshInterval; 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 @Override
public String toString() { public String toString() {
return "PolarisDiscoveryProperties{" + return "PolarisDiscoveryProperties{" +
@ -215,6 +235,7 @@ public class PolarisDiscoveryProperties {
", enabled=" + enabled + ", enabled=" + enabled +
", registerEnabled=" + registerEnabled + ", registerEnabled=" + registerEnabled +
", heartbeatEnabled=" + heartbeatEnabled + ", heartbeatEnabled=" + heartbeatEnabled +
", heartbeatInterval=" + heartbeatInterval +
", healthCheckUrl='" + healthCheckUrl + '\'' + ", healthCheckUrl='" + healthCheckUrl + '\'' +
", serviceListRefreshInterval=" + serviceListRefreshInterval + ", serviceListRefreshInterval=" + serviceListRefreshInterval +
'}'; '}';

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

@ -30,7 +30,6 @@ import com.tencent.polaris.client.api.SDKContext;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -40,7 +39,7 @@ import org.springframework.util.CollectionUtils;
* *
* @author Haotian Zhang, Andrew Shan, Jie Cheng * @author Haotian Zhang, Andrew Shan, Jie Cheng
*/ */
public class PolarisRegistration implements Registration, ServiceInstance { public class PolarisRegistration implements Registration {
private static final String METADATA_KEY_IP = "internal-ip"; private static final String METADATA_KEY_IP = "internal-ip";
private static final String METADATA_KEY_ADDRESS = "internal-address"; private static final String METADATA_KEY_ADDRESS = "internal-address";

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

@ -24,6 +24,12 @@
"defaultValue": true, "defaultValue": true,
"description": "enable polaris registration or not." "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", "name": "spring.cloud.polaris.discovery.health-check-url",
"type": "java.lang.String", "type": "java.lang.String",

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

@ -19,6 +19,10 @@
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId> <artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId> <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(); 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() { private ContextConstant() {
} }
@ -68,5 +73,10 @@ public final class ContextConstant {
* Order of configuration modifier. * Order of configuration modifier.
*/ */
public static Integer CONFIG_ORDER = 1; 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 { public final class MetadataConstant {
/**
* Default Private Constructor.
*/
private MetadataConstant() {
}
/** /**
* Order of filter, interceptor, ... * Order of filter, interceptor, ...
*/ */
@ -57,6 +63,11 @@ public final class MetadataConstant {
*/ */
public static final String CUSTOM_METADATA = "SCT-CUSTOM-METADATA"; 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. * System Metadata.
*/ */

@ -40,6 +40,17 @@ public class MetadataContext {
* transitive context. * transitive context.
*/ */
public static final String FRAGMENT_TRANSITIVE = "transitive"; 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); private static final Logger LOG = LoggerFactory.getLogger(MetadataContext.class);
/** /**
* Namespace of local instance. * Namespace of local instance.

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

@ -54,18 +54,22 @@ public class StaticMetadataManager {
private static final String ENV_METADATA_PREFIX = "SCT_METADATA_CONTENT_"; 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 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_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_ZONE = "SCT_METADATA_ZONE";
private static final String ENV_METADATA_REGION = "SCT_METADATA_REGION"; private static final String ENV_METADATA_REGION = "SCT_METADATA_REGION";
private static final String ENV_METADATA_CAMPUS = "SCT_METADATA_CAMPUS"; private static final String ENV_METADATA_CAMPUS = "SCT_METADATA_CAMPUS";
private Map<String, String> envMetadata; private Map<String, String> envMetadata;
private Map<String, String> envTransitiveMetadata; private Map<String, String> envTransitiveMetadata;
private Map<String, String> envDisposableMetadata;
private Map<String, String> configMetadata; private Map<String, String> configMetadata;
private Map<String, String> configTransitiveMetadata; private Map<String, String> configTransitiveMetadata;
private Map<String, String> configDisposableMetadata;
private Map<String, String> customSPIMetadata; private Map<String, String> customSPIMetadata;
private Map<String, String> customSPITransitiveMetadata; private Map<String, String> customSPITransitiveMetadata;
private Map<String, String> customSPIDisposableMetadata;
private Map<String, String> mergedStaticMetadata; private Map<String, String> mergedStaticMetadata;
private Map<String, String> mergedStaticTransitiveMetadata; private Map<String, String> mergedStaticTransitiveMetadata;
private Map<String, String> mergedStaticDisposableMetadata;
private String zone; private String zone;
private String region; private String region;
private String campus; private String campus;
@ -85,6 +89,7 @@ public class StaticMetadataManager {
LOGGER.info("[SCT] Loaded static metadata info. {}", this); LOGGER.info("[SCT] Loaded static metadata info. {}", this);
} }
@SuppressWarnings("DuplicatedCode")
private void parseEnvMetadata() { private void parseEnvMetadata() {
Map<String, String> allEnvs = System.getenv(); Map<String, String> allEnvs = System.getenv();
@ -118,27 +123,54 @@ public class StaticMetadataManager {
} }
} }
envTransitiveMetadata = Collections.unmodifiableMap(envTransitiveMetadata); 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) { private void parseConfigMetadata(MetadataLocalProperties metadataLocalProperties) {
Map<String, String> allMetadata = metadataLocalProperties.getContent(); Map<String, String> allMetadata = metadataLocalProperties.getContent();
List<String> transitiveKeys = metadataLocalProperties.getTransitive(); 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) { for (String key : transitiveKeys) {
if (allMetadata.containsKey(key)) { 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); configMetadata = Collections.unmodifiableMap(allMetadata);
} }
@SuppressWarnings("DuplicatedCode")
private void parseCustomMetadata(InstanceMetadataProvider instanceMetadataProvider) { private void parseCustomMetadata(InstanceMetadataProvider instanceMetadataProvider) {
if (instanceMetadataProvider == null) { if (instanceMetadataProvider == null) {
customSPIMetadata = Collections.emptyMap(); customSPIMetadata = Collections.emptyMap();
customSPITransitiveMetadata = Collections.emptyMap(); customSPITransitiveMetadata = Collections.emptyMap();
customSPIDisposableMetadata = Collections.emptyMap();
return; return;
} }
@ -162,6 +194,17 @@ public class StaticMetadataManager {
} }
} }
customSPITransitiveMetadata = Collections.unmodifiableMap(transitiveMetadata); 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() { private void merge() {
@ -173,15 +216,19 @@ public class StaticMetadataManager {
mergedMetadataResult.putAll(customSPIMetadata); mergedMetadataResult.putAll(customSPIMetadata);
// set location info as metadata // set location info as metadata
mergedMetadataResult.putAll(getLocationMetadata()); mergedMetadataResult.putAll(getLocationMetadata());
this.mergedStaticMetadata = Collections.unmodifiableMap(mergedMetadataResult); this.mergedStaticMetadata = Collections.unmodifiableMap(mergedMetadataResult);
Map<String, String> mergedTransitiveMetadataResult = new HashMap<>(); Map<String, String> mergedTransitiveMetadataResult = new HashMap<>();
mergedTransitiveMetadataResult.putAll(configTransitiveMetadata); mergedTransitiveMetadataResult.putAll(configTransitiveMetadata);
mergedTransitiveMetadataResult.putAll(envTransitiveMetadata); mergedTransitiveMetadataResult.putAll(envTransitiveMetadata);
mergedTransitiveMetadataResult.putAll(customSPITransitiveMetadata); mergedTransitiveMetadataResult.putAll(customSPITransitiveMetadata);
this.mergedStaticTransitiveMetadata = Collections.unmodifiableMap(mergedTransitiveMetadataResult); 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, private void parseLocationMetadata(MetadataLocalProperties metadataLocalProperties,
@ -228,6 +275,10 @@ public class StaticMetadataManager {
return envTransitiveMetadata; return envTransitiveMetadata;
} }
public Map<String, String> getEnvDisposableMetadata() {
return envDisposableMetadata;
}
public Map<String, String> getAllConfigMetadata() { public Map<String, String> getAllConfigMetadata() {
return configMetadata; return configMetadata;
} }
@ -236,6 +287,10 @@ public class StaticMetadataManager {
return configTransitiveMetadata; return configTransitiveMetadata;
} }
public Map<String, String> getConfigDisposableMetadata() {
return configDisposableMetadata;
}
public Map<String, String> getAllCustomMetadata() { public Map<String, String> getAllCustomMetadata() {
return customSPIMetadata; return customSPIMetadata;
} }
@ -244,6 +299,10 @@ public class StaticMetadataManager {
return customSPITransitiveMetadata; return customSPITransitiveMetadata;
} }
public Map<String, String> getCustomSPIDisposableMetadata() {
return customSPIDisposableMetadata;
}
public Map<String, String> getMergedStaticMetadata() { public Map<String, String> getMergedStaticMetadata() {
return mergedStaticMetadata; return mergedStaticMetadata;
} }
@ -252,6 +311,10 @@ public class StaticMetadataManager {
return mergedStaticTransitiveMetadata; return mergedStaticTransitiveMetadata;
} }
public Map<String, String> getMergedStaticDisposableMetadata() {
return mergedStaticDisposableMetadata;
}
public String getZone() { public String getZone() {
return zone; return zone;
} }

@ -43,6 +43,11 @@ public class MetadataLocalProperties {
*/ */
private List<String> transitive; private List<String> transitive;
/**
* A disposable metadata key list .
*/
private List<String> disposable;
public Map<String, String> getContent() { public Map<String, String> getContent() {
if (CollectionUtils.isEmpty(content)) { if (CollectionUtils.isEmpty(content)) {
content = new HashMap<>(); content = new HashMap<>();
@ -64,4 +69,15 @@ public class MetadataLocalProperties {
public void setTransitive(List<String> transitive) { public void setTransitive(List<String> transitive) {
this.transitive = 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("Env", staticMetadataManager.getAllEnvMetadata());
result.put("EnvTransitive", staticMetadataManager.getEnvTransitiveMetadata()); result.put("EnvTransitive", staticMetadataManager.getEnvTransitiveMetadata());
result.put("ConfigTransitive", staticMetadataManager.getConfigTransitiveMetadata()); result.put("ConfigTransitive", staticMetadataManager.getConfigTransitiveMetadata());
result.put("ConfigDisposable", staticMetadataManager.getConfigDisposableMetadata());
result.put("Config", staticMetadataManager.getAllConfigMetadata()); result.put("Config", staticMetadataManager.getAllConfigMetadata());
result.put("MergeStatic", staticMetadataManager.getMergedStaticMetadata()); 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("zone", staticMetadataManager.getZone());
result.put("region", staticMetadataManager.getRegion()); result.put("region", staticMetadataManager.getRegion());
result.put("campus", staticMetadataManager.getCampus()); result.put("campus", staticMetadataManager.getCampus());

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

@ -22,6 +22,7 @@ import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -42,7 +43,6 @@ public final class JacksonUtils {
private static final Logger LOG = LoggerFactory.getLogger(JacksonUtils.class); private static final Logger LOG = LoggerFactory.getLogger(JacksonUtils.class);
private JacksonUtils() { private JacksonUtils() {
} }
/** /**
@ -52,9 +52,26 @@ public final class JacksonUtils {
* @return Json String * @return Json String
*/ */
public static <T> String serialize2Json(T object) { 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 { try {
if (pretty) {
ObjectWriter objectWriter = OM.writerWithDefaultPrettyPrinter();
return objectWriter.writeValueAsString(object);
}
else {
return OM.writeValueAsString(object); return OM.writeValueAsString(object);
} }
}
catch (JsonProcessingException e) { catch (JsonProcessingException e) {
LOG.error("Object to Json failed. {}", object, e); LOG.error("Object to Json failed. {}", object, e);
throw new RuntimeException("Object to Json failed.", e); throw new RuntimeException("Object to Json failed.", e);

@ -9,6 +9,11 @@
"name": "spring.cloud.tencent.metadata.transitive", "name": "spring.cloud.tencent.metadata.transitive",
"type": "java.util.List", "type": "java.util.List",
"description": "Custom transitive metadata key 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("a", "1");
customMetadata.put("b", "22"); customMetadata.put("b", "22");
customMetadata.put("c", "3"); customMetadata.put("c", "3");
MetadataContextHolder.init(customMetadata); MetadataContextHolder.init(customMetadata, new HashMap<>());
metadataContext = MetadataContextHolder.get(); metadataContext = MetadataContextHolder.get();
customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Assertions.assertThat(customMetadata.get("a")).isEqualTo("1"); Assertions.assertThat(customMetadata.get("a")).isEqualTo("1");

@ -29,6 +29,11 @@
<artifactId>spring-cloud-tencent-polaris-context</artifactId> <artifactId>spring-cloud-tencent-polaris-context</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId> <artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>

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

@ -4,7 +4,7 @@
本样例将介绍如何在Spring Cloud项目中使用```spring-cloud-starter-tencent-metadata-transfer```以使用其各项功能。 本样例将介绍如何在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启动 ##### IDEA启动
分别启动 分别启动
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-callee-service```的```MetadataCalleeService``` - ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-frontend```的```MetadataFrontendService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-caller-service```的```MetadataCallerService``` - ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-middle```的```MetadataMiddleService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-backend```的```MetadataBackendService```
##### Maven打包启动 ##### Maven打包启动
@ -53,7 +54,7 @@ spring:
mvn clean package mvn clean package
``` ```
然后在```metadata-callee-service```、```metadata-caller-service```下找到生成的jar包运行 然后在```metadata-frontend```、```metadata-middle```、```metadata-backend```下找到生成的jar包运行
``` ```
java -jar ${app.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 ```yaml
spring: spring:
@ -76,10 +77,37 @@ spring:
CUSTOM-METADATA-KEY-LOCAL: CUSTOM-VALUE-LOCAL CUSTOM-METADATA-KEY-LOCAL: CUSTOM-VALUE-LOCAL
# 示例:可传递元数据 # 示例:可传递元数据
CUSTOM-METADATA-KEY-TRANSITIVE: CUSTOM-VALUE-TRANSITIVE CUSTOM-METADATA-KEY-TRANSITIVE: CUSTOM-VALUE-TRANSITIVE
# 示例:一次性元数据
CUSTOM-METADATA-KEY-DISPOSABLE: CUSTOM-VALUE-DISPOSABLE-FRONTEND
# 指定哪个元数据的键值将沿着链接传递 # 指定哪个元数据的键值将沿着链接传递
transitive: transitive:
- CUSTOM-METADATA-KEY-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 ```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-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE",
"CUSTOM-METADATA-KEY-LOCAL": "CUSTOM-VALUE-LOCAL" "CUSTOM-METADATA-KEY-TRANSITIVE-2": "CUSTOM-VALUE-TRANSITIVE-2"
}, },
"callee-transitive-metadata": { "middle-upstream-disposable-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE" "CUSTOM-METADATA-KEY-DISPOSABLE": "CUSTOM-VALUE-DISPOSABLE-FRONTEND"
}, },
"caller-transitive-metadata": { "middle-local-disposable-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE" "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` 项目中默认配置的所有的元数据。 > `*`(星号),代表示例中的`frontend`、`middle`、`backend`。
- Key `caller-transitive-metadata` 表示 `metadata-caller-service` 项目中指定的可以在链路中传递的元数据列表。
- Key `callee-transitive-metadata` 表示 `metadata-callee-service` 项目被 `metadata-caller-service` 调用时传递过来的上游的元数据列表。 - 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参考 ### Wiki参考

@ -2,9 +2,10 @@
## Example Introduction ## 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 ## Instruction
@ -40,8 +41,10 @@ Reference to [Polaris Getting Started](https://github.com/PolarisMesh/polaris#ge
- IDEA Launching - IDEA Launching
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-callee-service```s```MetadataCalleeService``` - ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-frontend```'s ```MetadataFrontendService```
- ```spring-cloud-tencent-examples/metadata-transfer-example/metadata-caller-service```'s```MetadataCallerService``` - ```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 - Maven Package Launching
@ -51,7 +54,7 @@ Execute under ```spring-cloud-tencent-examples/metadata-transfer-example```
mvn clean package 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} java -jar ${app.jar}
@ -62,7 +65,7 @@ Launch application, change ${app.jar} to jar's package name.
### Metadata Configuration ### 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 ```yaml
spring: spring:
@ -75,9 +78,38 @@ spring:
CUSTOM-METADATA-KEY-LOCAL: CUSTOM-VALUE-LOCAL CUSTOM-METADATA-KEY-LOCAL: CUSTOM-VALUE-LOCAL
# Example: transitive # Example: transitive
CUSTOM-METADATA-KEY-TRANSITIVE: CUSTOM-VALUE-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 # Assigned which metadata key-value will be passed along the link
transitive: transitive:
- CUSTOM-METADATA-KEY-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-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE",
"CUSTOM-METADATA-KEY-LOCAL": "CUSTOM-VALUE-LOCAL" "CUSTOM-METADATA-KEY-TRANSITIVE-2": "CUSTOM-VALUE-TRANSITIVE-2"
}, },
"callee-transitive-metadata": { "middle-upstream-disposable-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE" "CUSTOM-METADATA-KEY-DISPOSABLE": "CUSTOM-VALUE-DISPOSABLE-FRONTEND"
}, },
"caller-transitive-metadata": { "middle-local-disposable-metadata": {
"CUSTOM-METADATA-KEY-TRANSITIVE": "CUSTOM-VALUE-TRANSITIVE" "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 Response value description
- Key `caller-metadata-contents` represents all metadata configured by default in the `metadata-caller-service` project. > `*` (asterisk), representing `frontend`, `middle`, `backend` in the example.
- 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`. - 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 ### Wiki Reference

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

@ -15,8 +15,9 @@
* specific language governing permissions and limitations under the License. * 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 java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
@ -35,10 +36,10 @@ import org.springframework.web.bind.annotation.RestController;
* @author Palmer Xu * @author Palmer Xu
*/ */
@RestController @RestController
@RequestMapping("/metadata/service/callee") @RequestMapping("/metadata/service/backend")
public class MetadataCalleeController { 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}") @Value("${server.port:0}")
private int port; private int port;
@ -48,18 +49,36 @@ public class MetadataCalleeController {
* @return information of callee * @return information of callee
*/ */
@GetMapping("/info") @GetMapping("/info")
public Map<String, String> info() { public Map<String, Map<String, String>> info() {
LOG.info("Metadata Service Callee [{}] is called.", port); LOG.info("Metadata Backend Service [{}] is called.", port);
Map<String, Map<String, String>> ret = new HashMap<>();
// Get Custom Metadata From Context // Get Custom Metadata From Context
MetadataContext context = MetadataContextHolder.get(); MetadataContext context = MetadataContextHolder.get();
Map<String, String> customMetadataMap = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); Map<String, String> customMetadataMap = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
customMetadataMap.forEach((key, value) -> { 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. * 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.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -26,10 +26,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @author Palmer Xu * @author Palmer Xu
*/ */
@SpringBootApplication @SpringBootApplication
public class MetadataCalleeService { public class MetadataBackendService {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(MetadataCalleeService.class, args); SpringApplication.run(MetadataBackendService.class, args);
} }
} }

@ -1,8 +1,8 @@
server: server:
port: 48084 port: 48088
spring: spring:
application: application:
name: MetadataCalleeService name: MetadataBackendService
cloud: cloud:
polaris: polaris:
address: grpc://183.47.111.80:8091 address: grpc://183.47.111.80:8091
@ -11,3 +11,10 @@ spring:
discovery: discovery:
enabled: true enabled: true
register: 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. * 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.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -31,12 +30,11 @@ import org.springframework.web.client.RestTemplate;
* @author Palmer Xu * @author Palmer Xu
*/ */
@SpringBootApplication @SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients @EnableFeignClients
public class MetadataCallerService { public class MetadataFrontendService {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(MetadataCallerService.class, args); SpringApplication.run(MetadataFrontendService.class, args);
} }
@Bean @Bean
@ -44,5 +42,4 @@ public class MetadataCallerService {
public RestTemplate restTemplate() { public RestTemplate restTemplate() {
return new RestTemplate(); return new RestTemplate();
} }
} }

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

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

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

@ -10,8 +10,8 @@
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>metadata-callee-service</artifactId> <artifactId>metadata-middle</artifactId>
<name>Spring Cloud Tencent Metadata Transfer Callee Service</name> <name>Spring Cloud Tencent Metadata Transfer Middle Service</name>
<dependencies> <dependencies>
<dependency> <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> <name>Spring Cloud Starter Tencent Metadata Transfer Example</name>
<modules> <modules>
<module>metadata-callee-service</module> <module>metadata-frontend</module>
<module>metadata-caller-service</module> <module>metadata-middle</module>
<module>metadata-backend</module>
</modules> </modules>
<dependencies> <dependencies>

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

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

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

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

@ -19,6 +19,9 @@ spring:
heartbeat: heartbeat:
enabled: true enabled: true
health-check-url: /discovery/service/caller/healthCheck health-check-url: /discovery/service/caller/healthCheck
stat:
enabled: true
port: 28081
# consul: # consul:
# port: 8500 # port: 8500
# host: 127.0.0.1 # host: 127.0.0.1

@ -27,6 +27,33 @@
</dependency> </dependency>
<!-- Polaris dependencies start --> <!-- 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> <dependency>
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.polaris</groupId>
<artifactId>polaris-client</artifactId> <artifactId>polaris-client</artifactId>
@ -62,17 +89,6 @@
<artifactId>flow-cache-expired</artifactId> <artifactId>flow-cache-expired</artifactId>
</dependency> </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> <dependency>
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.polaris</groupId>
<artifactId>loadbalancer-random</artifactId> <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.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.ModifyAddress; import com.tencent.cloud.polaris.context.ModifyAddress;
import com.tencent.cloud.polaris.context.ServiceRuleManager; 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.api.exception.PolarisException;
import com.tencent.polaris.client.api.SDKContext; 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.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -46,6 +49,18 @@ public class PolarisContextAutoConfiguration {
return SDKContext.initContextByConfig(properties.configuration()); 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 @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ModifyAddress polarisConfigModifier() { 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 com.tencent.cloud.polaris.context.config.PolarisContextPostConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.context.config.PolarisContextBootstrapAutoConfiguration 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. * 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 feign.Client;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@ -36,14 +38,14 @@ import org.springframework.lang.NonNull;
* *
* @author Haotian Zhang * @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; private BeanFactory factory;
public PolarisFeignBeanPostProcessor(ConsumerAPI consumerAPI) { public EnhancedFeignBeanPostProcessor(List<EnhancedFeignPlugin> enhancedFeignPlugins) {
this.consumerAPI = consumerAPI; this.enhancedFeignPlugins = enhancedFeignPlugins;
} }
@Override @Override
@ -63,7 +65,7 @@ public class PolarisFeignBeanPostProcessor implements BeanPostProcessor, BeanFac
delegate = ((FeignBlockingLoadBalancerClient) bean).getDelegate(); delegate = ((FeignBlockingLoadBalancerClient) bean).getDelegate();
} }
if (delegate != null) { if (delegate != null) {
return new PolarisFeignBlockingLoadBalancerClient(createPolarisFeignClient(delegate), return new EnhancedFeignBlockingLoadBalancerClient(createPolarisFeignClient(delegate),
factory.getBean(BlockingLoadBalancerClient.class), factory.getBean(BlockingLoadBalancerClient.class),
factory.getBean(LoadBalancerProperties.class), factory.getBean(LoadBalancerProperties.class),
factory.getBean(LoadBalancerClientFactory.class)); factory.getBean(LoadBalancerClientFactory.class));
@ -75,12 +77,12 @@ public class PolarisFeignBeanPostProcessor implements BeanPostProcessor, BeanFac
} }
private boolean isNeedWrap(Object bean) { private boolean isNeedWrap(Object bean) {
return bean instanceof Client && !(bean instanceof PolarisFeignClient) return bean instanceof Client && !(bean instanceof EnhancedFeignClient)
&& !(bean instanceof PolarisFeignBlockingLoadBalancerClient); && !(bean instanceof EnhancedFeignBlockingLoadBalancerClient);
} }
private PolarisFeignClient createPolarisFeignClient(Client delegate) { private EnhancedFeignClient createPolarisFeignClient(Client delegate) {
return new PolarisFeignClient(delegate, consumerAPI); return new EnhancedFeignClient(delegate, enhancedFeignPlugins);
} }
@Override @Override

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License. * 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; import feign.Client;
@ -29,9 +29,9 @@ import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalance
* *
* @author Haotian Zhang * @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) { LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
super(delegate, loadBalancerClient, properties, 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. * 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 java.net.URI;
import com.tencent.cloud.common.metadata.MetadataContext; 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.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.ServiceCallResult; import com.tencent.polaris.api.rpc.ServiceCallResult;
import feign.Client;
import feign.Request; import feign.Request;
import feign.Request.Options;
import feign.Response;
import org.apache.commons.lang.StringUtils; 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 * @author Haotian Zhang
*/ */
public class PolarisFeignClient implements Client { public final class ReporterUtils {
private static final Logger LOG = LoggerFactory.getLogger(PolarisFeignClient.class);
private final Client delegate; private ReporterUtils() {
private final ConsumerAPI consumerAPI;
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 ServiceCallResult createServiceCallResult(final Request request) { public static ServiceCallResult createServiceCallResult(final Request request, RetStatus retStatus) {
ServiceCallResult resultRequest = new ServiceCallResult(); ServiceCallResult resultRequest = new ServiceCallResult();
resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE); resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE);
@ -85,7 +44,7 @@ public class PolarisFeignClient implements Client {
resultRequest.setService(serviceName); resultRequest.setService(serviceName);
URI uri = URI.create(request.url()); URI uri = URI.create(request.url());
resultRequest.setMethod(uri.getPath()); resultRequest.setMethod(uri.getPath());
resultRequest.setRetStatus(RetStatus.RetSuccess); resultRequest.setRetStatus(retStatus);
String sourceNamespace = MetadataContext.LOCAL_NAMESPACE; String sourceNamespace = MetadataContext.LOCAL_NAMESPACE;
String sourceService = MetadataContext.LOCAL_SERVICE; String sourceService = MetadataContext.LOCAL_SERVICE;
if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) { 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. * 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; import java.util.Map;
@ -30,17 +30,17 @@ import org.springframework.web.client.RestTemplate;
/** /**
* Autoconfiguration 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} * then replace {@link org.springframework.web.client.ResponseErrorHandler}
* with {@link PolarisRestTemplateResponseErrorHandler}. * with {@link EnhancedRestTemplateReporter} .
* *
* @author wh 2022/6/21 * @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; private ApplicationContext applicationContext;
public PolarisRestTemplateModifier(PolarisRestTemplateResponseErrorHandler polarisRestTemplateResponseErrorHandler) { public EnhancedRestTemplateModifier(EnhancedRestTemplateReporter enhancedRestTemplateReporter) {
this.polarisRestTemplateResponseErrorHandler = polarisRestTemplateResponseErrorHandler; this.enhancedRestTemplateReporter = enhancedRestTemplateReporter;
} }
@Override @Override
@ -54,7 +54,7 @@ public class PolarisRestTemplateModifier implements ApplicationContextAware, Sma
private void initRestTemplate(String beanName, Object bean) { private void initRestTemplate(String beanName, Object bean) {
if (bean instanceof RestTemplate) { if (bean instanceof RestTemplate) {
RestTemplate restTemplate = (RestTemplate) bean; RestTemplate restTemplate = (RestTemplate) bean;
restTemplate.setErrorHandler(polarisRestTemplateResponseErrorHandler); restTemplate.setErrorHandler(enhancedRestTemplateReporter);
} }
} }

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

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License. * 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; 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

@ -0,0 +1,116 @@
/*
* 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 com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.springframework.http.HttpStatus;
/**
* Test For {@link AbstractPolarisReporterAdapter}.
*
* @author <a href="mailto:iskp.me@gmail.com">Elve.Xu</a> 2022/7/11
*/
public class AbstractPolarisReporterAdapterTest {
@Test
public void testApplyWithDefaultConfig() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
// Mock Condition
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
// Assert
Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
}
@Test
public void testApplyWithoutIgnoreInternalServerError() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
// Mock Condition
properties.getStatuses().clear();
properties.setIgnoreInternalServerError(false);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
// Assert
Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(true);
Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
}
@Test
public void testApplyWithIgnoreInternalServerError() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
// Mock Condition
properties.getStatuses().clear();
properties.setIgnoreInternalServerError(true);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
// Assert
Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
}
@Test
public void testApplyWithoutSeries() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
// Mock Condition
properties.getStatuses().clear();
properties.getSeries().clear();
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
// Assert
Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true);
}
@Test
public void testApplyWithSeries() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
// Mock Condition
properties.getStatuses().clear();
properties.getSeries().clear();
properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
// Assert
Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(false);
Assertions.assertThat(adapter.apply(HttpStatus.FORBIDDEN)).isEqualTo(true);
}
/**
* Simple Polaris CircuitBreak Adapter Implements .
*/
public static class SimplePolarisReporterAdapter extends AbstractPolarisReporterAdapter {
public SimplePolarisReporterAdapter(RpcEnhancementReporterProperties properties) {
super(properties);
}
}
}

@ -13,19 +13,20 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.circuitbreaker; package com.tencent.cloud.rpc.enhancement.config;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration;
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.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignBeanPostProcessor;
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.polaris.api.core.ConsumerAPI;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -35,31 +36,33 @@ import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test For {@link PolarisCircuitBreakerAutoConfiguration} . * Test For {@link RpcEnhancementAutoConfiguration}.
* *
* @author <a href="mailto:iskp.me@gmail.com">Palmer Xu</a> 2022-06-28 * @author Haotian Zhang, wh, Palmer Xu
*/ */
public class PolarisRestTemplateAutoConfigurationTest { public class RpcEnhancementAutoConfigurationTest {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of( .withConfiguration(AutoConfigurations.of(
PolarisRestTemplateAutoConfigurationTester.class,
PolarisContextAutoConfiguration.class, PolarisContextAutoConfiguration.class,
PolarisCircuitBreakerAutoConfiguration.class)) RpcEnhancementAutoConfiguration.class,
PolarisRestTemplateAutoConfigurationTester.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true"); .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@Test @Test
public void testInitialization() { public void testDefaultInitialization() {
this.contextRunner this.contextRunner.run(context -> {
.run(context -> { assertThat(context).hasSingleBean(ConsumerAPI.class);
assertThat(context).hasSingleBean(PolarisRestTemplateModifier.class); assertThat(context).hasSingleBean(EnhancedFeignBeanPostProcessor.class);
assertThat(context).hasSingleBean(PolarisRestTemplateResponseErrorHandler.class); assertThat(context).hasSingleBean(SuccessPolarisReporter.class);
assertThat(context).hasSingleBean(ExceptionPolarisReporter.class);
assertThat(context).hasSingleBean(EnhancedRestTemplateModifier.class);
assertThat(context).hasSingleBean(EnhancedRestTemplateReporter.class);
}); });
} }
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@AutoConfigureBefore(PolarisCircuitBreakerAutoConfiguration.class)
static class PolarisRestTemplateAutoConfigurationTester { static class PolarisRestTemplateAutoConfigurationTester {
@Bean @Bean

@ -0,0 +1,64 @@
/*
* 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 org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.HttpStatus.MOVED_PERMANENTLY;
import static org.springframework.http.HttpStatus.MULTIPLE_CHOICES;
import static org.springframework.http.HttpStatus.Series.CLIENT_ERROR;
import static org.springframework.http.HttpStatus.Series.SERVER_ERROR;
/**
* Test For {@link RpcEnhancementReporterProperties}.
*
* @author Haotian Zhang
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RpcEnhancementReporterPropertiesTest.TestApplication.class)
@ActiveProfiles("test")
public class RpcEnhancementReporterPropertiesTest {
@Autowired
private RpcEnhancementReporterProperties rpcEnhancementReporterProperties;
@Test
public void testDefaultInitialization() {
assertThat(rpcEnhancementReporterProperties).isNotNull();
assertThat(rpcEnhancementReporterProperties.isIgnoreInternalServerError()).isFalse();
assertThat(rpcEnhancementReporterProperties.getSeries()).isNotEmpty();
assertThat(rpcEnhancementReporterProperties.getSeries().get(0)).isEqualTo(CLIENT_ERROR);
assertThat(rpcEnhancementReporterProperties.getSeries().get(1)).isEqualTo(SERVER_ERROR);
assertThat(rpcEnhancementReporterProperties.getStatuses()).isNotEmpty();
assertThat(rpcEnhancementReporterProperties.getStatuses().get(0)).isEqualTo(MULTIPLE_CHOICES);
assertThat(rpcEnhancementReporterProperties.getStatuses().get(1)).isEqualTo(MOVED_PERMANENTLY);
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -15,9 +15,8 @@
* specific language governing permissions and limitations under the License. * 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 feign.Client; import feign.Client;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -35,19 +34,17 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Test for {@link PolarisFeignBeanPostProcessor}. * Test for {@link EnhancedFeignBeanPostProcessor}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
public class PolarisFeignBeanPostProcessorTest { public class EnhancedFeignBeanPostProcessorTest {
private PolarisFeignBeanPostProcessor polarisFeignBeanPostProcessor; private EnhancedFeignBeanPostProcessor enhancedFeignBeanPostProcessor;
@Before @Before
public void setUp() { public void setUp() {
ConsumerAPI consumerAPI = mock(ConsumerAPI.class); enhancedFeignBeanPostProcessor = new EnhancedFeignBeanPostProcessor(null);
polarisFeignBeanPostProcessor = new PolarisFeignBeanPostProcessor(consumerAPI);
} }
@Test @Test
@ -66,24 +63,24 @@ public class PolarisFeignBeanPostProcessorTest {
} }
return null; return null;
}).when(beanFactory).getBean(any(Class.class)); }).when(beanFactory).getBean(any(Class.class));
polarisFeignBeanPostProcessor.setBeanFactory(beanFactory); enhancedFeignBeanPostProcessor.setBeanFactory(beanFactory);
// isNeedWrap(bean) == false // isNeedWrap(bean) == false
Object bean1 = new Object(); Object bean1 = new Object();
Object bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean1, "bean1"); Object bean = enhancedFeignBeanPostProcessor.postProcessBeforeInitialization(bean1, "bean1");
assertThat(bean).isNotInstanceOfAny( assertThat(bean).isNotInstanceOfAny(
PolarisFeignClient.class, EnhancedFeignClient.class,
PolarisFeignBlockingLoadBalancerClient.class); EnhancedFeignBlockingLoadBalancerClient.class);
// bean instanceOf Client.class // bean instanceOf Client.class
Client bean2 = mock(Client.class); Client bean2 = mock(Client.class);
bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean2, "bean2"); bean = enhancedFeignBeanPostProcessor.postProcessBeforeInitialization(bean2, "bean2");
assertThat(bean).isInstanceOf(PolarisFeignClient.class); assertThat(bean).isInstanceOf(EnhancedFeignClient.class);
// bean instanceOf FeignBlockingLoadBalancerClient.class // bean instanceOf FeignBlockingLoadBalancerClient.class
FeignBlockingLoadBalancerClient bean3 = mock(FeignBlockingLoadBalancerClient.class); FeignBlockingLoadBalancerClient bean4 = mock(FeignBlockingLoadBalancerClient.class);
doReturn(mock(Client.class)).when(bean3).getDelegate(); doReturn(mock(Client.class)).when(bean4).getDelegate();
bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean3, "bean3"); bean = enhancedFeignBeanPostProcessor.postProcessBeforeInitialization(bean4, "bean4");
assertThat(bean).isInstanceOf(PolarisFeignBlockingLoadBalancerClient.class); assertThat(bean).isInstanceOf(EnhancedFeignBlockingLoadBalancerClient.class);
} }
} }

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

Loading…
Cancel
Save